ci: implement 3-stage deployment pipeline with semantic-release

This commit is contained in:
badblocks 2025-07-31 19:18:43 -07:00
parent af8d86dedb
commit d4d84ad0db
No known key found for this signature in database
13 changed files with 271 additions and 169 deletions

View file

@ -1,21 +1,50 @@
# Dependencies and build artifacts
node_modules
.nuxt
.output
dist
.cache
.parcel-cache
# Git and version control
.git
.gitignore
# Environment files
.env
.env.local
.env.*.local
# Logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
*.log
# IDE and editor files
.DS_Store
.vscode
.idea
*.log
# Testing and coverage
coverage
.nyc_output
dist
# Temporary files
tmp
temp
.cache
.parcel-cache
# Semantic-release artifacts (copied explicitly in final stage)
CHANGELOG.md
# Development files
bun.lock
# CI/CD files that don't need to be in the container
.github/
.releaserc.json
# Documentation that gets copied explicitly in final stage
README.md
LICENSE
CLAUDE.md

1
.gitignore vendored
View file

@ -19,7 +19,6 @@ logs
.idea
public/headshot_pp.png
.secrets
.claude
# Local env files
.env

View file

@ -1,9 +1,44 @@
{
"branches": [
"main"
],
"branches": ["main"],
"preset": "conventionalcommits",
"presetConfig": {
"types": [
{ "type": "breaking", "section": "BREAKING CHANGES" },
{ "type": "feat", "section": "New Features" },
{ "type": "new", "section": "New Features" },
{ "type": "update", "section": "Dependencies" },
{ "type": "upgrade", "section": "Dependencies" },
{ "type": "deps", "section": "Dependencies" },
{ "type": "fix", "section": "Bug Fixes" },
{ "type": "build", "section": "Building and CI" },
{ "type": "ci", "section": "Building and CI" },
{ "type": "chore", "hidden": true },
{ "type": "docs", "hidden": true },
{ "type": "refactor", "hidden": true }
]
},
"plugins": [
[
"@semantic-release/commit-analyzer",
{
"releaseRules": [
{ "breaking": true, "release": "major" },
{ "revert": true, "release": "patch" },
{ "type": "breaking", "release": "major" },
{ "type": "feat", "release": "minor" },
{ "type": "update", "release": "minor" },
{ "type": "upgrade", "release": "minor" },
{ "type": "deps", "release": "minor" },
{ "type": "new", "release": "minor" },
{ "type": "fix", "release": "patch" },
{ "type": "build", "release": false },
{ "type": "ci", "release": false },
{ "type": "chore", "release": false },
{ "type": "docs", "release": false },
{ "type": "refactor", "release": false }
]
}
],
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
[
@ -16,7 +51,7 @@
[
"@semantic-release/git",
{
"assets": ["CHANGELOG.md", "package.json", "package-lock.json", "bun.lock"],
"assets": ["CHANGELOG.md", "package.json", "package-lock.json"],
"message": "chore: Release ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
]

View file

@ -1,19 +1,62 @@
FROM node:24-slim
# Stage 1: Runtime dependencies
FROM node:24-slim AS deps
SHELL ["bash", "-exc"]
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y python3 make g++ curl net-tools && rm -rf /var/lib/apt/lists/*
# Install dependencies
RUN --mount=type=bind,source=package-lock.json,target=package-lock.json --mount=type=bind,source=package.json,target=package.json npm ci --omit=dev && npm cache clean --force
# Stage 2: Build dependencies - Includes dev dependencies for build
FROM node:24-slim AS build-deps
SHELL ["bash", "-exc"]
WORKDIR /app
ENV DEBIAN_FRONTEND=noninteractive
# Install system dependencies
RUN apt-get update && apt-get install -y python3 make g++ curl net-tools && rm -rf /var/lib/apt/lists/*
# Install all dependencies including dev dependencies for build
RUN --mount=type=bind,source=package-lock.json,target=package-lock.json --mount=type=bind,source=package.json,target=package.json npm ci && npm cache clean --force
# Stage 3: Source and build - Cache invalidated when source code changes
FROM build-deps AS builder
SHELL ["bash", "-exc"]
WORKDIR /app
# Copy source code (excluding semantic-release artifacts)
COPY nuxt.config.ts tsconfig.json tailwind.config.js eslint.config.mjs ./
COPY app/ ./app/
COPY server/ ./server/
COPY public/ ./public/
# Build the application (this is cached unless source code changes)
RUN npx nuxt build
# Stage 4: Runtime assembly - Only invalidated by version/metadata changes
FROM deps AS runtime
ARG VERSION=0.0.0
WORKDIR /app
RUN apt-get update && apt-get install -y python3 make g++ curl net-tools && rm -rf /var/lib/apt/lists/*
# Copy production node_modules from deps stage
COPY --from=deps /app/node_modules ./node_modules
COPY package.json package-lock.json* ./
# Copy built application from builder stage
COPY --from=builder /app/.output ./.output
RUN npm ci
COPY . .
RUN npx nuxt build
# Copy package configs
# These change frequently but don't affect the expensive build steps above
COPY package.json package-lock.json ./
# Set runtime environment
ENV NUXT_HOST=0.0.0.0
ENV NUXT_PORT=3000
ENV NODE_ENV=production
@ -22,6 +65,7 @@ ENV APP_VERSION=${VERSION}
ENV PUID=1000
ENV PGID=1000
# Set up data directory and permissions
RUN mkdir -p data && chown -R ${PUID}:${PGID} data && chown -R ${PUID}:${PGID} /app && chmod 755 /app data
EXPOSE 3000

View file

133
README.md
View file

@ -1,95 +1,142 @@
# 🚀 Personal Portfolio Website
# 🚀 Portfolio Bonanza
My schnazzy portfolio turbocharged with Nuxt 4 and zero-downtime deployment wizardry! ✨
A spicy little portfolio site with some absolutely ridiculous SMS shenanigans built on Nuxt 4! Looks like things are getting too spicy for the pepper! 🌶️
## ⭐ What's Inside
## ✨ The Good Stuff
- 🌙 **Dark UI** with DaisyUI components
- 📱 **SMS Contact Form** with phone verification (because emails are so last millennium)
- 🎨 **Smooth animations** and typing effects
- 🐳 **Zero-downtime deployments** via Docker + HAProxy
- 🔐 **WireGuard tunnel** to home SMS gateway
- 🌙 **Night mode everything** - DaisyUI components that won't burn your eyeballs
- 📱 **SMS contact form** - Because I get wayyy too many emails already
- 🎭 **Snazzy animations** - ScrollReveal + Typed.js doing their thang
- 🐳 **Zero-downtime deploys** - Blue-green magic with Docker & HAProxy
- 🔧 **Overengineered infrastructure** - We like to do things the hard way over here
## 🚀 Quick Start
## 🏃‍♂️ Getting Started
```bash
# Install dependencies
# Grab the dependencies
bun install
# Start development server
# Copy example env file and edit
cp .env.example .env && nano .env
# Fire up the dev server
bun run dev
# Build for production
# Build the contraption
bun run build
# Generate static site
# Bake it
bun run generate
```
**Coffee Levels:**
☕☕
☕☕☕
☕☕☕☕
🚨🚨🚨🚨🚨
**Current vibe levels:**
😴 sleepy
🙂 caffeinated
🤓 productive
🔥 on fire
💀 send halp
## 📱 Contact System Flow
## 📞 Contact Form Wizardry
A bamboozling two-step form: collect message → verify phone → SMS me directly!
Rate limited to prevent spam-a-geddon. 🚫
Two-step verification dance: drop your message → prove you're human with SMS → message gets yeeted to my phone via some questionable infrastructure choices.
## 🏗️ Architecture
Rate-limited because spam just sucksssss! 🛡️
## 🏗️ Infrastructure Tomfoolery
Here's where things get ~completely~ unhinged:
```
🌍 Internet → VPS → 🔐 WireGuard → 🏠 Android SMS Gateway
🌐 Your Browser → Some VPS → 🔐 WireGuard Tunnel
→ 🏠 Home Network → 🧟‍♂️ My Frankenstein Phone/SMS-Gateway
→ 📱 Someone's Legit Regular Phone
```
Copy `.env.example` to `.env` for configuration.
### The SMS Gateway Setup
I'm running the `android-sms-gateway` app on a completely deranged setup:
- **Hardware:** Pixel 1 with the battery surgically removed (no spicy pillows please)
- **Power:** Permanently plugged into the wall like some kind of cursed landline
- **Network:** Hardwired ethernet via USB-C adapter ("WhyFight" with WiFi?)
- **Purpose:** Sits there 24/7 just waiting to send OTPs and forward your messages to my actual phone
The whole contraption lives on my home network and the VPS reaches it through a WireGuard tunnel because apparently I enjoy making simple things complicated just to save $2 a month!
## 🌳 Git Workflow Madness
This repo follows a three-branch strategy with automated promotions because manually managing branches is for chumps:
- **`dev`** - Where the magic happens! All new features and fixes go here
- **`staging`** - Integration testing playground, auto-promoted from develop daily Monday-Friday at 13:00 UTC/05:00 AM PST
- **`main`** - Production branch, gets promoted from staging weekly on Mondays at 12:00 UTC/04:00 AM PST
### The Repository Setup
We're running a bit of a funky setup here:
- **Primary repo:** Self-hosted Forgejo instance (because it's more fun that way!)
- **Mirror:** GitHub (push-only mirror for CI/CD and visibility)
- **CI/CD:** GitHub Actions (triggered by the mirror's pushes or scheduled)
So the workflow is: push to Forgejo origin → auto-mirrors to GitHub → GitHub Actions does the heavy lifting.
### Branch Promotion Dance 💃
**Daily (develop → staging):**
- Checks if develop has new commits since last staging update
- Validates that the latest build actually passed
- Fast-forward merge to staging (keeps history clean)
- Supports `[skip ci]` in commit messages for any oopsies or boo-boos
**Weekly (staging → main):**
- Runs semantic-release on staging for proper versioning
- Fast-forward merge to main for production release
- Some branch rebasing gymnastics to keep develop up-to-date
- Also supports `[skip ci]`
## 🚢 Deployment Shenanigans
Push to `staging` or `release` branches to trigger blue-green deployments!
Any (automated) pushes to `staging` or `main` branches to trigger blue-green staging and production deployments! Easy peasy lemon squeezy!
### Blue-Green Magic ✨
1. **Build Phase:** Build new containers alongside old ones
1. **Build Phase:** Build new containers and validate
2. **Deploy Phase:** Health check the newbies
3. **Switch Phase:** HAProxy traffic switcheroo
4. **Cleanup Phase:** Cleanup old containers
3. **Switch Phase:** HAProxy config update and traffic switcheroo
4. **Cleanup Phase:** Nuke old containers from high orbit
5. **Moon Phases:** 🌑 🌒 🌓 🌔 🌝 🌖 🌗 🌘 🌚
### GitHub Secrets Setup
Set these in your repo for deployment thingamajigs:
- `DEPLOY_KEY`, `DEPLOY_HOST` - SSH access stuff
- SMS gateway credentials and phone number
- `NUXT_SUPER_SECRET_SALT` - for cryptographic tomfoolery
Deployments generate the needed .env from GitHub secrets, so make sure to slap some secrets up first!
### WireGuard Setup
Copy `wireguard/wg0.conf.template``wg0.conf` and fill in your tunnel deets.
The stack is rocking gluetun, so just set up your WIREGUARD_ environment variables in Github secrets:
## 🔒 Security Fortress
- 🔐 WireGuard tunnel encryption
- 🛡️ Container firewalls and non-root execution
- 🔢 TOTP phone verification + rate limiting
- 🔤 ASCII-only validation (emoji-proof!)
- 🔤 ASCII-only validation (No spammy weird characters please!)
## 🔧 Troubleshooting
## 🆘 When Things Go Wrong
```bash
# Check container health
docker-compose ps && docker logs portfolio
# Check if containers are actually alive
docker compose ps && docker compose logs portfolio
# Test SMS connectivity
docker exec portfolio curl -f http://192.168.0.XXX:9090
# Poke the SMS gateway to see if it's responsive
docker compose exec portfolio curl -f http://your-sms-gateway-ip:9090/health
# Check WireGuard tunnel status
docker compose exec portfolio wg show
```
**Debug Panic Levels:** 😎 → 🤔 → 😅 → 😰 → 💀 → 🍕
**Debugging stages of grief:** 😎 confident → 🤔 confused → 😅 nervous → 😰 panicking → 💀 accepting fate → 🍕 ordering pizza
## 📁 What's Where
```
├── app/ # Nuxt 4 frontend
├── server/ # API routes + SMS gateway libs
├── server/ # API routes
├── deploy/ # Deployment scripts
└── .github/ # CI/CD workflows
```
@ -104,4 +151,4 @@ This project is licensed under **AGPL 3.0 only** - see the [LICENSE](LICENSE) fi
---
*Built with ❤️ and lots of ☕*
*Built with ❤️ and lots of ☕! (Sheesh, if you've read this far, just hire me, please!)*

View file

@ -1,6 +1,6 @@
services:
portfolio:
image: badbl0cks/portfolio:${IMAGE_TAG:-stable}
image: ${IMAGE_NAME:-portfolio:latest}
# image: ghcr.io/xe/x/httpdebug
# entrypoint: ["/ko-app/httpdebug", "--bind", ":3000"]
container_name: portfolio-${RELEASE_TYPE}-${DEPLOYMENT_COLOR}
@ -21,7 +21,16 @@ services:
- "deployment.release_type=${RELEASE_TYPE}"
user: "1000:1000"
healthcheck:
test: ["CMD", "curl", "-f", "-s", "--max-time", "5", "http://localhost:3000/api/health"]
test:
[
"CMD",
"curl",
"-f",
"-s",
"--max-time",
"5",
"http://localhost:3000/api/health",
]
interval: 30s
timeout: 15s
retries: 3
@ -47,7 +56,8 @@ services:
aliases:
- wireguard-${RELEASE_TYPE}
healthcheck:
test: ["CMD", "ping", "-c", "1", "-W", "3", "$$NUXT_ANDROID_SMS_GATEWAY_IP"]
test:
["CMD", "ping", "-c", "1", "-W", "3", "$$NUXT_ANDROID_SMS_GATEWAY_IP"]
interval: 30s
timeout: 15s
retries: 3

View file

@ -10,6 +10,9 @@ else
exit 1
fi
# Validate common required environment variables
require_var "DEPLOY_HOST"
readonly HAPROXY_BASE_DIR="/srv/haproxy"
readonly HAPROXY_CONFIGS_DIR="${HAPROXY_BASE_DIR}/configs"
readonly HAPROXY_COMPOSE_FILE="${HAPROXY_BASE_DIR}/docker-compose.yml"

View file

@ -9,14 +9,12 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/common-lib.sh"
# Validate required environment variables
require_var "DOCKER_HOST"
require_var "REPO_PROJECT_PATH"
require_var "REPO_NAME_ONLY"
require_var "REPO"
require_var "IMAGE_TAR"
require_var "PROD"
require_var "DOMAIN"
require_var "WIREGUARD_ENDPOINT_HOST"
require_var "REPO_PROJECT_PATH"
require_var "DEPLOY_HOST"
validate_deployment_env
@ -76,10 +74,18 @@ echo "DEPLOYMENT_COLOR=\"${NEW_COLOR}\"" >> .env
echo "RELEASE_TYPE=\"${RELEASE_TYPE}\"" >> .env
echo "WIREGUARD_ENDPOINT_IP=\"${WIREGUARD_ENDPOINT_IP}\"" >> .env
# Set computed image name based on release type
if [ "$RELEASE_TYPE" = "staging" ]; then
IMAGE_NAME="portfolio-dev:${IMAGE_TAG}"
else
IMAGE_NAME="portfolio:${IMAGE_TAG}"
fi
echo "IMAGE_NAME=\"${IMAGE_NAME}\"" >> .env
echo "📋 Copying deployment files..."
scp deploy/docker-compose.yml deploy/haproxy.cfg .env deploy:"${NEW_RELEASE_PATH}/"
echo "🐳 Loading Docker image..."
echo "🐳 Loading Docker image (${IMAGE_TAR})..."
docker load -i "${IMAGE_TAR}"
PROJECT_NAME=$(get_project_name "$NEW_COLOR")

View file

@ -1,49 +0,0 @@
#!/bin/bash
set -euo pipefail
# Generate Docker tags based on git ref and environment
# Usage: ./generate-docker-tags.sh IMAGE_BASE GIT_SHA GIT_REF PROD
if [ $# -ne 4 ]; then
echo "Error: Invalid number of arguments" > /dev/stderr
echo "Usage: $0 IMAGE_BASE GIT_SHA GIT_REF PROD" > /dev/stderr
exit 1
fi
IMAGE_BASE="$1"
GIT_SHA="$2"
GIT_REF="$3"
PROD="$4"
# Validate inputs
if [ -z "$IMAGE_BASE" ] || [ -z "$GIT_SHA" ]; then
echo "Error: IMAGE_BASE and GIT_SHA cannot be empty" > /dev/stderr
exit 1
fi
# Always include SHA tags
echo "${IMAGE_BASE}:sha-${GIT_SHA:0:7}"
echo "${IMAGE_BASE}:sha-${GIT_SHA}"
# Handle version tags
if [[ "$GIT_REF" =~ ^refs/tags/v([0-9]+)\.([0-9]+)\.([0-9]+)(-.*)?$ ]]; then
MAJOR="${BASH_REMATCH[1]}"
MINOR="${BASH_REMATCH[2]}"
PATCH="${BASH_REMATCH[3]}"
PRERELEASE="${BASH_REMATCH[4]}"
if [[ -z "$PRERELEASE" ]] && [[ "$PROD" == "true" ]]; then
echo "${IMAGE_BASE}:latest"
echo "${IMAGE_BASE}:stable"
[[ "$MAJOR" -gt 0 ]] && echo "${IMAGE_BASE}:v${MAJOR}"
echo "${IMAGE_BASE}:v${MAJOR}.${MINOR}"
echo "${IMAGE_BASE}:v${MAJOR}.${MINOR}.${PATCH}"
else
echo "${IMAGE_BASE}:latest-staging"
echo "${IMAGE_BASE}:staging"
echo "${IMAGE_BASE}:v${MAJOR}.${MINOR}.${PATCH}-prerelease"
fi
elif [[ "$PROD" == "false" ]]; then
echo "${IMAGE_BASE}:latest-staging"
echo "${IMAGE_BASE}:staging"
fi

View file

@ -1,40 +0,0 @@
#!/bin/bash
set -euo pipefail
# Parse repository name and generate project paths
# Usage: ./parse-repository-name.sh GITHUB_REPOSITORY
if [ $# -eq 0 ]; then
echo "Error: No repository name provided" > /dev/stderr
echo "Usage: $0 GITHUB_REPOSITORY" > /dev/stderr
exit 1
fi
GITHUB_REPOSITORY="$1"
echo "GITHUB_REPOSITORY: $GITHUB_REPOSITORY" > /dev/stderr
if [[ "$GITHUB_REPOSITORY" == *".git" ]]; then
if [[ "$GITHUB_REPOSITORY" == "https://"* ]]; then
echo "GITHUB_REPOSITORY ends in .git and is an HTTPS URI" > /dev/stderr
REPO=$(echo "$GITHUB_REPOSITORY" | sed 's/\.git$//' | cut -d'/' -f4-5 | sed 's/[^a-zA-Z0-9\/-]/-/g')
elif [[ "$GITHUB_REPOSITORY" == "git@"* ]]; then
echo "GITHUB_REPOSITORY ends in .git and is an SSH URI" > /dev/stderr
REPO=$(echo "$GITHUB_REPOSITORY" | sed 's/\.git$//' | cut -d':' -f2 | sed 's/[^a-zA-Z0-9\/-]/-/g')
else
echo "GITHUB_REPOSITORY ends in .git and is not a URI" > /dev/stderr
REPO=$(echo "$GITHUB_REPOSITORY" | sed 's/\.git$//' | sed 's/[^a-zA-Z0-9\/-]/-/g')
fi
else
echo "GITHUB_REPOSITORY is not a URI" > /dev/stderr
REPO=$(echo "$GITHUB_REPOSITORY" | sed 's/[^a-zA-Z0-9\/-]/-/g')
fi
REPO_NAME_ONLY=$(echo "$REPO" | cut -d'/' -f2)
# Default path - will be modified by deployment script based on environment
REPO_PROJECT_PATH="/srv/${REPO_NAME_ONLY}"
# Output in format that can be sourced - using printf %q for proper escaping
printf "export REPO=%q\n" "$REPO"
printf "export REPO_NAME_ONLY=%q\n" "$REPO_NAME_ONLY"
printf "export REPO_PROJECT_PATH=%q\n" "$REPO_PROJECT_PATH"

35
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "portfolio",
"version": "0.0.0-development",
"version": "0.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "portfolio",
"version": "0.0.0-development",
"version": "0.0.1",
"hasInstallScript": true,
"dependencies": {
"@nuxt/fonts": "0.11.4",
@ -16,6 +16,7 @@
"@nuxt/ui": "3.2.0",
"@unhead/vue": "^2.0.12",
"android-sms-gateway": "^3.0.0",
"conventional-changelog-conventionalcommits": "^9.1.0",
"daisyui": "^5.0.46",
"husky": "^9.1.7",
"nuxt": "^4.0.1",
@ -784,7 +785,9 @@
}
},
"node_modules/@eslint/plugin-kit": {
"version": "0.3.3",
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz",
"integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==",
"devOptional": true,
"license": "Apache-2.0",
"dependencies": {
@ -5910,7 +5913,6 @@
},
"node_modules/array-ify": {
"version": "1.0.0",
"dev": true,
"license": "MIT"
},
"node_modules/ast-kit": {
@ -6848,7 +6850,6 @@
},
"node_modules/compare-func": {
"version": "2.0.0",
"dev": true,
"license": "MIT",
"dependencies": {
"array-ify": "^1.0.0",
@ -6857,7 +6858,6 @@
},
"node_modules/compare-func/node_modules/dot-prop": {
"version": "5.3.0",
"dev": true,
"license": "MIT",
"dependencies": {
"is-obj": "^2.0.0"
@ -6959,6 +6959,18 @@
"node": ">=18"
}
},
"node_modules/conventional-changelog-conventionalcommits": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-9.1.0.tgz",
"integrity": "sha512-MnbEysR8wWa8dAEvbj5xcBgJKQlX/m0lhS8DsyAAWDHdfs2faDJxTgzRYlRYpXSe7UiKrIIlB4TrBKU9q9DgkA==",
"license": "ISC",
"dependencies": {
"compare-func": "^2.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/conventional-changelog-writer": {
"version": "8.2.0",
"dev": true,
@ -9988,7 +10000,9 @@
}
},
"node_modules/ipx": {
"version": "2.1.0",
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ipx/-/ipx-2.1.1.tgz",
"integrity": "sha512-XuM9FEGOT+/45mfAWZ5ykwkZ/oE7vWpd1iWjRffMWlwAYIRzb/xD6wZhQ4BzmPMX6Ov5dqK0wUyD0OEN9oWT6g==",
"license": "MIT",
"optional": true,
"dependencies": {
@ -10184,7 +10198,6 @@
},
"node_modules/is-obj": {
"version": "2.0.0",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@ -16673,6 +16686,8 @@
},
"node_modules/semantic-release": {
"version": "24.2.7",
"resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-24.2.7.tgz",
"integrity": "sha512-g7RssbTAbir1k/S7uSwSVZFfFXwpomUB9Oas0+xi9KStSCmeDXcA7rNhiskjLqvUe/Evhx8fVCT16OSa34eM5g==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -17996,7 +18011,9 @@
}
},
"node_modules/tmp": {
"version": "0.2.3",
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.4.tgz",
"integrity": "sha512-UdiSoX6ypifLmrfQ/XfiawN6hkjSBpCjhKxxZcWlUUmoXLaCKQU0bx4HF/tdDK2uzRuchf1txGvrWBzYREssoQ==",
"license": "MIT",
"engines": {
"node": ">=14.14"

View file

@ -1,6 +1,6 @@
{
"name": "portfolio",
"version": "0.0.0-development",
"version": "0.0.1",
"private": true,
"type": "module",
"scripts": {
@ -31,6 +31,7 @@
"@nuxt/ui": "3.2.0",
"@unhead/vue": "^2.0.12",
"android-sms-gateway": "^3.0.0",
"conventional-changelog-conventionalcommits": "^9.1.0",
"daisyui": "^5.0.46",
"husky": "^9.1.7",
"nuxt": "^4.0.1",