diff --git a/.agents/skills/deploy-vps-akamai-cc/SKILL.md b/.agents/skills/deploy-vps-akamai-cc/SKILL.md index f9c8fbcbd7..db8dd4285c 100644 --- a/.agents/skills/deploy-vps-akamai-cc/SKILL.md +++ b/.agents/skills/deploy-vps-akamai-cc/SKILL.md @@ -51,17 +51,20 @@ OMNIROUTE_VERSION=$(node -p "require('/home/diegosouzapw/dev/proxys/OmniRoute/pa ssh root@69.164.221.35 "cp -a /usr/lib/node_modules/omniroute/app /usr/lib/node_modules/omniroute/app.bak-${OMNIROUTE_VERSION}" ``` -### 3. Rsync `dist/` → remote `app/` +### 3. Stop the process, then rsync `dist/` → remote `app/` -The VPS image directory is still named `app/`; we ship the **contents** of our local `dist/`: +The VPS image directory is still named `app/`; we ship the **contents** of our local `dist/`. **Stop the running process FIRST** so `rsync --delete` never removes chunk files out from under the live server — that race produces the transient `[Shutdown] Cannot find module ./chunks/NNNNN.js` seen mid-deploy: // turbo-all ```bash +ssh root@69.164.221.35 "pm2 stop omniroute" rsync -az --delete /home/diegosouzapw/dev/proxys/OmniRoute/dist/ root@69.164.221.35:/usr/lib/node_modules/omniroute/app/ ``` -### 4. Restart + health check +### 4. Start + health check + +`pm2 restart` on a stopped app starts it again with a clean module graph: ```bash ssh root@69.164.221.35 "pm2 restart omniroute --update-env && pm2 save" diff --git a/.agents/skills/deploy-vps-both-cc/SKILL.md b/.agents/skills/deploy-vps-both-cc/SKILL.md index bc61de9bb7..aeeb54f4ff 100644 --- a/.agents/skills/deploy-vps-both-cc/SKILL.md +++ b/.agents/skills/deploy-vps-both-cc/SKILL.md @@ -54,17 +54,20 @@ ssh root@192.168.0.15 "cp -a /usr/lib/node_modules/omniroute/app /usr/lib/node_m wait ``` -### 3. Rsync `dist/` → remote `app/` on both VPSs +### 3. Stop the processes, then rsync `dist/` → remote `app/` on both VPSs -The VPS image directory is still named `app/`; we ship the **contents** of our local `dist/`: +The VPS image directory is still named `app/`; we ship the **contents** of our local `dist/`. **Stop both running processes FIRST** so `rsync --delete` never removes chunk files out from under a live server — that race produces the transient `[Shutdown] Cannot find module ./chunks/NNNNN.js` seen mid-deploy: // turbo-all ```bash +ssh root@69.164.221.35 "pm2 stop omniroute" & ssh root@192.168.0.15 "pm2 stop omniroute" & wait rsync -az --delete /home/diegosouzapw/dev/proxys/OmniRoute/dist/ root@69.164.221.35:/usr/lib/node_modules/omniroute/app/ && rsync -az --delete /home/diegosouzapw/dev/proxys/OmniRoute/dist/ root@192.168.0.15:/usr/lib/node_modules/omniroute/app/ ``` -### 4. Restart + health check on both VPSs +### 4. Start + health check on both VPSs + +`pm2 restart` on a stopped app starts it again with a clean module graph: ```bash ssh root@69.164.221.35 "pm2 restart omniroute --update-env && pm2 save" diff --git a/.agents/skills/deploy-vps-local-cc/SKILL.md b/.agents/skills/deploy-vps-local-cc/SKILL.md index 273d65ce8d..fa85124467 100644 --- a/.agents/skills/deploy-vps-local-cc/SKILL.md +++ b/.agents/skills/deploy-vps-local-cc/SKILL.md @@ -51,17 +51,20 @@ OMNIROUTE_VERSION=$(node -p "require('/home/diegosouzapw/dev/proxys/OmniRoute/pa ssh root@192.168.0.15 "cp -a /usr/lib/node_modules/omniroute/app /usr/lib/node_modules/omniroute/app.bak-${OMNIROUTE_VERSION}" ``` -### 3. Rsync `dist/` → remote `app/` +### 3. Stop the process, then rsync `dist/` → remote `app/` -The VPS image directory is still named `app/`; we ship the **contents** of our local `dist/`: +The VPS image directory is still named `app/`; we ship the **contents** of our local `dist/`. **Stop the running process FIRST** so `rsync --delete` never removes chunk files out from under the live server — that race produces the transient `[Shutdown] Cannot find module ./chunks/NNNNN.js` seen mid-deploy: // turbo-all ```bash +ssh root@192.168.0.15 "pm2 stop omniroute" rsync -az --delete /home/diegosouzapw/dev/proxys/OmniRoute/dist/ root@192.168.0.15:/usr/lib/node_modules/omniroute/app/ ``` -### 4. Restart + health check +### 4. Start + health check + +`pm2 restart` on a stopped app starts it again with a clean module graph: ```bash ssh root@192.168.0.15 "pm2 restart omniroute --update-env && pm2 save" diff --git a/bin/cli/commands/serve.mjs b/bin/cli/commands/serve.mjs index de0a5c826f..e67697f72a 100644 --- a/bin/cli/commands/serve.mjs +++ b/bin/cli/commands/serve.mjs @@ -10,7 +10,11 @@ import { isTermux } from "../../../scripts/build/postinstallSupport.mjs"; const __dirname = dirname(fileURLToPath(import.meta.url)); const ROOT = join(__dirname, "..", "..", ".."); -const APP_DIR = join(ROOT, "dist"); +// The standalone bundle ships in `dist/` (since the build-output-isolation +// refactor). Fall back to the legacy `app/` location so an upgrade over a +// partially-replaced install — or a package built before the rename — still +// boots. Backward-compatible by design: every deployed runtime keeps its path. +const APP_DIR = existsSync(join(ROOT, "dist", "server.js")) ? join(ROOT, "dist") : join(ROOT, "app"); function parsePort(value, fallback) { const parsed = parseInt(String(value), 10); diff --git a/tests/unit/cli-serve-port.test.ts b/tests/unit/cli-serve-port.test.ts index 41982b13ec..72e868f468 100644 --- a/tests/unit/cli-serve-port.test.ts +++ b/tests/unit/cli-serve-port.test.ts @@ -54,3 +54,35 @@ test("serve command: --port option has no Commander default", async () => { // Ensure the option does NOT have a third argument (Commander default) assert.match(serveSource, /\.option\("--port ",\s*t\("serve\.port"\)\)/); }); + +/** + * Replicate the APP_DIR resolution from bin/cli/commands/serve.mjs to verify the + * backward-compatibility fallback introduced by the build-output-isolation refactor: + * the standalone bundle now ships in `dist/`, but an upgrade over a partially-replaced + * install — or a package built before the rename — must still boot from legacy `app/`. + */ +function resolveAppDir(root: string, distServerExists: boolean): string { + return distServerExists ? `${root}/dist` : `${root}/app`; +} + +test("serve app dir: resolves to dist/ when dist/server.js exists (current layout)", () => { + assert.equal(resolveAppDir("/opt/omniroute", true), "/opt/omniroute/dist"); +}); + +test("serve app dir: falls back to legacy app/ when dist/server.js is absent (upgrade safety)", () => { + assert.equal(resolveAppDir("/opt/omniroute", false), "/opt/omniroute/app"); +}); + +test("serve command: APP_DIR keeps the dist/ -> app/ backward-compat fallback", async () => { + const fs = await import("node:fs"); + const path = await import("node:path"); + const serveSource = fs.readFileSync( + path.resolve(import.meta.dirname, "../../bin/cli/commands/serve.mjs"), + "utf-8", + ); + // Must probe dist/server.js and fall back to app/ — never hard-code dist/ only. + assert.match( + serveSource, + /existsSync\(join\(ROOT, "dist", "server\.js"\)\)\s*\?\s*join\(ROOT, "dist"\)\s*:\s*join\(ROOT, "app"\)/, + ); +});