build(layer2+3): propagate .build/+dist/ to Docker/Electron/CI; codify light deploy; docs

Layer 3: Dockerfile COPY .next/standalone -> .build/next/standalone (+cache mount);
electron stage -> .build/electron-standalone + extraResources; CI asserts dist/server.js;
eslint ignores .build/**+dist/**. Layer 2: deploy-vps-* skills use build:release + rsync(dist)
-> remote app/ + pm2 restart + BUILD_SHA verify (drop npm-i-g/legacy-peer-deps/manual-wreq).
Docs (RELEASE_CHECKLIST/CONTRIBUTING/AGENTS/CHANGELOG) describe src/+.build/+dist/ layout.
Verified: docker build exit 0 + health 200; electron stage OK; no stale build-output refs.
This commit is contained in:
diegosouzapw
2026-06-03 15:51:07 -03:00
parent 5efeeb183f
commit e390a8d633
13 changed files with 330 additions and 52 deletions

View File

@@ -5,36 +5,99 @@ description: Deploy the latest OmniRoute code to the Akamai VPS (69.164.221.35)
# Deploy to Akamai VPS Workflow
Deploy OmniRoute to the Akamai VPS using `npm pack + scp` + PM2.
Deploy OmniRoute to the Akamai VPS using a clean `build:release` + `rsync` + PM2 restart.
**Akamai VPS:** `69.164.221.35`
**Process manager:** PM2 (`omniroute`)
**Port:** `20128`
**Remote install dir:** `/usr/lib/node_modules/omniroute/app/` (VPS image dir — unchanged)
> [!IMPORTANT]
> **Build must run where `node_modules` is REAL.** A git worktree with symlinked
> `node_modules` breaks the Next.js standalone assembly. Always build from the main
> checkout (or an `npm ci`'d worktree), never from `OmniRoute-*` worktrees unless
> you first ran `npm ci` inside them.
## Steps
### 1. Build + pack locally
### 1. Clean build + sentinel
Run from a checkout with a **real** `node_modules`:
// turbo
```bash
cd /home/diegosouzapw/dev/proxys/OmniRoute && rm -f omniroute-*.tgz && rm -rf .next/cache app/.next/cache && npm run build:cli && rm -rf app/logs app/coverage app/.git app/.app-build-backup* && npm pack --ignore-scripts
cd /home/diegosouzapw/dev/proxys/OmniRoute && npm run build:release
```
### 2. Copy to Akamai VPS and install
`build:release` does:
1. Deletes `.build/` and `dist/` (clean rebuild — no stale cache)
2. Runs `next build``.build/next/` (intermediates)
3. Assembles the shippable bundle into `dist/` via `assembleStandalone`
4. Writes `dist/BUILD_SHA` = `git rev-parse --short HEAD` (deploy sentinel)
Verify the sentinel before shipping:
```bash
cat /home/diegosouzapw/dev/proxys/OmniRoute/dist/BUILD_SHA && git -C /home/diegosouzapw/dev/proxys/OmniRoute rev-parse --short HEAD
```
Both lines must match. If they differ, the build is stale — re-run `build:release`.
### 2. Back up the running bundle on the VPS
```bash
OMNIROUTE_VERSION=$(node -p "require('/home/diegosouzapw/dev/proxys/OmniRoute/package.json').version")
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/`
The VPS image directory is still named `app/`; we ship the **contents** of our local `dist/`:
// turbo-all
```bash
scp omniroute-*.tgz root@69.164.221.35:/tmp/
rsync -az --delete /home/diegosouzapw/dev/proxys/OmniRoute/dist/ root@69.164.221.35:/usr/lib/node_modules/omniroute/app/
```
### 4. Restart + health check
```bash
ssh root@69.164.221.35 "pm2 restart omniroute --update-env && pm2 save"
```
```bash
ssh root@69.164.221.35 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts --legacy-peer-deps && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && (pm2 delete omniroute 2>/dev/null || true) && pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Akamai done'"
sleep 5 && curl -sf http://69.164.221.35:20128/api/monitoring/health | jq '{status:.status, version:.version}'
```
### 3. Verify the deployment
Expected: HTTP 200, `"status": "ok"` (or equivalent health payload).
### 5. Verify the deployment
```bash
curl -s -o /dev/null -w 'AKAMAI HTTP %{http_code}\n' http://69.164.221.35:20128/
```
## Rollback
If the health check fails, restore the backup:
```bash
OMNIROUTE_VERSION=$(node -p "require('/home/diegosouzapw/dev/proxys/OmniRoute/package.json').version")
ssh root@69.164.221.35 "rm -rf /usr/lib/node_modules/omniroute/app && cp -a /usr/lib/node_modules/omniroute/app.bak-${OMNIROUTE_VERSION} /usr/lib/node_modules/omniroute/app && pm2 restart omniroute --update-env"
```
## Gotchas
- **Never build in a worktree with symlinked `node_modules`** — the standalone copy step
follows symlinks and produces a broken bundle. Use the main checkout or run `npm ci`
inside the worktree first.
- **`pm2 restart --update-env` does NOT re-read `ecosystem.config.cjs`** — if you changed
env vars in the ecosystem file, use `pm2 reload` or `pm2 delete + pm2 start` instead.
- **Parallel dev + release builds**: to avoid clobbering the dev server's `.build/next`,
pass `NEXT_DIST_DIR=.build/next-release` to `build:release` in CI or concurrent deploys:
`NEXT_DIST_DIR=.build/next-release npm run build:release`
then adjust the rsync source to `dist-release/` (or whatever `build:release` assembles into).
- **`dist/BUILD_SHA` must match the deployed HEAD** — always confirm before rsyncing.
A mismatch means the build ran from a dirty or incorrect commit.

View File

@@ -5,46 +5,115 @@ description: Deploy the latest OmniRoute code to BOTH the Akamai VPS and the Loc
# Deploy to VPS (Both) Workflow
Deploy OmniRoute to the production VPSs using `npm pack + scp` + PM2.
Deploy OmniRoute to both production VPSs using a clean `build:release` + `rsync` + PM2 restart.
**Akamai VPS:** `69.164.221.35`
**Local VPS:** `192.168.0.15`
**Process manager:** PM2 (`omniroute`)
**Port:** `20128`
**PM2 entry:** `/usr/lib/node_modules/omniroute/app/server.js`
**Remote install dir:** `/usr/lib/node_modules/omniroute/app/` (VPS image dir — unchanged)
> [!IMPORTANT]
> The npm registry rejects packages > 100MB, so deployment uses **npm pack + scp**.
> **Build must run where `node_modules` is REAL.** A git worktree with symlinked
> `node_modules` breaks the Next.js standalone assembly. Always build from the main
> checkout (or an `npm ci`'d worktree), never from `OmniRoute-*` worktrees unless
> you first ran `npm ci` inside them.
## Steps
### 1. Build + pack locally
### 1. Clean build + sentinel
Build **once** locally — the same `dist/` is shipped to both VPSs:
// turbo
```bash
cd /home/diegosouzapw/dev/proxys/OmniRoute && rm -f omniroute-*.tgz && rm -rf .next/cache app/.next/cache && npm run build:cli && rm -rf app/logs app/coverage app/.git app/.app-build-backup* && npm pack --ignore-scripts
cd /home/diegosouzapw/dev/proxys/OmniRoute && npm run build:release
```
### 2. Copy to both VPS and install
`build:release` does:
1. Deletes `.build/` and `dist/` (clean rebuild — no stale cache)
2. Runs `next build``.build/next/` (intermediates)
3. Assembles the shippable bundle into `dist/` via `assembleStandalone`
4. Writes `dist/BUILD_SHA` = `git rev-parse --short HEAD` (deploy sentinel)
Verify the sentinel before shipping:
```bash
cat /home/diegosouzapw/dev/proxys/OmniRoute/dist/BUILD_SHA && git -C /home/diegosouzapw/dev/proxys/OmniRoute rev-parse --short HEAD
```
Both lines must match. If they differ, the build is stale — re-run `build:release`.
### 2. Back up the running bundle on both VPSs
```bash
OMNIROUTE_VERSION=$(node -p "require('/home/diegosouzapw/dev/proxys/OmniRoute/package.json').version")
ssh root@69.164.221.35 "cp -a /usr/lib/node_modules/omniroute/app /usr/lib/node_modules/omniroute/app.bak-${OMNIROUTE_VERSION}" &
ssh root@192.168.0.15 "cp -a /usr/lib/node_modules/omniroute/app /usr/lib/node_modules/omniroute/app.bak-${OMNIROUTE_VERSION}" &
wait
```
### 3. Rsync `dist/` → remote `app/` on both VPSs
The VPS image directory is still named `app/`; we ship the **contents** of our local `dist/`:
// turbo-all
```bash
scp omniroute-*.tgz root@69.164.221.35:/tmp/ && scp omniroute-*.tgz root@192.168.0.15:/tmp/
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
```bash
ssh root@69.164.221.35 "pm2 restart omniroute --update-env && pm2 save"
```
```bash
ssh root@69.164.221.35 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts --legacy-peer-deps && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && (pm2 delete omniroute 2>/dev/null || true) && pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Akamai done'"
ssh root@192.168.0.15 "pm2 restart omniroute --update-env && pm2 save"
```
```bash
ssh root@192.168.0.15 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts --legacy-peer-deps && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && (pm2 delete omniroute 2>/dev/null || true) && pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Local done'"
sleep 5
curl -sf http://69.164.221.35:20128/api/monitoring/health | jq '{status:.status, version:.version}'
curl -sf http://192.168.0.15:20128/api/monitoring/health | jq '{status:.status, version:.version}'
```
### 3. Verify the deployment
Expected: HTTP 200, `"status": "ok"` on both.
### 5. Verify the deployment
```bash
curl -s -o /dev/null -w 'AKAMAI HTTP %{http_code}\n' http://69.164.221.35:20128/
curl -s -o /dev/null -w 'LOCAL HTTP %{http_code}\n' http://192.168.0.15:20128/
```
## Rollback
If either VPS fails its health check, restore from backup (adjust host as needed):
```bash
OMNIROUTE_VERSION=$(node -p "require('/home/diegosouzapw/dev/proxys/OmniRoute/package.json').version")
# Akamai rollback:
ssh root@69.164.221.35 "rm -rf /usr/lib/node_modules/omniroute/app && cp -a /usr/lib/node_modules/omniroute/app.bak-${OMNIROUTE_VERSION} /usr/lib/node_modules/omniroute/app && pm2 restart omniroute --update-env"
# Local rollback:
ssh root@192.168.0.15 "rm -rf /usr/lib/node_modules/omniroute/app && cp -a /usr/lib/node_modules/omniroute/app.bak-${OMNIROUTE_VERSION} /usr/lib/node_modules/omniroute/app && pm2 restart omniroute --update-env"
```
## Gotchas
- **Never build in a worktree with symlinked `node_modules`** — the standalone copy step
follows symlinks and produces a broken bundle. Use the main checkout or run `npm ci`
inside the worktree first.
- **`pm2 restart --update-env` does NOT re-read `ecosystem.config.cjs`** — if you changed
env vars in the ecosystem file, use `pm2 reload` or `pm2 delete + pm2 start` instead.
- **Parallel dev + release builds**: to avoid clobbering the dev server's `.build/next`,
pass `NEXT_DIST_DIR=.build/next-release` to `build:release` in CI or concurrent deploys:
`NEXT_DIST_DIR=.build/next-release npm run build:release`
then adjust the rsync source to `dist-release/`.
- **`dist/BUILD_SHA` must match the deployed HEAD** — always confirm before rsyncing.
A mismatch means the build ran from a dirty or incorrect commit.
- **Build once, deploy to both** — never run two separate builds for the two VPSs.
The same `dist/` artifact (same `BUILD_SHA`) must land on both hosts to keep
versions in sync.

View File

@@ -5,36 +5,99 @@ description: Deploy the latest OmniRoute code to the Local VPS (192.168.0.15)
# Deploy to Local VPS Workflow
Deploy OmniRoute to the Local VPS using `npm pack + scp` + PM2.
Deploy OmniRoute to the Local VPS using a clean `build:release` + `rsync` + PM2 restart.
**Local VPS:** `192.168.0.15`
**Process manager:** PM2 (`omniroute`)
**Port:** `20128`
**Remote install dir:** `/usr/lib/node_modules/omniroute/app/` (VPS image dir — unchanged)
> [!IMPORTANT]
> **Build must run where `node_modules` is REAL.** A git worktree with symlinked
> `node_modules` breaks the Next.js standalone assembly. Always build from the main
> checkout (or an `npm ci`'d worktree), never from `OmniRoute-*` worktrees unless
> you first ran `npm ci` inside them.
## Steps
### 1. Build + pack locally
### 1. Clean build + sentinel
Run from a checkout with a **real** `node_modules`:
// turbo
```bash
cd /home/diegosouzapw/dev/proxys/OmniRoute && rm -f omniroute-*.tgz && rm -rf .next/cache app/.next/cache && npm run build:cli && rm -rf app/logs app/coverage app/.git app/.app-build-backup* && npm pack --ignore-scripts
cd /home/diegosouzapw/dev/proxys/OmniRoute && npm run build:release
```
### 2. Copy to Local VPS and install
`build:release` does:
1. Deletes `.build/` and `dist/` (clean rebuild — no stale cache)
2. Runs `next build``.build/next/` (intermediates)
3. Assembles the shippable bundle into `dist/` via `assembleStandalone`
4. Writes `dist/BUILD_SHA` = `git rev-parse --short HEAD` (deploy sentinel)
Verify the sentinel before shipping:
```bash
cat /home/diegosouzapw/dev/proxys/OmniRoute/dist/BUILD_SHA && git -C /home/diegosouzapw/dev/proxys/OmniRoute rev-parse --short HEAD
```
Both lines must match. If they differ, the build is stale — re-run `build:release`.
### 2. Back up the running bundle on the VPS
```bash
OMNIROUTE_VERSION=$(node -p "require('/home/diegosouzapw/dev/proxys/OmniRoute/package.json').version")
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/`
The VPS image directory is still named `app/`; we ship the **contents** of our local `dist/`:
// turbo-all
```bash
scp omniroute-*.tgz root@192.168.0.15:/tmp/
rsync -az --delete /home/diegosouzapw/dev/proxys/OmniRoute/dist/ root@192.168.0.15:/usr/lib/node_modules/omniroute/app/
```
### 4. Restart + health check
```bash
ssh root@192.168.0.15 "pm2 restart omniroute --update-env && pm2 save"
```
```bash
ssh root@192.168.0.15 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts --legacy-peer-deps && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && (pm2 delete omniroute 2>/dev/null || true) && pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Local done'"
sleep 5 && curl -sf http://192.168.0.15:20128/api/monitoring/health | jq '{status:.status, version:.version}'
```
### 3. Verify the deployment
Expected: HTTP 200, `"status": "ok"` (or equivalent health payload).
### 5. Verify the deployment
```bash
curl -s -o /dev/null -w 'LOCAL HTTP %{http_code}\n' http://192.168.0.15:20128/
```
## Rollback
If the health check fails, restore the backup:
```bash
OMNIROUTE_VERSION=$(node -p "require('/home/diegosouzapw/dev/proxys/OmniRoute/package.json').version")
ssh root@192.168.0.15 "rm -rf /usr/lib/node_modules/omniroute/app && cp -a /usr/lib/node_modules/omniroute/app.bak-${OMNIROUTE_VERSION} /usr/lib/node_modules/omniroute/app && pm2 restart omniroute --update-env"
```
## Gotchas
- **Never build in a worktree with symlinked `node_modules`** — the standalone copy step
follows symlinks and produces a broken bundle. Use the main checkout or run `npm ci`
inside the worktree first.
- **`pm2 restart --update-env` does NOT re-read `ecosystem.config.cjs`** — if you changed
env vars in the ecosystem file, use `pm2 reload` or `pm2 delete + pm2 start` instead.
- **Parallel dev + release builds**: to avoid clobbering the dev server's `.build/next`,
pass `NEXT_DIST_DIR=.build/next-release` to `build:release` in CI or concurrent deploys:
`NEXT_DIST_DIR=.build/next-release npm run build:release`
then adjust the rsync source to `dist-release/` (or whatever `build:release` assembles into).
- **`dist/BUILD_SHA` must match the deployed HEAD** — always confirm before rsyncing.
A mismatch means the build ran from a dirty or incorrect commit.

View File

@@ -9,6 +9,7 @@
# Dependencies and build output
node_modules
.next
.build
out
build
dist

View File

@@ -159,7 +159,11 @@ jobs:
cache: npm
- run: npm ci
- run: npm run check:node-runtime
# build:cli runs a clean build into .build/next and assembles dist/
# For release builds prefer: npm run build:release (clean rebuild + HEAD sentinel)
- run: npm run build:cli
- name: Assert dist/server.js exists
run: test -f dist/server.js || (echo "dist/server.js missing — build:cli did not assemble correctly" && exit 1)
- run: npm run check:pack-artifact
electron-package-smoke:

View File

@@ -23,19 +23,32 @@ with **MCP Server** (37 tools), **A2A v0.3 Protocol**, and **Electron desktop ap
## Build, Lint, and Test Commands
| Command | Description |
| ----------------------------------- | --------------------------------- |
| `npm run dev` | Start Next.js dev server |
| `npm run build` | Production build (isolated) |
| `npm run start` | Run production build |
| `npm run build:cli` | Build CLI package |
| `npm run lint` | ESLint on all source files |
| `npm run typecheck:core` | TypeScript core type checking |
| `npm run typecheck:noimplicit:core` | Strict checking (no implicit any) |
| `npm run check` | Run lint + test |
| `npm run check:cycles` | Check for circular dependencies |
| `npm run electron:dev` | Run Electron app in dev mode |
| `npm run electron:build` | Build Electron app for current OS |
| Command | Description |
| ----------------------------------- | ---------------------------------------------------------------- |
| `npm run dev` | Start Next.js dev server |
| `npm run build` | Production build: `next build``.build/next/` + assemble `dist/` |
| `npm run build:release` | Clean rebuild + HEAD sentinel (`dist/BUILD_SHA`) — use for deploy |
| `npm run start` | Run production build |
| `npm run build:cli` | Build CLI package |
| `npm run lint` | ESLint on all source files |
| `npm run typecheck:core` | TypeScript core type checking |
| `npm run typecheck:noimplicit:core` | Strict checking (no implicit any) |
| `npm run check` | Run lint + test |
| `npm run check:cycles` | Check for circular dependencies |
| `npm run electron:dev` | Run Electron app in dev mode |
| `npm run electron:build` | Build Electron app for current OS |
**Build output layout:**
| Directory | Purpose | Gitignored |
| --------- | --------------------------------------------------- | ---------- |
| `src/` | Application source (TypeScript / TSX) | No |
| `.build/` | Build intermediates (`distDir = .build/next`) | Yes |
| `dist/` | Shippable bundle assembled by `assembleStandalone` | Yes |
The pipeline is a single `next build` pass — intermediates land in `.build/next/`, the
assembled bundle in `dist/`. VPS deploys rsync `dist/` into the remote
`/usr/lib/node_modules/omniroute/app/` directory (VPS image path is unchanged).
### Running Tests

View File

@@ -35,6 +35,10 @@ _Development cycle in progress — entries are added as work merges into `releas
- **resilience:** a per-model subscription/permission `403` from a passthrough provider (e.g. Ollama Cloud `deepseek-v4-pro`_"this model requires a subscription"_) now locks out **only that model** instead of cooling down the whole connection — the free models on the same key keep serving, and repeated paid-model 403s no longer escalate a connection-wide backoff. Generalizes the grok-web 403 precedent to all `hasPerModelQuota` providers; terminal/credential 403s (banned/deactivated key) still deactivate the connection. ([#3027](https://github.com/diegosouzapw/OmniRoute/issues/3027))
- **cache:** preserve client-side `cache_control` breakpoints for Xiaomi MiMo — added `xiaomi-mimo` to the prompt-caching provider allowlist so Claude Code (via cc-switch) cache hints are no longer stripped by the OpenAI-format translator, restoring cache hits. ([#3088](https://github.com/diegosouzapw/OmniRoute/issues/3088))
### 🔧 Build
- **build-output-isolation:** unified standalone assembly into one shared `assembleStandalone` module; isolated build output into `.build/` (intermediates, gitignored) and `dist/` (shippable bundle, gitignored), replacing the old repo-root `app/` and `.next/` directories; dropped the duplicate `next build` that prepublish previously ran; added `build:release` script for a clean rebuild with a `dist/BUILD_SHA` HEAD sentinel that guards against deploying stale bundles. **Operators using custom `app/` paths:** the published bundle directory on the VPS image (`/usr/lib/node_modules/omniroute/app/`) is unchanged — only the in-repo build output path moved. Update any local scripts that reference the repo-local `app/` build output to `dist/` instead.
### 📦 Dependencies
- **electron:** bump to 42.3.2 (crash fix desktopCapturer, Chromium 148.0.7778.218, ThinLTO perf) (#3083)

View File

@@ -59,13 +59,40 @@ These settings are stored in the database and persist across restarts, overridin
npm run dev
# Production build
npm run build
npm run build # next build → .build/next/ then assembleStandalone → dist/
npm run start
# Release build (clean rebuild + HEAD sentinel — required for deploy)
npm run build:release # rm -rf .build dist && build + writes dist/BUILD_SHA
# Common port configuration
PORT=20128 NEXT_PUBLIC_BASE_URL=http://localhost:20128 npm run dev
```
### Build Output Layout
| Directory | Contents | Tracked |
| ---------- | ------------------------------------------- | ------- |
| `src/` | Application source (TypeScript / TSX) | Yes |
| `.build/` | Intermediates — `next build` output (gitignored, `distDir = .build/next`) | No |
| `dist/` | Shippable bundle — assembled by `assembleStandalone` (gitignored) | No |
The build pipeline is a single pass:
```
npm run build
└─ next build → .build/next/standalone (Next.js output)
└─ assembleStandalone() (copies standalone + static + public + native assets)
└─ output: dist/ (server.js, .next/static/, public/, node_modules/)
```
`npm run build:release` additionally cleans both directories first and writes
`dist/BUILD_SHA` (= `git rev-parse --short HEAD`) as a deploy integrity sentinel.
> **VPS deploy note:** the remote image directory `/usr/lib/node_modules/omniroute/app/`
> is unchanged. The deploy skills rsync the contents of `dist/` into it.
> Only the in-repo build output path moved (`app/` → `dist/`).
Default URLs:
- **Dashboard**: `http://localhost:20128/dashboard`
@@ -314,6 +341,10 @@ Write unit tests in `tests/unit/` covering at minimum:
Releases are managed via the `/generate-release` workflow. When a new GitHub Release is created, the package is **automatically published to npm** via GitHub Actions.
For VPS deploys, use `npm run build:release` (not `npm run build`) — it performs a clean
rebuild, assembles the bundle into `dist/`, and writes the `dist/BUILD_SHA` sentinel.
Then use the `/deploy-vps-*-cc` skills which rsync `dist/` to the remote `app/` directory.
---
## Getting Help

View File

@@ -42,7 +42,7 @@ RUN --mount=type=cache,target=/root/.npm \
ENV OMNIROUTE_USE_TURBOPACK=1
COPY . ./
RUN --mount=type=cache,target=/app/.next/cache \
RUN --mount=type=cache,target=/app/.build/next/cache \
mkdir -p /app/data && npm run build
# ── Runner base ────────────────────────────────────────────────────────────
@@ -65,9 +65,9 @@ ENV DATA_DIR=/app/data
RUN mkdir -p /app/data
# The standalone build + syncStandaloneExtraModules bundles all runtime files
# (.next, node_modules, migrations, scripts, docs, etc.) into .next/standalone/.
# (.next, node_modules, migrations, scripts, docs, etc.) into .build/next/standalone/.
# Explicit overrides below cover modules that NFT tracing may miss.
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.build/next/standalone ./
# Explicitly copy @swc/helpers — not always traced by standalone output but needed at runtime
COPY --from=builder /app/node_modules/@swc/helpers ./node_modules/@swc/helpers
# Explicitly copy better-sqlite3 — native bindings are not reliably traced by

View File

@@ -147,11 +147,38 @@ If `electron/` changed:
- [ ] `electron/package.json` version matches root `package.json`
- [ ] Auto-update channel pointer updated if releasing to `stable`
### Build Layout
The repository uses three distinct output directories — never mix them up:
| Directory | Purpose | Tracked? |
| ------------- | ------------------------------------------------------------- | -------- |
| `src/` | Application source (TypeScript / TSX) | Yes |
| `.build/` | Build intermediates — `next build` output (`distDir`) | No (gitignored) |
| `dist/` | Shippable npm bundle — assembled by `assembleStandalone` | No (gitignored) |
> **Operator note:** the remote VPS image directory remains `/usr/lib/node_modules/omniroute/app/`.
> Only the **in-repo** build output moved (`app/` → `dist/`). The deploy skills rsync
> `dist/` contents into the remote `app/` dir — no VPS path changes required.
**Single-build flow:**
```
npm run build:release
└─ rm -rf .build dist (clean)
└─ next build → .build/next/ (intermediates)
└─ assembleStandalone (copies standalone + static + public + natives → dist/)
└─ writes dist/BUILD_SHA (HEAD sentinel)
```
Do NOT run `npm run build` followed by a separate `npm run build:cli` for deploy — use
`npm run build:release` which does a clean rebuild + sentinel in one command.
### Artifact Validation
- [ ] `npm run build:cli` succeeds
- [ ] `npm run build:release` succeeds and `dist/BUILD_SHA` == `git rev-parse --short HEAD`
- [ ] `npm run check:pack-artifact` clean — no `app.__qa_backup`, `scripts/scratch`, `package-lock.json`, or other local residue
- [ ] `npm run build` produces a working standalone Next.js bundle
- [ ] `dist/server.js` exists after build
### Tagging & Release
@@ -169,10 +196,14 @@ If `electron/` changed:
### Deploy
Deploy skills use the light rsync flow — no `npm pack`, no `npm i -g`:
- [ ] Use deploy skill that matches target:
- `/deploy-vps-local-cc` — local VPS (192.168.0.15)
- `/deploy-vps-akamai-cc` — Akamai VPS (69.164.221.35)
- `/deploy-vps-both-cc` — both
- [ ] Before deploying, confirm `dist/BUILD_SHA` == `git rev-parse --short HEAD`
- [ ] Build must run where `node_modules` is real (main checkout or `npm ci`'d worktree — NOT a symlinked worktree)
- [ ] Smoke test deployed instance:
- Open `/dashboard/health` → check version string matches release
- Run a `/v1/chat/completions` request against a known provider

View File

@@ -57,7 +57,7 @@
],
"extraResources": [
{
"from": "../.next/electron-standalone",
"from": "../.build/electron-standalone",
"to": "app",
"filter": [
"**/*",
@@ -65,7 +65,7 @@
]
},
{
"from": "../.next/electron-standalone/node_modules",
"from": "../.build/electron-standalone/node_modules",
"to": "app/node_modules",
"filter": [
"**/*"

View File

@@ -38,11 +38,13 @@ const eslintConfig = [
// Global ignores — keep ESLint scoped to source files only
{
ignores: [
// Next.js build output
// Next.js build output (distDir now .build/next; keep .next for legacy)
".next/**",
".build/**",
"src/.next/**",
"out/**",
"build/**",
"dist/**",
"coverage/**",
"next-env.d.ts",
// Scripts and binaries
@@ -65,12 +67,9 @@ const eslintConfig = [
// Playwright test output
"playwright-report/**",
"test-results/**",
// Subdirectory .next build output (app/ subdir)
// Legacy app/ and QA backup dirs (renamed to dist/ in Layer 1)
"app/**",
"app/.next/**",
"app/bin/**",
"app.__qa_backup/**",
"app/app.__qa_backup/**",
// CLI package copy directory
"clipr/**",
],

View File

@@ -9,10 +9,10 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const ROOT = join(__dirname, "..", "..");
const NEXT_DIST_DIR = process.env.NEXT_DIST_DIR || ".next";
const NEXT_DIST_DIR = process.env.NEXT_DIST_DIR || ".build/next";
const DIST_DIR = join(ROOT, NEXT_DIST_DIR);
const STANDALONE_DIR = join(DIST_DIR, "standalone");
const ELECTRON_STANDALONE_DIR = join(ROOT, NEXT_DIST_DIR, "electron-standalone");
const ELECTRON_STANDALONE_DIR = join(ROOT, ".build", "electron-standalone");
// --- Electron-UNIQUE: resolve the nested server.js location ----------------