From 7e04c853a7bef40b5af15faa548076d091a6f642 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 30 Aug 2026 15:10:13 +0000 Subject: [PATCH 1/3] test(infra): gate every vitest-running package on disableConsoleIntercept (#10374) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The late-console teardown race (#10374) is removed per suite by disableConsoleIntercept: true; this gate makes the invariant hold for the whole moving population — 38 vitest configs on 2026-08-21 were 43 by 2026-08-30, every arrival with the intercept armed, and 29 more packages run vitest with no config file at all. The sweep that applies the setting to all of them follows in this branch; the gate is what keeps the next arrival from undoing it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC --- .github/workflows/lint.yml | 20 + scripts/check-console-intercept-disarm.mjs | 423 +++++++++++++++++++++ 2 files changed, 443 insertions(+) create mode 100644 scripts/check-console-intercept-disarm.mjs 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/scripts/check-console-intercept-disarm.mjs b/scripts/check-console-intercept-disarm.mjs new file mode 100644 index 0000000000..5fefb28722 --- /dev/null +++ b/scripts/check-console-intercept-disarm.mjs @@ -0,0 +1,423 @@ +#!/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'; + +/** + * 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/; +/** 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 `/*`; this expander + * refuses anything wider so a future workspace-file shape FAILS the gate + * instead of silently emptying its population. + */ +export function workspacePackageDirs(root) { + const raw = readFileSync(join(root, 'pnpm-workspace.yaml'), 'utf8'); + const globs = []; + let inPackages = false; + for (const line of raw.split('\n')) { + if (/^packages:\s*$/.test(line)) { + inPackages = true; + continue; + } + if (inPackages) { + const m = line.match(/^\s+-\s+['"]?([^'"#\s]+)['"]?\s*$/); + if (m) { + globs.push(m[1]); + continue; + } + if (line.trim() !== '') inPackages = false; + } + } + if (globs.length === 0) { + throw new Error(`no package globs parsed from ${join(root, 'pnpm-workspace.yaml')}`); + } + const dirs = []; + for (const glob of globs) { + if (glob.endsWith('/*')) { + const base = join(root, glob.slice(0, -2)); + if (!existsSync(base)) continue; + for (const entry of readdirSync(base, { withFileTypes: true })) { + if (!entry.isDirectory()) continue; + const dir = join(base, entry.name); + if (existsSync(join(dir, 'package.json'))) dirs.push(dir); + } + } else if (!glob.includes('*')) { + const dir = join(root, glob); + if (existsSync(join(dir, 'package.json'))) dirs.push(dir); + } else { + throw new Error( + `pnpm-workspace.yaml glob '${glob}' is wider than '/*' — teach ` + + `workspacePackageDirs() the new shape before trusting this gate again`, + ); + } + } + return dirs; +} + +/** @returns {{findings: string[], vitestPackages: number}} */ +export function scan(root) { + const findings = []; + let vitestPackages = 0; + for (const dir of workspacePackageDirs(root)) { + let manifest; + try { + manifest = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')); + } catch (error) { + findings.push(`${rel(root, dir)}/package.json: unreadable (${error.message})`); + continue; + } + const scripts = Object.values(manifest.scripts ?? {}); + if (!scripts.some((s) => typeof s === 'string' && VITEST_SCRIPT_RE.test(s))) continue; + vitestPackages += 1; + + const configName = VITEST_CONFIG_NAMES.find((name) => existsSync(join(dir, name))); + if (!configName) { + findings.push( + `${rel(root, dir)}: runs vitest with NO package-root vitest config — vitest's ` + + `default ARMS console interception, exposing this suite to the #10374 ` + + `teardown race. Add a vitest.config.ts whose test block carries:\n${REMEDY}`, + ); + continue; + } + const masked = maskProse(readFileSync(join(dir, configName), 'utf8')); + if (REARM_RE.test(masked)) { + findings.push( + `${rel(root, dir)}/${configName}: sets disableConsoleIntercept: FALSE — this ` + + `re-arms the #10374 teardown race at the package root. The only sanctioned ` + + `home for that value is a fixture config BELOW the package root (the ` + + `teardown-race pin's positive control). Set it to true, or move the config ` + + `under the fixture directory that needs it.`, + ); + continue; + } + if (!DISARM_RE.test(masked)) { + findings.push( + `${rel(root, dir)}/${configName}: does not set disableConsoleIntercept: true ` + + `(a comment about it does not count) — this suite pays a console RPC per ` + + `write and any late console.log can fail its fully green run ` + + `(EnvironmentTeardownError: [vitest-worker]: Closing rpc while ` + + `"onUserConsoleLog" was pending). Add to the test block:\n${REMEDY}`, + ); + } + } + return { findings, vitestPackages }; +} + +function rel(root, path) { + return path.startsWith(root) ? path.slice(root.length + 1) : path; +} + +function main() { + let result; + try { + result = scan(REPO_ROOT); + } catch (error) { + console.error(`check-console-intercept-disarm: MEASUREMENT FAILED — ${error.message}`); + process.exit(2); + } + const { findings, vitestPackages } = result; + if (vitestPackages === 0) { + console.error( + 'check-console-intercept-disarm: MEASUREMENT FAILED — the scan found ZERO ' + + 'vitest-running packages, and this repo has dozens. The population went ' + + 'missing, which is not the same fact as "everything is disarmed".', + ); + process.exit(2); + } + if (findings.length > 0) { + console.error( + `check-console-intercept-disarm: ${findings.length} package(s) exposed to the ` + + `vitest late-console teardown race (#10374):\n\n${findings.join('\n\n')}\n`, + ); + process.exit(1); + } + console.log( + `OK: ${vitestPackages} vitest-running package(s), every one disarms console ` + + `interception at the package root.`, + ); +} + +// ── self-test ─────────────────────────────────────────────────────────────── +// +// Builds a throwaway workspace in $TMPDIR per case. The cases pin the verdict +// DIRECTION of every rule above — most importantly that prose never satisfies +// the check (case 5) and that a fixture config below the package root is +// invisible (case 6): those are the two silent-in-the-green-direction bugs a +// textual gate can grow. + +function buildWorkspace(caseDir, packages) { + mkdirSync(join(caseDir, 'packages'), { recursive: true }); + writeFileSync(join(caseDir, 'pnpm-workspace.yaml'), 'packages:\n - packages/*\n'); + for (const [name, files] of Object.entries(packages)) { + const dir = join(caseDir, 'packages', name); + mkdirSync(dir, { recursive: true }); + for (const [file, content] of Object.entries(files)) { + mkdirSync(dirname(join(dir, file)), { recursive: true }); + writeFileSync(join(dir, file), content); + } + } +} + +const TEST_MANIFEST = JSON.stringify({ name: 'x', scripts: { test: 'vitest run' } }); +const QUIET_MANIFEST = JSON.stringify({ name: 'x', scripts: { build: 'tsup' } }); +const DISARMED = `import { defineConfig } from 'vitest/config'; +export default defineConfig({ test: { disableConsoleIntercept: true } }); +`; + +function selfTest() { + const cases = [ + { + name: 'disarmed package passes', + packages: { a: { 'package.json': TEST_MANIFEST, 'vitest.config.ts': DISARMED } }, + expectFindings: 0, + }, + { + name: 'config without the setting fails', + packages: { + a: { + 'package.json': TEST_MANIFEST, + 'vitest.config.ts': `export default { test: { globals: true } };\n`, + }, + }, + expectFindings: 1, + expectText: 'does not set disableConsoleIntercept', + }, + { + name: 'vitest script with no config fails', + packages: { a: { 'package.json': TEST_MANIFEST } }, + expectFindings: 1, + expectText: 'NO package-root vitest config', + }, + { + name: 'explicit false at package root fails', + packages: { + a: { + 'package.json': TEST_MANIFEST, + 'vitest.config.ts': `export default { test: { disableConsoleIntercept: false } };\n`, + }, + }, + expectFindings: 1, + expectText: 'FALSE', + }, + { + name: 'a comment about the setting does not satisfy the check', + packages: { + a: { + 'package.json': TEST_MANIFEST, + 'vitest.config.ts': + `// disableConsoleIntercept: true — explained here, set nowhere\n` + + `/* disableConsoleIntercept: true */\n` + + `export default { test: { globals: true } };\n`, + }, + }, + expectFindings: 1, + expectText: 'a comment about it does not count', + }, + { + name: 'a fixture config below the package root is out of scope', + packages: { + a: { + 'package.json': TEST_MANIFEST, + 'vitest.config.ts': DISARMED, + 'test/fixtures/race/vitest.unguarded.config.ts': + `export default { test: { disableConsoleIntercept: false } };\n`, + }, + }, + expectFindings: 0, + }, + { + name: 'a package that never runs vitest is ignored', + packages: { a: { 'package.json': QUIET_MANIFEST } }, + expectFindings: 0, + expectVitestPackages: 0, + }, + { + name: 'string-guarded config: setting inside a template literal does not count', + packages: { + a: { + 'package.json': TEST_MANIFEST, + 'vitest.config.ts': + 'const doc = `disableConsoleIntercept: true`;\n' + + `export default { test: { globals: true } };\n`, + }, + }, + // String/template content is blanked before matching, so a template + // literal spelling the setting never satisfies the gate. + expectFindings: 1, + expectText: 'does not set disableConsoleIntercept', + }, + ]; + + let failures = 0; + for (const testCase of cases) { + const caseDir = mkdtempSync(join(tmpdir(), 'ci-disarm-')); + try { + buildWorkspace(caseDir, testCase.packages); + const { findings, vitestPackages } = scan(caseDir); + const problems = []; + if (findings.length !== testCase.expectFindings) { + problems.push(`expected ${testCase.expectFindings} finding(s), got ${findings.length}`); + } + if (testCase.expectText && !findings.some((f) => f.includes(testCase.expectText))) { + problems.push(`no finding contains '${testCase.expectText}'`); + } + if ( + testCase.expectVitestPackages !== undefined && + vitestPackages !== testCase.expectVitestPackages + ) { + problems.push(`expected ${testCase.expectVitestPackages} vitest package(s), got ${vitestPackages}`); + } + if (problems.length > 0) { + failures += 1; + console.error(`self-test FAIL: ${testCase.name}\n ${problems.join('\n ')}`); + for (const f of findings) console.error(` finding: ${f.split('\n')[0]}`); + } + } finally { + rmSync(caseDir, { recursive: true, force: true }); + } + } + + // Anti-vacuity over the REAL tree: the scan must see the real population. + const real = scan(REPO_ROOT); + if (real.vitestPackages < 60) { + failures += 1; + console.error( + `self-test FAIL: real-tree scan sees only ${real.vitestPackages} vitest-running ` + + `package(s); the population this gate was written against had 72. The ` + + `workspace expansion has gone blind, which would pass every future arrival.`, + ); + } + + if (failures > 0) process.exit(1); + console.log(`self-test OK: ${cases.length} cases + real-tree population floor.`); +} + +if (process.argv.includes('--self-test')) selfTest(); +else main(); From e8d5eb4bd3b4023a326f207084bca85dcab1a1ad Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 30 Aug 2026 15:16:57 +0000 Subject: [PATCH 2/3] fix(gate): put check-console-intercept-disarm's dispatch behind isEntrypoint (check:entry-guard) Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC --- scripts/check-console-intercept-disarm.mjs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/check-console-intercept-disarm.mjs b/scripts/check-console-intercept-disarm.mjs index 5fefb28722..94986009db 100644 --- a/scripts/check-console-intercept-disarm.mjs +++ b/scripts/check-console-intercept-disarm.mjs @@ -90,6 +90,7 @@ 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 @@ -419,5 +420,7 @@ function selfTest() { console.log(`self-test OK: ${cases.length} cases + real-tree population floor.`); } -if (process.argv.includes('--self-test')) selfTest(); -else main(); +if (isEntrypoint(import.meta.url)) { + if (process.argv.includes('--self-test')) selfTest(); + else main(); +} From d0154fd245294f9c8b09cb4f4738b2377997eda8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 30 Aug 2026 15:59:31 +0000 Subject: [PATCH 3/3] test(repo-wide): disarm vitest console interception in all 72 vitest-running packages (#10374) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Measured across the whole population (8,066 intercepted console RPCs per full-repo run carrying 61,086 lines, every one discarded by the non-TTY default reporter): 33/72 suites emit nothing and are disarmed for free; dogfood alone carries 67% of the volume; and the one suite that actually flaked (showcase) sits mid-tail at 340 lines — volume does not predict the trigger, so the mechanism is removed everywhere rather than the trigger narrowed anywhere. 42 configs edited, 29 created for packages that ran vitest with no config; dogfood's setting is per-project because inline projects do not inherit the root value (measured on vitest 4.1.10). Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC --- examples/app-crm/vitest.config.ts | 9 ++ examples/app-todo/vitest.config.ts | 19 ++++ examples/embed-objectql/vitest.config.ts | 19 ++++ packages/adapters/hono/vitest.config.ts | 7 ++ packages/cli/vitest.config.ts | 16 ++- packages/client-react/vitest.config.ts | 7 ++ packages/client/vitest.config.ts | 7 ++ packages/cloud-connection/vitest.config.ts | 9 ++ .../connectors/connector-mcp/vitest.config.ts | 19 ++++ .../connector-openapi/vitest.config.ts | 9 ++ .../connector-rest/vitest.config.ts | 19 ++++ .../connector-slack/vitest.config.ts | 19 ++++ packages/core/vitest.config.ts | 7 ++ packages/create-objectstack/vitest.config.ts | 7 ++ .../drivers/driver-memory/vitest.config.ts | 7 ++ .../drivers/driver-mongodb/vitest.config.ts | 7 ++ packages/drivers/driver-sql/vitest.config.ts | 7 ++ .../driver-sqlite-wasm/vitest.config.ts | 19 ++++ .../drivers/driver-turso/vitest.config.ts | 19 ++++ packages/formula/vitest.config.ts | 19 ++++ packages/lint/vitest.config.ts | 19 ++++ packages/mcp/vitest.config.ts | 7 ++ packages/metadata-core/vitest.config.ts | 7 ++ packages/metadata-fs/vitest.config.ts | 7 ++ packages/metadata-protocol/vitest.config.ts | 19 ++++ packages/metadata/vitest.config.ts | 7 ++ packages/objectql/vitest.config.ts | 19 ++++ packages/observability/vitest.config.ts | 19 ++++ packages/platform-objects/vitest.config.ts | 9 ++ .../plugins/embedder-openai/vitest.config.ts | 7 ++ .../plugins/knowledge-memory/vitest.config.ts | 7 ++ .../knowledge-ragflow/vitest.config.ts | 7 ++ .../plugins/plugin-approvals/vitest.config.ts | 7 ++ .../plugins/plugin-audit/vitest.config.ts | 7 ++ packages/plugins/plugin-auth/vitest.config.ts | 7 ++ packages/plugins/plugin-dev/vitest.config.ts | 7 ++ .../plugins/plugin-email/vitest.config.ts | 19 ++++ .../plugin-hono-server/vitest.config.ts | 7 ++ .../plugin-pinyin-search/vitest.config.ts | 19 ++++ .../plugins/plugin-reports/vitest.config.ts | 19 ++++ .../plugins/plugin-security/vitest.config.ts | 7 ++ .../plugins/plugin-sharing/vitest.config.ts | 7 ++ .../plugins/plugin-webhooks/vitest.config.ts | 9 ++ packages/qa/dogfood/vitest.config.ts | 10 ++ .../qa/downstream-contract/vitest.config.ts | 7 ++ packages/qa/http-conformance/vitest.config.ts | 7 ++ packages/rest/vitest.config.ts | 7 ++ packages/runtime/vitest.config.ts | 7 ++ packages/sdui-parser/vitest.config.ts | 19 ++++ .../service-analytics/vitest.config.ts | 19 ++++ .../service-automation/vitest.config.ts | 9 ++ .../services/service-cache/vitest.config.ts | 19 ++++ .../service-cluster-redis/vitest.config.ts | 19 ++++ .../services/service-cluster/vitest.config.ts | 9 ++ .../service-datasource/vitest.config.ts | 7 ++ .../services/service-i18n/vitest.config.ts | 19 ++++ .../services/service-job/vitest.config.ts | 19 ++++ .../service-knowledge/vitest.config.ts | 7 ++ .../service-messaging/vitest.config.ts | 9 ++ .../services/service-package/vitest.config.ts | 19 ++++ .../services/service-queue/vitest.config.ts | 19 ++++ .../service-realtime/vitest.config.ts | 19 ++++ .../service-settings/vitest.config.ts | 7 ++ .../services/service-sms/vitest.config.ts | 19 ++++ .../services/service-storage/vitest.config.ts | 7 ++ packages/spec/vitest.config.ts | 7 ++ .../triggers/trigger-api/vitest.config.ts | 19 ++++ .../trigger-record-change/vitest.config.ts | 7 ++ .../trigger-schedule/vitest.config.ts | 19 ++++ packages/types/vitest.config.ts | 19 ++++ packages/verify/vitest.config.ts | 19 ++++ scripts/check-console-intercept-disarm.mjs | 98 +++++++++++++++++++ 72 files changed, 967 insertions(+), 4 deletions(-) create mode 100644 examples/app-todo/vitest.config.ts create mode 100644 examples/embed-objectql/vitest.config.ts create mode 100644 packages/connectors/connector-mcp/vitest.config.ts create mode 100644 packages/connectors/connector-rest/vitest.config.ts create mode 100644 packages/connectors/connector-slack/vitest.config.ts create mode 100644 packages/drivers/driver-sqlite-wasm/vitest.config.ts create mode 100644 packages/drivers/driver-turso/vitest.config.ts create mode 100644 packages/formula/vitest.config.ts create mode 100644 packages/lint/vitest.config.ts create mode 100644 packages/metadata-protocol/vitest.config.ts create mode 100644 packages/objectql/vitest.config.ts create mode 100644 packages/observability/vitest.config.ts create mode 100644 packages/plugins/plugin-email/vitest.config.ts create mode 100644 packages/plugins/plugin-pinyin-search/vitest.config.ts create mode 100644 packages/plugins/plugin-reports/vitest.config.ts create mode 100644 packages/sdui-parser/vitest.config.ts create mode 100644 packages/services/service-analytics/vitest.config.ts create mode 100644 packages/services/service-cache/vitest.config.ts create mode 100644 packages/services/service-cluster-redis/vitest.config.ts create mode 100644 packages/services/service-i18n/vitest.config.ts create mode 100644 packages/services/service-job/vitest.config.ts create mode 100644 packages/services/service-package/vitest.config.ts create mode 100644 packages/services/service-queue/vitest.config.ts create mode 100644 packages/services/service-realtime/vitest.config.ts create mode 100644 packages/services/service-sms/vitest.config.ts create mode 100644 packages/triggers/trigger-api/vitest.config.ts create mode 100644 packages/triggers/trigger-schedule/vitest.config.ts create mode 100644 packages/types/vitest.config.ts create mode 100644 packages/verify/vitest.config.ts 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 index 94986009db..ca57638fb8 100644 --- a/scripts/check-console-intercept-disarm.mjs +++ b/scripts/check-console-intercept-disarm.mjs @@ -124,6 +124,60 @@ const VITEST_CONFIG_NAMES = [ 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|$)/; @@ -226,6 +280,21 @@ export function scan(root) { `(EnvironmentTeardownError: [vitest-worker]: Closing rpc while ` + `"onUserConsoleLog" was pending). Add to the test block:\n${REMEDY}`, ); + continue; + } + if (PROJECTS_RE.test(masked)) { + const blocks = projectTestBlocks(masked); + const undisarmed = blocks.filter((b) => !DISARM_RE.test(b)); + if (blocks.length === 0 || undisarmed.length > 0) { + findings.push( + `${rel(root, dir)}/${configName}: defines inline projects, and ` + + `${blocks.length === 0 ? 'no project test block could be read' : `${undisarmed.length} of ${blocks.length} project test block(s) lack the setting`} ` + + `— a ROOT-level disableConsoleIntercept is INERT for project runs ` + + `(measured on vitest 4.1.10: a probe project's console write still ` + + `arrived over RPC with the root disarm in place). Put\n${REMEDY}\n` + + `inside EVERY project's own test block.`, + ); + } } } return { findings, vitestPackages }; @@ -359,6 +428,35 @@ function selfTest() { expectFindings: 0, expectVitestPackages: 0, }, + { + name: 'projects config with only a ROOT disarm fails (root is inert for projects)', + packages: { + a: { + 'package.json': TEST_MANIFEST, + 'vitest.config.ts': + `export default { test: { disableConsoleIntercept: true, projects: [\n` + + ` { test: { name: 'p1', include: ['x'] } },\n` + + ` { test: { name: 'p2', disableConsoleIntercept: true } },\n` + + `] } };\n`, + }, + }, + expectFindings: 1, + expectText: 'INERT for project runs', + }, + { + name: 'projects config disarmed in every project passes', + packages: { + a: { + 'package.json': TEST_MANIFEST, + 'vitest.config.ts': + `export default { test: { projects: [\n` + + ` { test: { name: 'p1', disableConsoleIntercept: true } },\n` + + ` { test: { name: 'p2', disableConsoleIntercept: true } },\n` + + `] } };\n`, + }, + }, + expectFindings: 0, + }, { name: 'string-guarded config: setting inside a template literal does not count', packages: {