fix(web): remove config-load warning filter, fix root cause (#1491) (#1494)

The `UNRESOLVED_IMPORT` warnings that `quiet-config-warnings.mjs` suppressed
no longer occur, so the filter (and the build-time `onwarn` suppressions for
the same three deps) are removed.

Root cause: Vite's config bundler (Rolldown) statically walks the node-only
dev backend (`core/mcp/remote/node/server.ts`) when it loads `vite.config.ts`,
reaching `chokidar`, `atomically`, and `@napi-rs/keyring`. `core/` has no
node_modules of its own, so at config-bundle time those deps resolve from
`core/`'s parent chain — the repo-root `node_modules`. Before #1452 the deps
lived only under `clients/web/node_modules` (a sibling, off the resolution
chain), so Rolldown emitted `UNRESOLVED_IMPORT`. #1452 declared them in the
repo-root `package.json` (for the CLI/TUI/launcher, which consume core/'s node
code), installing them at the repo root and making them resolvable. Verified
by hiding the root copy: the warning returns; restore it and the dev/build
startup is clean.

A dynamic import of the backend does not help — Rolldown follows dynamic
imports during config bundling too. The fix is the deps being resolvable, so
the suppression is just removed and the invariant is documented in
`vite.config.ts`.

- Remove `clients/web/server/quiet-config-warnings.mjs` + `.d.mts` and its test
- Restore the `dev` script to plain `vite` (drop the `--import` hook)
- Remove the now-dead `build.rollupOptions.onwarn` UNRESOLVED suppressions
  (never fire: the Hono plugin is `apply: 'serve'`, out of the build graph)
- Document why config-bundle resolution succeeds in `vite.config.ts`
- Update AGENTS.md project tree

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cliff Hall 2026-06-15 11:37:06 -04:00 • committed by GitHub
commit a1f40a4222
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 14 additions and 178 deletions

View file

@ -1,8 +0,0 @@
/** Type declarations for the dev-only `quiet-config-warnings.mjs` hook so its
* pure helpers can be imported (and unit-tested) from TypeScript. */
/** (package-quote, repo-relative-source-path) pairs of benign warnings to drop. */
export declare const BENIGN_PAIRS: ReadonlyArray<readonly [string, string]>;
/** True when `text` is a benign node-only UNRESOLVED_IMPORT warning. */
export declare function isBenignWarning(text: string): boolean;

View file

@ -1,66 +0,0 @@
/**
* Drops the benign "UNRESOLVED_IMPORT" warnings Vite's config bundler (Rolldown)
* prints at `vite dev` startup for node-only backend deps that the Hono plugin
* pulls in (`chokidar`, `atomically`, `@napi-rs/keyring`).
*
* Why a stream filter and not a Vite hook: the dev backend
* (`core/mcp/remote/node/server.ts`) is statically imported by the Hono plugin,
* so loading vite.config.ts bundles it and Rolldown walks its node-only deps.
* Those deps are correctly externalized (the browser never loads them; Node
* resolves them at runtime), but Rolldown still warns. The warnings are printed
* directly to the process streams during config loading — before any Vite
* logger exists — so `customLogger`, `logLevel`, and `rollupOptions.onwarn` all
* miss them. Each warning (header + code frame) is emitted as a single write,
* so we drop any write whose text carries the unresolved-import signature for
* one of these known-benign (package, source-file) pairs. Installed via
* `node --import` ahead of the Vite CLI; only affects the dev script.
*
* This matcher is tied to Rolldown's current warning format (the `'pkg'` quote
* style and the repo-relative source path it prints). If these warnings ever
* reappear, check whether Rolldown changed its message format. The failure mode
* is safe: a format change just makes the filter a no-op (the warnings return),
* it never drops anything else.
*/
// (package, source-file) pairs. The file is a repo-relative path fragment — not
// just a basename — so an unrelated future warning that happens to mention the
// same dep and a same-named file isn't silently dropped.
export const BENIGN_PAIRS = [
["'chokidar'", "core/mcp/remote/node/server.ts"],
["'atomically'", "core/storage/store-io.ts"],
["'@napi-rs/keyring'", "core/auth/node/secret-store.ts"],
];
export function isBenignWarning(text) {
if (!text.includes("UNRESOLVED_IMPORT")) return false;
return BENIGN_PAIRS.some(
([dep, file]) => text.includes(dep) && text.includes(file),
);
}
function patch(stream) {
const original = stream.write.bind(stream);
stream.write = (chunk, encoding, callback) => {
const text =
typeof chunk === "string"
? chunk
: Buffer.isBuffer(chunk)
? chunk.toString("utf8")
: "";
if (text && isBenignWarning(text)) {
// Pretend the write succeeded so callers/promises don't stall.
if (typeof encoding === "function") encoding();
else if (typeof callback === "function") callback();
return true;
}
return original(chunk, encoding, callback);
};
}
// Guard so importing this module (e.g. from a unit test for isBenignWarning)
// doesn't monkey-patch the test runner's streams. Under `npm run dev` VITEST is
// unset, so the patch installs as intended.
if (!process.env.VITEST) {
patch(process.stdout);
patch(process.stderr);
}