chore(build): re-apply build-reorg follow-ups (compat fallback + deploy docs) (#3127)

* build(compat): serve CLI falls back dist/ -> app/ for upgrade safety

Backward-compat hardening: the npm CLI prefers the new dist/ standalone but falls
back to the legacy app/ location, so an upgrade over a partially-replaced install
(or a package built before the app/->dist/ rename) still boots. Deployed runtimes
(VPS app/, Docker /app, Electron resources/app) are unchanged by design.

* docs(deploy): stop pm2 before rsync --delete to avoid transient chunk error

rsync --delete removing chunk files under a live server produced the transient [Shutdown] Cannot find module ./chunks/NNNNN.js. Stop the process first, then start with a clean module graph.

* test(cli): cover serve APP_DIR dist/->app/ backward-compat fallback
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-06-03 18:12:30 -03:00
committed by GitHub
parent 0231fbb335
commit c9620eb741
5 changed files with 55 additions and 10 deletions

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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);

View File

@@ -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 <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"\)/,
);
});