fix(build): Turbopack/Docker build — externalize sqlite-vec .so + sync mitm manager stub

The Docker image build (`docker compose --profile cli build`) runs `next build`
with OMNIROUTE_USE_TURBOPACK=1 and failed with two Turbopack errors that the
webpack-based VM build never hits — which is why the VM deploy validated but the
Docker build errored (#3066). The reporter's log was truncated before the real
errors; reproducing `OMNIROUTE_USE_TURBOPACK=1 npm run build` locally surfaced them:

1. node_modules/sqlite-vec-linux-x64/vec0.so — "Unknown module type". sqlite-vec
   ships a native vec0.so loaded at runtime via createRequire(); Turbopack tried to
   bundle the .so. Fixed by adding "sqlite-vec" to serverExternalPackages, exactly
   like better-sqlite3.

2. /api/tools/agent-bridge/state statically imports getAllAgentsStatus from
   @/mitm/manager, which next.config aliases to manager.stub.ts for the Turbopack
   build. The stub did not export getAllAgentsStatus → "Export getAllAgentsStatus
   doesn't exist in target module". Added the export (throws like the other heavy
   ops — MITM/agent-bridge is non-functional in the bundled build anyway).

Tests (tests/unit/next-config.test.ts):
- assert sqlite-vec is in serverExternalPackages.
- new guard: manager.stub.ts must export every name statically imported from
  @/mitm/manager across src/app (catches stub/manager drift — would have caught this).

Verified: OMNIROUTE_USE_TURBOPACK=1 npm run build → EXIT 0 (was: Build error
occurred); webpack build → EXIT 0; typecheck:core / check:cycles / lint clean.

Fixes #3066
This commit is contained in:
diegosouzapw
2026-06-02 12:18:56 -03:00
parent e438139b03
commit 146244b8f5
4 changed files with 82 additions and 0 deletions

View File

@@ -74,6 +74,13 @@
### Fixed
- **build:** Docker image build (`docker compose --profile cli build`, which runs
`next build` with Turbopack) no longer errors. Two Turbopack-only failures were
fixed: `sqlite-vec` is now externalized so Turbopack stops trying to bundle its
native `vec0.so` ("Unknown module type"), and `manager.stub.ts` now exports
`getAllAgentsStatus` (statically imported by `/api/tools/agent-bridge/state` — the
missing export aborted the build). The webpack-based VM build was unaffected, which
is why the deploy validated while the Docker build errored. (#3066 — thanks @freefrank)
- **codex/providers:** `POST /api/providers/[id]/refresh` (the manual/auto "refresh
token" endpoint) no longer rotates rotating-refresh providers (Codex/OpenAI share
one Auth0 `client_id`). This was the last unguarded proactive-refresh entry point:

View File

@@ -141,6 +141,11 @@ const nextConfig = {
"thread-stream",
"pino-abstract-transport",
"better-sqlite3",
// sqlite-vec ships a native vec0.so loaded at runtime via createRequire().
// Turbopack otherwise tries to bundle the .so and fails with "Unknown module
// type"; externalizing it keeps the require at runtime (like better-sqlite3).
// See issue #3066.
"sqlite-vec",
"node-machine-id",
"keytar",
"wreq-js",

View File

@@ -13,6 +13,13 @@ export const clearCachedPassword = () => {};
export const getMitmStatus = async () => {
throw new Error(STUB_ERROR);
};
// Statically imported by /api/tools/agent-bridge/state — the stub must export it or
// the Turbopack build fails ("Export getAllAgentsStatus doesn't exist"). MITM/agent
// bridge needs host-level access and is non-functional in the bundled build anyway,
// so this throws like the other heavy ops. See issue #3066.
export const getAllAgentsStatus = (): never => {
throw new Error(STUB_ERROR);
};
export const startMitm = async (
_apiKey: string,
_sudoPassword: string,

View File

@@ -83,6 +83,9 @@ test("next config declares Turbopack aliases, runtime assets and server external
for (const packageName of [
"thread-stream",
"better-sqlite3",
// sqlite-vec ships a native vec0.so loaded at runtime; without externalizing it
// the Turbopack build fails with "Unknown module type" on the .so (issue #3066).
"sqlite-vec",
"wreq-js",
"fs",
"path",
@@ -95,6 +98,66 @@ test("next config declares Turbopack aliases, runtime assets and server external
}
});
// ── manager.stub.ts must cover every static @/mitm/manager import (issue #3066) ──
//
// next.config aliases `@/mitm/manager` → `manager.stub.ts` for the Turbopack build
// (Docker uses Turbopack; the VM/webpack build uses the real module, which is why the
// VM validated while Docker's `npm run build` errored). Any route that statically
// imports a name the stub doesn't export breaks the Turbopack build with
// "Export X doesn't exist in target module". This guard fails on that drift — it is
// what would have caught the missing getAllAgentsStatus export in #3066.
test("manager.stub.ts exports every name statically imported from @/mitm/manager", async () => {
const fs = await import("node:fs");
const appDir = path.join(process.cwd(), "src", "app");
// Collect named imports from `... from "@/mitm/manager"` (NOT manager.runtime, which
// is loaded via dynamic import() and resolves to the real module at runtime).
const collectImports = (dir: string, acc: Set<string>): void => {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
collectImports(full, acc);
continue;
}
if (!/\.(ts|tsx)$/.test(entry.name)) continue;
const src = fs.readFileSync(full, "utf-8");
const re = /import\s*\{([^}]*)\}\s*from\s*["']@\/mitm\/manager["']/g;
let m: RegExpExecArray | null;
while ((m = re.exec(src)) !== null) {
for (const raw of m[1].split(",")) {
const name = raw.trim().split(/\s+as\s+/)[0].trim();
if (name) acc.add(name);
}
}
}
};
const imported = new Set<string>();
collectImports(appDir, imported);
// Sanity: the suite is meaningless if it finds nothing to check.
assert.ok(imported.size > 0, "expected at least one static @/mitm/manager import in src/app");
assert.ok(imported.has("getAllAgentsStatus"), "fixture: agent-bridge/state imports getAllAgentsStatus");
const stubSrc = fs.readFileSync(
path.join(process.cwd(), "src", "mitm", "manager.stub.ts"),
"utf-8"
);
const stubExports = new Set(
[...stubSrc.matchAll(/export\s+(?:const|function|async\s+function)\s+([A-Za-z0-9_]+)/g)].map(
(m) => m[1]
)
);
const missing = [...imported].filter((name) => !stubExports.has(name));
assert.deepEqual(
missing,
[],
`manager.stub.ts is missing exports statically imported by routes: ${missing.join(", ")}`
);
});
test("next-intl webpack hook preserves caller config and filters known extractor warnings", async () => {
const { default: nextConfig } = await loadNextConfig("webpack-pass-through");
const config: any = {