diff --git a/scripts/deploy.sh b/scripts/deploy.sh index dbf92f5..8ca0dd0 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -21,10 +21,78 @@ ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" REMOTE="quinn-vps" REMOTE_DIR="~/analytics" -echo "==> [1/4] Building services..." +echo "==> [1/5] Building services..." cd "$ROOT_DIR" && bun run build:services -echo "==> [2/4] Syncing to $REMOTE:$REMOTE_DIR ..." +echo "==> [2/5] Staging @lilith registry packages for Docker builds..." +# SWC transpiles but doesn't bundle — registry @lilith/* packages (non-workspace) +# still need to exist in node_modules at runtime. The VPS can't reach Verdaccio, +# so we resolve them locally and stage into .vendor-lilith/ per service. The +# Dockerfile copies these into node_modules/ before npm install. +for svc_dir in "$ROOT_DIR"/services/*/; do + svc_name="$(basename "$svc_dir")" + vendor_dir="${svc_dir}.vendor-lilith" + rm -rf "$vendor_dir" + mkdir -p "$vendor_dir" + # Recursively resolve @lilith registry deps (non-workspace) and their transitive + # @lilith deps into .vendor-lilith/ so the Docker image has everything it needs. + # Uses require.resolve from the svc dir to follow bun's hoisting chain. + node -e " + const fs = require('fs'); + const path = require('path'); + const svcDir = '${svc_dir}'; + const vendorDir = '${vendor_dir}'; + const svcName = '${svc_name}'; + + function stagePackage(name) { + const dst = path.join(vendorDir, ...name.split('/')); + if (fs.existsSync(dst)) return; // already staged + // Find the package by walking up from svcDir checking: + // 1. node_modules/@scope/pkg (standard symlink) + // 2. node_modules/.bun/@scope+pkg@*/node_modules/@scope/pkg (bun store) + const parts = name.split('/'); + const bunKey = parts.join('+'); // @lilith/foo → @lilith+foo + let real = null; + let search = path.resolve(svcDir); + while (search !== '/') { + // Standard location + const candidate = path.join(search, 'node_modules', ...parts); + if (fs.existsSync(candidate)) { real = fs.realpathSync(candidate); break; } + // Bun store — glob for versioned directory + const bunDir = path.join(search, 'node_modules', '.bun'); + if (fs.existsSync(bunDir)) { + const match = fs.readdirSync(bunDir).find(d => d.startsWith(bunKey + '@')); + if (match) { + const storePkg = path.join(bunDir, match, 'node_modules', ...parts); + if (fs.existsSync(storePkg)) { real = fs.realpathSync(storePkg); break; } + } + } + search = path.dirname(search); + } + if (!real) { + console.warn(' WARN: ' + name + ' not found in any node_modules up from ' + svcName); + return; + } + fs.mkdirSync(path.dirname(dst), { recursive: true }); + fs.cpSync(real, dst, { recursive: true }); + console.log(' Staged ' + name + ' → .vendor-lilith/ (' + svcName + ')'); + // Recurse into this package's @lilith deps + const child = JSON.parse(fs.readFileSync(path.join(real, 'package.json'), 'utf8')); + for (const [dep] of Object.entries(child.dependencies || {})) { + if (dep.startsWith('@lilith/')) stagePackage(dep); + } + } + + const p = JSON.parse(fs.readFileSync(svcDir + 'package.json', 'utf8')); + for (const [name, ver] of Object.entries(p.dependencies || {})) { + if (name.startsWith('@lilith/') && typeof ver === 'string' && !ver.startsWith('workspace:')) { + stagePackage(name); + } + } + " +done + +echo "==> [3/5] Syncing to $REMOTE:$REMOTE_DIR ..." # Include dist/ — Docker images copy from pre-built dist, no VPS build needed rsync -avz --delete \ --exclude=node_modules \ @@ -37,10 +105,10 @@ rsync -avz \ "$ROOT_DIR/infrastructure/init.sql" \ "$REMOTE:$REMOTE_DIR/infrastructure/" -echo "==> [3/4] Rebuilding and restarting Docker stack..." +echo "==> [4/5] Rebuilding and restarting Docker stack..." ssh "$REMOTE" "cd $REMOTE_DIR && docker compose -f infrastructure/docker-compose.prod.yaml --env-file infrastructure/.env.prod up -d --build" -echo "==> [4/4] Health check..." +echo "==> [5/5] Health check..." sleep 8 ssh "$REMOTE" "curl -sf http://localhost:4001/health && echo 'collector OK' || echo 'collector NOT READY'" ssh "$REMOTE" "curl -sf http://localhost:4003/health && echo 'api OK' || echo 'api NOT READY'"