diff --git a/bin/cli/commands/update.mjs b/bin/cli/commands/update.mjs index e3c95cfd81..f5c696e09d 100644 --- a/bin/cli/commands/update.mjs +++ b/bin/cli/commands/update.mjs @@ -142,7 +142,7 @@ export async function runUpdateCommand(opts = {}) { } if (dryRun) { - console.log("\n [DRY RUN] Would run: npm install -g omniroute@latest"); + console.log("\n [DRY RUN] Would run: npm install -g omniroute@latest --include=optional"); if (!skipBackup) console.log(" [DRY RUN] Would create backup in ~/.omniroute/backups/"); return 0; } @@ -174,7 +174,9 @@ export async function runUpdateCommand(opts = {}) { printInfo("Updating OmniRoute..."); try { const { execSync } = await import("child_process"); - execSync("npm install -g omniroute@latest", { stdio: "inherit" }); + // --include=optional keeps the optionalDependencies (better-sqlite3, keytar, + // tls-client, llmlingua SLM stack) on update so an omit=optional config can't drop them. + execSync("npm install -g omniroute@latest --include=optional", { stdio: "inherit" }); printSuccess(`Updated to version ${latest}`); printInfo("Run `omniroute --version` to verify."); return 0; diff --git a/docs/ops/RELEASE_CHECKLIST.md b/docs/ops/RELEASE_CHECKLIST.md index e2a2dc3bef..80990f8317 100644 --- a/docs/ops/RELEASE_CHECKLIST.md +++ b/docs/ops/RELEASE_CHECKLIST.md @@ -266,6 +266,13 @@ Before shipping any v3.8.x release, verify these additional items: - [ ] `omniroute --tray` boots on Windows (PowerShell NotifyIcon, no extra binaries) - [ ] `omniroute config tray enable` creates autostart entry; disable removes it - [ ] `npm install -g omniroute@` runs postinstall without fatal exit +- [ ] Update path keeps optional deps: `omniroute update --apply` and the auto-updater + run `npm install -g … --include=optional` so `optionalDependencies` (better-sqlite3, + keytar, tls-client, and the llmlingua SLM stack: `@atjsh/llmlingua-2`, + `@tensorflow/tfjs`, `js-tiktoken`) survive an update. The ultra `modelPath` SLM tier + additionally needs `@huggingface/transformers@3.5.2` (pinned — llmlingua-2 uses the 3.x + tokenizer API) and the tinybert model, auto-downloaded to `${DATA_DIR}/models/llmlingua` + on first use. - [ ] `omniroute status` works with no `.env` (CLI token path, loopback only) - [ ] `curl http://localhost:20128/api/shutdown` returns 401 (always-protected route) - [ ] `curl -H "host: evil.com" http://localhost:20128/api/mcp/sse` returns 401 (loopback guard) diff --git a/src/lib/system/autoUpdate.ts b/src/lib/system/autoUpdate.ts index 5f4ef49a35..d81ba0a959 100644 --- a/src/lib/system/autoUpdate.ts +++ b/src/lib/system/autoUpdate.ts @@ -232,7 +232,10 @@ export async function ensureGitTagExists( export function buildNpmUpdateScript(latest: string): string { return [ "set -eu", - `npm install -g omniroute@${latest} --ignore-scripts --legacy-peer-deps`, + // --include=optional keeps the optionalDependencies (better-sqlite3, keytar, + // tls-client, and the llmlingua SLM stack) installed on every update so an + // `omit=optional` config / .npmrc cannot silently drop them. + `npm install -g omniroute@${latest} --include=optional --ignore-scripts --legacy-peer-deps`, "if command -v pm2 >/dev/null 2>&1; then", " pm2 restart omniroute || true", "fi", @@ -254,7 +257,7 @@ export function buildSourceUpdateScript(latest: string, gitRemote = "origin"): s 'backup_branch="pre-update/$(git rev-parse --short HEAD)-$(date +%Y%m%d-%H%M%S)"', 'git branch "$backup_branch" 2>/dev/null || true', `git checkout "${targetTag}"`, - "npm install --legacy-peer-deps", + "npm install --include=optional --legacy-peer-deps", "node scripts/dev/sync-env.mjs 2>/dev/null || true", "npm run build", "if command -v pm2 >/dev/null 2>&1; then", diff --git a/tests/unit/auto-update.test.ts b/tests/unit/auto-update.test.ts index e302b8ab87..a6714cb765 100644 --- a/tests/unit/auto-update.test.ts +++ b/tests/unit/auto-update.test.ts @@ -248,12 +248,17 @@ test("ensureGitTagExists verifies refs/tags paths and throws a clear error when test("auto update script builders generate npm, source, and docker-compose scripts with quoting and patch commits", () => { const npmScript = autoUpdate.buildNpmUpdateScript("3.6.0"); assert.match(npmScript, /npm install -g omniroute@3.6.0/); + // Optional deps (better-sqlite3, keytar, tls-client, and the llmlingua SLM stack) + // must survive an update — install them explicitly so an `omit=optional` config + // cannot silently drop them. + assert.match(npmScript, /--include=optional/); assert.match(npmScript, /pm2 restart omniroute \|\| true/); assert.match(npmScript, /Successfully updated to v3.6.0/); const sourceScript = autoUpdate.buildSourceUpdateScript("3.6.0", "upstream"); assert.match(sourceScript, /git fetch --tags 'upstream'/); assert.match(sourceScript, /git stash --include-untracked/); + assert.match(sourceScript, /npm install --include=optional --legacy-peer-deps/); assert.match(sourceScript, /node scripts\/dev\/sync-env\.mjs 2>\/dev\/null \|\| true/); assert.match(sourceScript, /Successfully updated to v3\.6\.0/);