diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 8c4eb78b5d..24685db023 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -2692,6 +2692,26 @@ jobs: - name: Cross-package test inputs run: pnpm check:cross-package-test-inputs + # Console-intercept disarm (#10374). vitest's worker forwards every + # console.* write to the main thread over RPC and DISCARDS the promise; + # a write that lands after teardown's `rpcDone()` snapshot is rejected + # into an unhandled error, failing a fully green suite (signature: + # `EnvironmentTeardownError: [vitest-worker]: Closing rpc while + # "onUserConsoleLog" was pending` — the file it names is where the + # worker WAS, not where the bug is). The window is load-width, so it + # presents as a merge-queue flake nobody can reproduce. The one general + # defence is `disableConsoleIntercept: true` in every package-root + # vitest config — it removes the mechanism (no RPC, nothing to reject) + # — and the population it must cover MOVES: five new vitest configs + # arrived in the nine days before this gate, every one with the + # intercept armed. This asserts every vitest-running package carries + # the disarm; the mechanism docblock lives in + # examples/app-showcase/vitest.config.ts and the teardown-race pin in + # that app proves the defect is still live in the installed vitest. + # Self-test first, then the real scan; reads ~72 manifests; sub-second. + - name: Console-intercept disarm + run: node scripts/check-console-intercept-disarm.mjs --self-test && node scripts/check-console-intercept-disarm.mjs + # Live-server database isolation (#10382). CI provisions ONE Postgres and # ONE MySQL for the whole temporal-conformance job and points every live # leg at them, and every live suite in the repo issues a `drop` when it diff --git a/examples/app-crm/vitest.config.ts b/examples/app-crm/vitest.config.ts index cf7e440d44..61ecb53409 100644 --- a/examples/app-crm/vitest.config.ts +++ b/examples/app-crm/vitest.config.ts @@ -4,6 +4,15 @@ import { defineConfig } from 'vitest/config'; import path from 'node:path'; export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, resolve: { // `action-predicate-sparse-face.test.ts` (#8990) drives this app's row // action predicate through the CEL engine itself, because the whole point diff --git a/examples/app-todo/vitest.config.ts b/examples/app-todo/vitest.config.ts new file mode 100644 index 0000000000..5e0591efc4 --- /dev/null +++ b/examples/app-todo/vitest.config.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// This config exists for exactly one setting; everything else stays on +// vitest's defaults, deliberately — a key added here re-specifies behaviour +// for every test file in the package (packages/cli/vitest.config.ts's header +// records the incident that taught that). +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, +}); diff --git a/examples/embed-objectql/vitest.config.ts b/examples/embed-objectql/vitest.config.ts new file mode 100644 index 0000000000..5e0591efc4 --- /dev/null +++ b/examples/embed-objectql/vitest.config.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// This config exists for exactly one setting; everything else stays on +// vitest's defaults, deliberately — a key added here re-specifies behaviour +// for every test file in the package (packages/cli/vitest.config.ts's header +// records the incident that taught that). +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, +}); diff --git a/packages/adapters/hono/vitest.config.ts b/packages/adapters/hono/vitest.config.ts index d0bd8f175c..7f5fb15c1d 100644 --- a/packages/adapters/hono/vitest.config.ts +++ b/packages/adapters/hono/vitest.config.ts @@ -5,6 +5,13 @@ import path from 'node:path'; export default defineConfig({ test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, globals: true, environment: 'node', }, diff --git a/packages/cli/vitest.config.ts b/packages/cli/vitest.config.ts index 864ca9b50b..f5afc5bd10 100644 --- a/packages/cli/vitest.config.ts +++ b/packages/cli/vitest.config.ts @@ -3,10 +3,11 @@ // This package had NO vitest config until #9832, and that is the fact this // header exists to keep visible: adding one changes how every test file in // `packages/cli` is configured, not just the file that needed it. So the -// config is deliberately minimal — anchored `resolve.alias` entries and no -// `test` block at all, because the test files here run on vitest's defaults -// (`globals: false`, `environment: 'node'`) and a `test` block would silently -// re-specify them. Sibling configs in this repo do carry +// config is deliberately minimal — anchored `resolve.alias` entries and a +// `test` block that only carries keys with a recorded warrant (`server.deps`, +// and the #10374 console-intercept disarm), because the test files here run on +// vitest's defaults (`globals: false`, `environment: 'node'`) and re-specifying +// THOSE keys would silently flip them. Sibling configs in this repo do carry // `test: { globals: true, … }`; copying that shape here would have flipped // `globals` for every existing file in the package. // @@ -378,6 +379,13 @@ export default defineConfig({ ], }, test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, server: { deps: { external: [/packages[\/]types[\/]dist/], diff --git a/packages/client-react/vitest.config.ts b/packages/client-react/vitest.config.ts index 4af82d1a18..9fce59150f 100644 --- a/packages/client-react/vitest.config.ts +++ b/packages/client-react/vitest.config.ts @@ -2,6 +2,13 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, // The only DOM environment in the workspace, and deliberately so (#4682): // these hooks are exercised through React's real renderer, so `document` // and friends must exist. Every other package here runs `environment: diff --git a/packages/client/vitest.config.ts b/packages/client/vitest.config.ts index 87a1e01008..23d56d3849 100644 --- a/packages/client/vitest.config.ts +++ b/packages/client/vitest.config.ts @@ -3,6 +3,13 @@ import path from 'path'; export default defineConfig({ test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, // Exclude integration tests that require a running server exclude: [ '**/node_modules/**', diff --git a/packages/cloud-connection/vitest.config.ts b/packages/cloud-connection/vitest.config.ts index 72243c2945..8ba4ce9249 100644 --- a/packages/cloud-connection/vitest.config.ts +++ b/packages/cloud-connection/vitest.config.ts @@ -4,6 +4,15 @@ import { defineConfig } from 'vitest/config'; import path from 'node:path'; export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, resolve: { // One entry, for `canonical-expression-envelopes.test.ts` (#11480) — the // only suite here that imports `@objectstack/lint` as a VALUE. It runs the diff --git a/packages/connectors/connector-mcp/vitest.config.ts b/packages/connectors/connector-mcp/vitest.config.ts new file mode 100644 index 0000000000..5e0591efc4 --- /dev/null +++ b/packages/connectors/connector-mcp/vitest.config.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// This config exists for exactly one setting; everything else stays on +// vitest's defaults, deliberately — a key added here re-specifies behaviour +// for every test file in the package (packages/cli/vitest.config.ts's header +// records the incident that taught that). +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, +}); diff --git a/packages/connectors/connector-openapi/vitest.config.ts b/packages/connectors/connector-openapi/vitest.config.ts index cd2bb2114f..4d2ddd1ded 100644 --- a/packages/connectors/connector-openapi/vitest.config.ts +++ b/packages/connectors/connector-openapi/vitest.config.ts @@ -40,6 +40,15 @@ import { defineConfig } from 'vitest/config'; import path from 'path'; export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, resolve: { // Array form with ANCHORED patterns, per the trap the gate documents: the // object form matches by PREFIX, so a bare key whose replacement is a FILE diff --git a/packages/connectors/connector-rest/vitest.config.ts b/packages/connectors/connector-rest/vitest.config.ts new file mode 100644 index 0000000000..5e0591efc4 --- /dev/null +++ b/packages/connectors/connector-rest/vitest.config.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// This config exists for exactly one setting; everything else stays on +// vitest's defaults, deliberately — a key added here re-specifies behaviour +// for every test file in the package (packages/cli/vitest.config.ts's header +// records the incident that taught that). +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, +}); diff --git a/packages/connectors/connector-slack/vitest.config.ts b/packages/connectors/connector-slack/vitest.config.ts new file mode 100644 index 0000000000..5e0591efc4 --- /dev/null +++ b/packages/connectors/connector-slack/vitest.config.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// This config exists for exactly one setting; everything else stays on +// vitest's defaults, deliberately — a key added here re-specifies behaviour +// for every test file in the package (packages/cli/vitest.config.ts's header +// records the incident that taught that). +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, +}); diff --git a/packages/core/vitest.config.ts b/packages/core/vitest.config.ts index 4a1f07e28a..6311316bf7 100644 --- a/packages/core/vitest.config.ts +++ b/packages/core/vitest.config.ts @@ -5,6 +5,13 @@ import path from 'path'; export default defineConfig({ test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, globals: true, environment: 'node', }, diff --git a/packages/create-objectstack/vitest.config.ts b/packages/create-objectstack/vitest.config.ts index bd4f0bf346..68e5d5330b 100644 --- a/packages/create-objectstack/vitest.config.ts +++ b/packages/create-objectstack/vitest.config.ts @@ -2,6 +2,13 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, include: ['test/**/*.test.ts', 'src/**/*.test.ts'], environment: 'node', }, diff --git a/packages/drivers/driver-memory/vitest.config.ts b/packages/drivers/driver-memory/vitest.config.ts index 1cfb01f5f8..10a2bf9d7e 100644 --- a/packages/drivers/driver-memory/vitest.config.ts +++ b/packages/drivers/driver-memory/vitest.config.ts @@ -5,6 +5,13 @@ import path from 'path'; export default defineConfig({ test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, globals: true, environment: 'node', }, diff --git a/packages/drivers/driver-mongodb/vitest.config.ts b/packages/drivers/driver-mongodb/vitest.config.ts index ef043d4f66..55b097d75e 100644 --- a/packages/drivers/driver-mongodb/vitest.config.ts +++ b/packages/drivers/driver-mongodb/vitest.config.ts @@ -2,6 +2,13 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, globals: true, testTimeout: 30_000, hookTimeout: 30_000, diff --git a/packages/drivers/driver-sql/vitest.config.ts b/packages/drivers/driver-sql/vitest.config.ts index 3d5689d336..778459f12a 100644 --- a/packages/drivers/driver-sql/vitest.config.ts +++ b/packages/drivers/driver-sql/vitest.config.ts @@ -5,6 +5,13 @@ import path from 'path'; export default defineConfig({ test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, globals: true, environment: 'node', // #9350: the live-dialect files each own a schema (Postgres) / database diff --git a/packages/drivers/driver-sqlite-wasm/vitest.config.ts b/packages/drivers/driver-sqlite-wasm/vitest.config.ts new file mode 100644 index 0000000000..5e0591efc4 --- /dev/null +++ b/packages/drivers/driver-sqlite-wasm/vitest.config.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// This config exists for exactly one setting; everything else stays on +// vitest's defaults, deliberately — a key added here re-specifies behaviour +// for every test file in the package (packages/cli/vitest.config.ts's header +// records the incident that taught that). +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, +}); diff --git a/packages/drivers/driver-turso/vitest.config.ts b/packages/drivers/driver-turso/vitest.config.ts new file mode 100644 index 0000000000..5e0591efc4 --- /dev/null +++ b/packages/drivers/driver-turso/vitest.config.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// This config exists for exactly one setting; everything else stays on +// vitest's defaults, deliberately — a key added here re-specifies behaviour +// for every test file in the package (packages/cli/vitest.config.ts's header +// records the incident that taught that). +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, +}); diff --git a/packages/formula/vitest.config.ts b/packages/formula/vitest.config.ts new file mode 100644 index 0000000000..5e0591efc4 --- /dev/null +++ b/packages/formula/vitest.config.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// This config exists for exactly one setting; everything else stays on +// vitest's defaults, deliberately — a key added here re-specifies behaviour +// for every test file in the package (packages/cli/vitest.config.ts's header +// records the incident that taught that). +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, +}); diff --git a/packages/lint/vitest.config.ts b/packages/lint/vitest.config.ts new file mode 100644 index 0000000000..5e0591efc4 --- /dev/null +++ b/packages/lint/vitest.config.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// This config exists for exactly one setting; everything else stays on +// vitest's defaults, deliberately — a key added here re-specifies behaviour +// for every test file in the package (packages/cli/vitest.config.ts's header +// records the incident that taught that). +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, +}); diff --git a/packages/mcp/vitest.config.ts b/packages/mcp/vitest.config.ts index 85dde61042..0ecbfd2d45 100644 --- a/packages/mcp/vitest.config.ts +++ b/packages/mcp/vitest.config.ts @@ -5,6 +5,13 @@ import path from 'path'; export default defineConfig({ test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, environment: 'node', }, resolve: { diff --git a/packages/metadata-core/vitest.config.ts b/packages/metadata-core/vitest.config.ts index bd4f0bf346..68e5d5330b 100644 --- a/packages/metadata-core/vitest.config.ts +++ b/packages/metadata-core/vitest.config.ts @@ -2,6 +2,13 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, include: ['test/**/*.test.ts', 'src/**/*.test.ts'], environment: 'node', }, diff --git a/packages/metadata-fs/vitest.config.ts b/packages/metadata-fs/vitest.config.ts index b950de441a..15837aee6e 100644 --- a/packages/metadata-fs/vitest.config.ts +++ b/packages/metadata-fs/vitest.config.ts @@ -4,6 +4,13 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, globals: false, environment: 'node', testTimeout: 10000, diff --git a/packages/metadata-protocol/vitest.config.ts b/packages/metadata-protocol/vitest.config.ts new file mode 100644 index 0000000000..5e0591efc4 --- /dev/null +++ b/packages/metadata-protocol/vitest.config.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// This config exists for exactly one setting; everything else stays on +// vitest's defaults, deliberately — a key added here re-specifies behaviour +// for every test file in the package (packages/cli/vitest.config.ts's header +// records the incident that taught that). +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, +}); diff --git a/packages/metadata/vitest.config.ts b/packages/metadata/vitest.config.ts index 4450e6423f..48350fede6 100644 --- a/packages/metadata/vitest.config.ts +++ b/packages/metadata/vitest.config.ts @@ -52,6 +52,13 @@ export default defineConfig({ ], }, test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, globals: true, environment: 'node', include: ['src/**/*.test.ts'], diff --git a/packages/objectql/vitest.config.ts b/packages/objectql/vitest.config.ts new file mode 100644 index 0000000000..5e0591efc4 --- /dev/null +++ b/packages/objectql/vitest.config.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// This config exists for exactly one setting; everything else stays on +// vitest's defaults, deliberately — a key added here re-specifies behaviour +// for every test file in the package (packages/cli/vitest.config.ts's header +// records the incident that taught that). +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, +}); diff --git a/packages/observability/vitest.config.ts b/packages/observability/vitest.config.ts new file mode 100644 index 0000000000..5e0591efc4 --- /dev/null +++ b/packages/observability/vitest.config.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// This config exists for exactly one setting; everything else stays on +// vitest's defaults, deliberately — a key added here re-specifies behaviour +// for every test file in the package (packages/cli/vitest.config.ts's header +// records the incident that taught that). +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, +}); diff --git a/packages/platform-objects/vitest.config.ts b/packages/platform-objects/vitest.config.ts index 316265dde4..79d431ef26 100644 --- a/packages/platform-objects/vitest.config.ts +++ b/packages/platform-objects/vitest.config.ts @@ -4,6 +4,15 @@ import { defineConfig } from 'vitest/config'; import path from 'node:path'; export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, resolve: { // One entry, for `managed-api-method-affordance-sweep.test.ts` (#7934) — // the only suite here that imports a sibling package as a VALUE. It calls diff --git a/packages/plugins/embedder-openai/vitest.config.ts b/packages/plugins/embedder-openai/vitest.config.ts index 0a273614c7..0b465dd81e 100644 --- a/packages/plugins/embedder-openai/vitest.config.ts +++ b/packages/plugins/embedder-openai/vitest.config.ts @@ -5,6 +5,13 @@ import path from 'path'; export default defineConfig({ test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, globals: true, environment: 'node', }, diff --git a/packages/plugins/knowledge-memory/vitest.config.ts b/packages/plugins/knowledge-memory/vitest.config.ts index e14a089922..b16f31d9af 100644 --- a/packages/plugins/knowledge-memory/vitest.config.ts +++ b/packages/plugins/knowledge-memory/vitest.config.ts @@ -5,6 +5,13 @@ import path from 'path'; export default defineConfig({ test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, globals: true, environment: 'node', }, diff --git a/packages/plugins/knowledge-ragflow/vitest.config.ts b/packages/plugins/knowledge-ragflow/vitest.config.ts index e14a089922..b16f31d9af 100644 --- a/packages/plugins/knowledge-ragflow/vitest.config.ts +++ b/packages/plugins/knowledge-ragflow/vitest.config.ts @@ -5,6 +5,13 @@ import path from 'path'; export default defineConfig({ test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, globals: true, environment: 'node', }, diff --git a/packages/plugins/plugin-approvals/vitest.config.ts b/packages/plugins/plugin-approvals/vitest.config.ts index 7bd6df505a..213f2997bc 100644 --- a/packages/plugins/plugin-approvals/vitest.config.ts +++ b/packages/plugins/plugin-approvals/vitest.config.ts @@ -33,6 +33,13 @@ export default defineConfig({ ], }, test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, globals: true, environment: 'node', include: ['src/**/*.test.ts'], diff --git a/packages/plugins/plugin-audit/vitest.config.ts b/packages/plugins/plugin-audit/vitest.config.ts index 3313c9917d..86c03c897f 100644 --- a/packages/plugins/plugin-audit/vitest.config.ts +++ b/packages/plugins/plugin-audit/vitest.config.ts @@ -11,6 +11,13 @@ import path from 'path'; export default defineConfig({ test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, globals: true, environment: 'node', }, diff --git a/packages/plugins/plugin-auth/vitest.config.ts b/packages/plugins/plugin-auth/vitest.config.ts index 017b1e591c..ff8d75bd92 100644 --- a/packages/plugins/plugin-auth/vitest.config.ts +++ b/packages/plugins/plugin-auth/vitest.config.ts @@ -8,6 +8,13 @@ const here = path.dirname(fileURLToPath(import.meta.url)); export default defineConfig({ test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, environment: 'node', testTimeout: 10_000, alias: [ diff --git a/packages/plugins/plugin-dev/vitest.config.ts b/packages/plugins/plugin-dev/vitest.config.ts index f9689a0ce8..a883510405 100644 --- a/packages/plugins/plugin-dev/vitest.config.ts +++ b/packages/plugins/plugin-dev/vitest.config.ts @@ -10,6 +10,13 @@ import path from 'path'; export default defineConfig({ test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, globals: true, environment: 'node', }, diff --git a/packages/plugins/plugin-email/vitest.config.ts b/packages/plugins/plugin-email/vitest.config.ts new file mode 100644 index 0000000000..5e0591efc4 --- /dev/null +++ b/packages/plugins/plugin-email/vitest.config.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// This config exists for exactly one setting; everything else stays on +// vitest's defaults, deliberately — a key added here re-specifies behaviour +// for every test file in the package (packages/cli/vitest.config.ts's header +// records the incident that taught that). +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, +}); diff --git a/packages/plugins/plugin-hono-server/vitest.config.ts b/packages/plugins/plugin-hono-server/vitest.config.ts index c5e324b61d..5b3b2cc5c4 100644 --- a/packages/plugins/plugin-hono-server/vitest.config.ts +++ b/packages/plugins/plugin-hono-server/vitest.config.ts @@ -5,6 +5,13 @@ import path from 'path'; export default defineConfig({ test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, globals: true, environment: 'node', }, diff --git a/packages/plugins/plugin-pinyin-search/vitest.config.ts b/packages/plugins/plugin-pinyin-search/vitest.config.ts new file mode 100644 index 0000000000..5e0591efc4 --- /dev/null +++ b/packages/plugins/plugin-pinyin-search/vitest.config.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// This config exists for exactly one setting; everything else stays on +// vitest's defaults, deliberately — a key added here re-specifies behaviour +// for every test file in the package (packages/cli/vitest.config.ts's header +// records the incident that taught that). +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, +}); diff --git a/packages/plugins/plugin-reports/vitest.config.ts b/packages/plugins/plugin-reports/vitest.config.ts new file mode 100644 index 0000000000..5e0591efc4 --- /dev/null +++ b/packages/plugins/plugin-reports/vitest.config.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// This config exists for exactly one setting; everything else stays on +// vitest's defaults, deliberately — a key added here re-specifies behaviour +// for every test file in the package (packages/cli/vitest.config.ts's header +// records the incident that taught that). +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, +}); diff --git a/packages/plugins/plugin-security/vitest.config.ts b/packages/plugins/plugin-security/vitest.config.ts index b4042e2092..8d43c6bfad 100644 --- a/packages/plugins/plugin-security/vitest.config.ts +++ b/packages/plugins/plugin-security/vitest.config.ts @@ -5,6 +5,13 @@ import path from 'path'; export default defineConfig({ test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, globals: true, environment: 'node', }, diff --git a/packages/plugins/plugin-sharing/vitest.config.ts b/packages/plugins/plugin-sharing/vitest.config.ts index 90cf8bb9fe..32c1ba8950 100644 --- a/packages/plugins/plugin-sharing/vitest.config.ts +++ b/packages/plugins/plugin-sharing/vitest.config.ts @@ -5,6 +5,13 @@ import path from 'path'; export default defineConfig({ test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, environment: 'node', }, resolve: { diff --git a/packages/plugins/plugin-webhooks/vitest.config.ts b/packages/plugins/plugin-webhooks/vitest.config.ts index 33317de9c0..8f1f0e8272 100644 --- a/packages/plugins/plugin-webhooks/vitest.config.ts +++ b/packages/plugins/plugin-webhooks/vitest.config.ts @@ -29,6 +29,15 @@ import path from 'node:path'; * would silently narrow what the suite collects. */ export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, resolve: { alias: [ { find: /^@objectstack\/core$/, replacement: path.resolve(__dirname, '../../core/src/index.ts') }, diff --git a/packages/qa/dogfood/vitest.config.ts b/packages/qa/dogfood/vitest.config.ts index e16250942c..d032eb2512 100644 --- a/packages/qa/dogfood/vitest.config.ts +++ b/packages/qa/dogfood/vitest.config.ts @@ -57,6 +57,11 @@ export default defineConfig({ projects: [ { test: { + // #10374: disarm the late-console teardown race. Set PER PROJECT because + // inline projects do not inherit the root-level setting (measured on + // vitest 4.1.10). Mechanism: examples/app-showcase/vitest.config.ts. + // Enforced by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, name: 'shared-showcase', include: SHARED_SHOWCASE, isolate: false, @@ -64,6 +69,11 @@ export default defineConfig({ }, { test: { + // #10374: disarm the late-console teardown race. Set PER PROJECT because + // inline projects do not inherit the root-level setting (measured on + // vitest 4.1.10). Mechanism: examples/app-showcase/vitest.config.ts. + // Enforced by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, name: 'isolated', include: ['test/**/*.test.ts'], exclude: SHARED_SHOWCASE, diff --git a/packages/qa/downstream-contract/vitest.config.ts b/packages/qa/downstream-contract/vitest.config.ts index 7898efa662..9fff21543b 100644 --- a/packages/qa/downstream-contract/vitest.config.ts +++ b/packages/qa/downstream-contract/vitest.config.ts @@ -5,6 +5,13 @@ import path from 'path'; export default defineConfig({ test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, environment: 'node', }, resolve: { diff --git a/packages/qa/http-conformance/vitest.config.ts b/packages/qa/http-conformance/vitest.config.ts index 201ee43e71..3afab41a0c 100644 --- a/packages/qa/http-conformance/vitest.config.ts +++ b/packages/qa/http-conformance/vitest.config.ts @@ -4,6 +4,13 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, globals: true, environment: 'node', // The conformance suite boots real kernels + sockets. diff --git a/packages/rest/vitest.config.ts b/packages/rest/vitest.config.ts index bf9784f482..8a68f19248 100644 --- a/packages/rest/vitest.config.ts +++ b/packages/rest/vitest.config.ts @@ -5,6 +5,13 @@ import path from 'path'; export default defineConfig({ test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, globals: true, environment: 'node', }, diff --git a/packages/runtime/vitest.config.ts b/packages/runtime/vitest.config.ts index e65d458edf..74f150efce 100644 --- a/packages/runtime/vitest.config.ts +++ b/packages/runtime/vitest.config.ts @@ -113,6 +113,13 @@ export default defineConfig({ ], }, test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, globals: true, environment: 'node', include: ['src/**/*.test.ts'], diff --git a/packages/sdui-parser/vitest.config.ts b/packages/sdui-parser/vitest.config.ts new file mode 100644 index 0000000000..5e0591efc4 --- /dev/null +++ b/packages/sdui-parser/vitest.config.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// This config exists for exactly one setting; everything else stays on +// vitest's defaults, deliberately — a key added here re-specifies behaviour +// for every test file in the package (packages/cli/vitest.config.ts's header +// records the incident that taught that). +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, +}); diff --git a/packages/services/service-analytics/vitest.config.ts b/packages/services/service-analytics/vitest.config.ts new file mode 100644 index 0000000000..5e0591efc4 --- /dev/null +++ b/packages/services/service-analytics/vitest.config.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// This config exists for exactly one setting; everything else stays on +// vitest's defaults, deliberately — a key added here re-specifies behaviour +// for every test file in the package (packages/cli/vitest.config.ts's header +// records the incident that taught that). +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, +}); diff --git a/packages/services/service-automation/vitest.config.ts b/packages/services/service-automation/vitest.config.ts index b023d8e61f..be52266208 100644 --- a/packages/services/service-automation/vitest.config.ts +++ b/packages/services/service-automation/vitest.config.ts @@ -19,6 +19,15 @@ import path from 'node:path'; * `packages/services/service-messaging/vitest.config.ts`. */ export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, resolve: { alias: [ { diff --git a/packages/services/service-cache/vitest.config.ts b/packages/services/service-cache/vitest.config.ts new file mode 100644 index 0000000000..5e0591efc4 --- /dev/null +++ b/packages/services/service-cache/vitest.config.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// This config exists for exactly one setting; everything else stays on +// vitest's defaults, deliberately — a key added here re-specifies behaviour +// for every test file in the package (packages/cli/vitest.config.ts's header +// records the incident that taught that). +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, +}); diff --git a/packages/services/service-cluster-redis/vitest.config.ts b/packages/services/service-cluster-redis/vitest.config.ts new file mode 100644 index 0000000000..5e0591efc4 --- /dev/null +++ b/packages/services/service-cluster-redis/vitest.config.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// This config exists for exactly one setting; everything else stays on +// vitest's defaults, deliberately — a key added here re-specifies behaviour +// for every test file in the package (packages/cli/vitest.config.ts's header +// records the incident that taught that). +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, +}); diff --git a/packages/services/service-cluster/vitest.config.ts b/packages/services/service-cluster/vitest.config.ts index 01c5d06b19..c2574b5d48 100644 --- a/packages/services/service-cluster/vitest.config.ts +++ b/packages/services/service-cluster/vitest.config.ts @@ -19,6 +19,15 @@ import path from 'node:path'; * `packages/services/service-messaging/vitest.config.ts`. */ export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, resolve: { alias: [ { diff --git a/packages/services/service-datasource/vitest.config.ts b/packages/services/service-datasource/vitest.config.ts index bef9273c58..04ed6c0e9f 100644 --- a/packages/services/service-datasource/vitest.config.ts +++ b/packages/services/service-datasource/vitest.config.ts @@ -5,6 +5,13 @@ import path from 'path'; export default defineConfig({ test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, environment: 'node', // This package had no vitest config at all, so every case ran under // vitest's 5000ms default — the structural hole #4856 could not cover diff --git a/packages/services/service-i18n/vitest.config.ts b/packages/services/service-i18n/vitest.config.ts new file mode 100644 index 0000000000..5e0591efc4 --- /dev/null +++ b/packages/services/service-i18n/vitest.config.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// This config exists for exactly one setting; everything else stays on +// vitest's defaults, deliberately — a key added here re-specifies behaviour +// for every test file in the package (packages/cli/vitest.config.ts's header +// records the incident that taught that). +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, +}); diff --git a/packages/services/service-job/vitest.config.ts b/packages/services/service-job/vitest.config.ts new file mode 100644 index 0000000000..5e0591efc4 --- /dev/null +++ b/packages/services/service-job/vitest.config.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// This config exists for exactly one setting; everything else stays on +// vitest's defaults, deliberately — a key added here re-specifies behaviour +// for every test file in the package (packages/cli/vitest.config.ts's header +// records the incident that taught that). +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, +}); diff --git a/packages/services/service-knowledge/vitest.config.ts b/packages/services/service-knowledge/vitest.config.ts index 3a1e587003..4bfc71ed08 100644 --- a/packages/services/service-knowledge/vitest.config.ts +++ b/packages/services/service-knowledge/vitest.config.ts @@ -5,6 +5,13 @@ import path from 'path'; export default defineConfig({ test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, globals: true, environment: 'node', }, diff --git a/packages/services/service-messaging/vitest.config.ts b/packages/services/service-messaging/vitest.config.ts index 58bc9ec2e3..fe92a78a42 100644 --- a/packages/services/service-messaging/vitest.config.ts +++ b/packages/services/service-messaging/vitest.config.ts @@ -18,6 +18,15 @@ import path from 'node:path'; * `examples/app-showcase/vitest.config.ts`. */ export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, resolve: { alias: [ { find: /^@objectstack\/core$/, replacement: path.resolve(__dirname, '../../core/src/index.ts') }, diff --git a/packages/services/service-package/vitest.config.ts b/packages/services/service-package/vitest.config.ts new file mode 100644 index 0000000000..5e0591efc4 --- /dev/null +++ b/packages/services/service-package/vitest.config.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// This config exists for exactly one setting; everything else stays on +// vitest's defaults, deliberately — a key added here re-specifies behaviour +// for every test file in the package (packages/cli/vitest.config.ts's header +// records the incident that taught that). +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, +}); diff --git a/packages/services/service-queue/vitest.config.ts b/packages/services/service-queue/vitest.config.ts new file mode 100644 index 0000000000..5e0591efc4 --- /dev/null +++ b/packages/services/service-queue/vitest.config.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// This config exists for exactly one setting; everything else stays on +// vitest's defaults, deliberately — a key added here re-specifies behaviour +// for every test file in the package (packages/cli/vitest.config.ts's header +// records the incident that taught that). +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, +}); diff --git a/packages/services/service-realtime/vitest.config.ts b/packages/services/service-realtime/vitest.config.ts new file mode 100644 index 0000000000..5e0591efc4 --- /dev/null +++ b/packages/services/service-realtime/vitest.config.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// This config exists for exactly one setting; everything else stays on +// vitest's defaults, deliberately — a key added here re-specifies behaviour +// for every test file in the package (packages/cli/vitest.config.ts's header +// records the incident that taught that). +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, +}); diff --git a/packages/services/service-settings/vitest.config.ts b/packages/services/service-settings/vitest.config.ts index 93e040b538..2dcc5aaa26 100644 --- a/packages/services/service-settings/vitest.config.ts +++ b/packages/services/service-settings/vitest.config.ts @@ -37,6 +37,13 @@ import path from 'path'; export default defineConfig({ test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, globals: true, environment: 'node', }, diff --git a/packages/services/service-sms/vitest.config.ts b/packages/services/service-sms/vitest.config.ts new file mode 100644 index 0000000000..5e0591efc4 --- /dev/null +++ b/packages/services/service-sms/vitest.config.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// This config exists for exactly one setting; everything else stays on +// vitest's defaults, deliberately — a key added here re-specifies behaviour +// for every test file in the package (packages/cli/vitest.config.ts's header +// records the incident that taught that). +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, +}); diff --git a/packages/services/service-storage/vitest.config.ts b/packages/services/service-storage/vitest.config.ts index ec37aa0bf9..7bbb33530a 100644 --- a/packages/services/service-storage/vitest.config.ts +++ b/packages/services/service-storage/vitest.config.ts @@ -5,6 +5,13 @@ import path from 'path'; export default defineConfig({ test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, environment: 'node', }, resolve: { diff --git a/packages/spec/vitest.config.ts b/packages/spec/vitest.config.ts index 5dcd3cb829..c3fa3a7a02 100644 --- a/packages/spec/vitest.config.ts +++ b/packages/spec/vitest.config.ts @@ -4,6 +4,13 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, globals: true, environment: 'node', include: ['src/**/*.test.ts', 'scripts/**/*.test.ts'], diff --git a/packages/triggers/trigger-api/vitest.config.ts b/packages/triggers/trigger-api/vitest.config.ts new file mode 100644 index 0000000000..5e0591efc4 --- /dev/null +++ b/packages/triggers/trigger-api/vitest.config.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// This config exists for exactly one setting; everything else stays on +// vitest's defaults, deliberately — a key added here re-specifies behaviour +// for every test file in the package (packages/cli/vitest.config.ts's header +// records the incident that taught that). +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, +}); diff --git a/packages/triggers/trigger-record-change/vitest.config.ts b/packages/triggers/trigger-record-change/vitest.config.ts index 5391c0911c..5a7df708fc 100644 --- a/packages/triggers/trigger-record-change/vitest.config.ts +++ b/packages/triggers/trigger-record-change/vitest.config.ts @@ -20,6 +20,13 @@ import path from 'path'; */ export default defineConfig({ test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, globals: true, environment: 'node', }, diff --git a/packages/triggers/trigger-schedule/vitest.config.ts b/packages/triggers/trigger-schedule/vitest.config.ts new file mode 100644 index 0000000000..5e0591efc4 --- /dev/null +++ b/packages/triggers/trigger-schedule/vitest.config.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// This config exists for exactly one setting; everything else stays on +// vitest's defaults, deliberately — a key added here re-specifies behaviour +// for every test file in the package (packages/cli/vitest.config.ts's header +// records the incident that taught that). +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, +}); diff --git a/packages/types/vitest.config.ts b/packages/types/vitest.config.ts new file mode 100644 index 0000000000..5e0591efc4 --- /dev/null +++ b/packages/types/vitest.config.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// This config exists for exactly one setting; everything else stays on +// vitest's defaults, deliberately — a key added here re-specifies behaviour +// for every test file in the package (packages/cli/vitest.config.ts's header +// records the incident that taught that). +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, +}); diff --git a/packages/verify/vitest.config.ts b/packages/verify/vitest.config.ts new file mode 100644 index 0000000000..5e0591efc4 --- /dev/null +++ b/packages/verify/vitest.config.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// This config exists for exactly one setting; everything else stays on +// vitest's defaults, deliberately — a key added here re-specifies behaviour +// for every test file in the package (packages/cli/vitest.config.ts's header +// records the incident that taught that). +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // A late console.* must not redden a green suite (#10374): vitest's worker + // forwards console output over RPC and discards the promise, and a write + // landing after teardown's rpcDone() snapshot is rejected into an unhandled + // error — a fully green run that exits 1. Disarming removes the mechanism. + // Mechanism + measured costs: examples/app-showcase/vitest.config.ts. + // Enforced repo-wide by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true, + }, +}); diff --git a/scripts/check-console-intercept-disarm.mjs b/scripts/check-console-intercept-disarm.mjs new file mode 100644 index 0000000000..ca57638fb8 --- /dev/null +++ b/scripts/check-console-intercept-disarm.mjs @@ -0,0 +1,524 @@ +#!/usr/bin/env node +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. +// +// check-console-intercept-disarm — every package that runs vitest must disarm +// vitest's console interception (`disableConsoleIntercept: true`) in its +// package-root vitest config. +// +// ── The defect this keeps closed (#10374, fix shape landed in #10293) ─────── +// +// vitest 4's worker replaces `console` with one that forwards every write to +// the main thread over RPC, and `sendLog` (in vitest's own +// `dist/chunks/console.*.js`) DISCARDS the promise that forwarding returns. +// Worker teardown then runs `await rpcDone()` — which awaits a SNAPSHOT of the +// pending set — followed by `$rejectPendingCalls(...)`. A console RPC created +// after that snapshot is rejected as an UNHANDLED error, and vitest fails the +// run even though every assertion passed. The recognisable signature: +// +// Test Files N passed (N) +// Tests X passed (X) +// Errors 1 error +// EnvironmentTeardownError: [vitest-worker]: Closing rpc while +// "onUserConsoleLog" was pending +// +// ⚠️ The file that error names is where the rejection ORIGINATED (the file the +// worker happened to be on), not where the bug is. The window is load-width — +// ~1ms idle, wide enough for any leaked timer or fire-and-forget write on a +// saturated runner — which is why it presents as a merge-queue flake that "no +// one can reproduce". Full mechanism, reproduction and measured costs: +// `examples/app-showcase/vitest.config.ts` (the disarm's docblock) and +// `examples/app-showcase/test/vitest-console-teardown-race.test.ts` (the pin +// that proves the mechanism is still live in the installed vitest, and fails +// if showcase's disarm is removed). +// +// Setting `disableConsoleIntercept: true` removes the MECHANISM rather than +// narrowing the trigger: with no console RPC there is no pending call for +// `$rejectPendingCalls` to reject, so no future late `console.log` in any +// package can redden a green run this way. +// +// ── Why a gate and not a sweep (#10374's measured population) ─────────────── +// +// The sweep that accompanied this gate disarmed every suite it could find — +// and the population it found was already stale-prone in both directions: +// 38 vitest configs on 2026-08-21 had become 43 by 2026-08-30 (five new +// configs in nine days, every one of them arriving with the intercept armed), +// and 29 MORE packages ran vitest with no config file at all, invisible to +// any count of `vitest.config.ts`. A sweep's terminal state leaves the next +// package unguarded, and the symptom of the omission is a suite that is green +// for months and then fails a merge-queue run no one can reproduce. The +// invariant has to be asserted mechanically or it is not asserted at all. +// +// ── What this checks ──────────────────────────────────────────────────────── +// +// For every workspace package (from `pnpm-workspace.yaml`'s globs) whose +// `scripts` invoke vitest: +// +// 1. A package-root vitest config must exist (`vitest.config.{ts,mts,cts, +// js,mjs,cjs}`). A package with no config runs vitest's defaults, and +// the default arms the intercept. +// 2. Its comment-masked source must set `disableConsoleIntercept: true` — +// masked via `scripts/js-comment-mask.mjs`, so a docblock ABOUT the +// setting (this repo's configs carry long rationale comments) never +// satisfies the check, and a commented-out setting doesn't either. +// 3. `disableConsoleIntercept: false` at package root is refused loudly — +// it re-arms the mechanism while reading as a considered choice. +// +// Deliberately OUT of scope: configs below the package root, e.g. +// `examples/app-showcase/test/fixtures/late-console-teardown/ +// vitest.unguarded.config.ts` — that fixture sets `false` ON PURPOSE (it is +// the positive control the teardown-race pin measures the defect with), and +// scoping this gate to package-root configs is what lets it exist. +// +// No waiver ledger, deliberately: no suite in the repo today depends on the +// interception (measured: no package-root config sets `onConsoleLog`, and the +// full-population measurement behind #10374 found the intercepted output of +// every suite was being DISCARDED by the non-TTY default reporter's +// `silent: 'passed-only'`). If a real dependency on interception ever +// appears, add a shrink-only ledger here with the package and the reason — +// never widen the scan away from the package. +// +// Exit 0: every vitest-running package is disarmed. Exit 1: findings (each +// names the package, what is missing, and the exact line to add). Exit 2: +// the gate itself could not measure (broken workspace file, empty population) +// — never reported as a pass. +// +// node scripts/check-console-intercept-disarm.mjs +// node scripts/check-console-intercept-disarm.mjs --self-test + +import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { dirname, join, resolve } from 'node:path'; +import { tmpdir } from 'node:os'; +import { fileURLToPath } from 'node:url'; +import { blank, scanSource } from './js-comment-mask.mjs'; +import { isEntrypoint } from './invoked-as.mjs'; + +/** + * Comments AND string/template/regex content blanked, offsets kept. This gate + * looks for a bare code-position `disableConsoleIntercept: true`, so unlike + * the gates whose signal IS a string literal, a quoted spelling here is never + * the real setting — it is prose (an error message, a doc snippet) and + * blanking it keeps prose from satisfying the check. The boundary this + * accepts: a config spelling the KEY as a quoted property + * (`'disableConsoleIntercept': true`) reds the gate even though vitest would + * honour it — the failure is loud, names the file, and the remedy is the + * unquoted spelling every other config uses. + */ +function maskProse(source) { + const { comment, literal } = scanSource(source); + const flags = new Uint8Array(comment.length); + for (let i = 0; i < flags.length; i++) flags[i] = comment[i] | literal[i]; + return blank(source, flags); +} + +const HERE = dirname(fileURLToPath(import.meta.url)); +const REPO_ROOT = resolve(HERE, '..'); + +const VITEST_CONFIG_NAMES = [ + 'vitest.config.ts', + 'vitest.config.mts', + 'vitest.config.cts', + 'vitest.config.js', + 'vitest.config.mjs', + 'vitest.config.cjs', +]; + +const DISARM_RE = /\bdisableConsoleIntercept\s*:\s*true\b/; +const REARM_RE = /\bdisableConsoleIntercept\s*:\s*false\b/; +const PROJECTS_RE = /\bprojects\s*:\s*\[/; +const TEST_BLOCK_RE = /\btest\s*:\s*\{/g; + +/** + * For a config that defines inline `projects`, the ROOT-level setting is + * INERT: measured on vitest 4.1.10 (probe fixture: root + * `disableConsoleIntercept: true` + one inline project + a logging test — + * the reporter received the console RPC all the same, byte-identical to the + * armed control). So a projects config must carry the disarm inside EVERY + * project's own `test` block, and a root-only spelling is precisely the + * declared-≠-enforced shape this repo refuses. Brace-matching is safe here + * because the source arrives with comments AND string/template content + * blanked — no brace inside prose survives to miscount. + * + * @returns {string[]} the masked bodies of every `test: {...}` block that + * sits INSIDE the projects array. + */ +function projectTestBlocks(masked) { + const start = PROJECTS_RE.exec(masked); + if (!start) return []; + const openBracket = masked.indexOf('[', start.index); + let depth = 0; + let end = -1; + for (let i = openBracket; i < masked.length; i++) { + if (masked[i] === '[') depth += 1; + else if (masked[i] === ']') { + depth -= 1; + if (depth === 0) { + end = i; + break; + } + } + } + if (end < 0) return []; + const region = masked.slice(openBracket, end + 1); + const blocks = []; + TEST_BLOCK_RE.lastIndex = 0; + let m; + while ((m = TEST_BLOCK_RE.exec(region)) !== null) { + const open = openBracket + m.index + m[0].length - 1; // the '{' + let d = 0; + for (let i = open; i <= end; i++) { + if (masked[i] === '{') d += 1; + else if (masked[i] === '}') { + d -= 1; + if (d === 0) { + blocks.push(masked.slice(open, i + 1)); + break; + } + } + } + } + return blocks; +} +/** A script that runs vitest — `vitest run`, `vitest --coverage`, a bare `vitest`. */ +const VITEST_SCRIPT_RE = /(?:^|\s|&&|;)vitest(?:\s|$)/; + +const REMEDY = ` // Late console writes must not redden a green suite (#10374) — see the + // mechanism docblock in examples/app-showcase/vitest.config.ts. Enforced + // by scripts/check-console-intercept-disarm.mjs. + disableConsoleIntercept: true,`; + +/** + * Expand `pnpm-workspace.yaml`'s package globs. Every glob in that file is + * either a literal directory or a single-level `