From dc04fea749161c2ebdac94fff908de3dca21df61 Mon Sep 17 00:00:00 2001 From: imccyu Date: Wed, 17 Jun 2026 23:41:18 +0800 Subject: [PATCH] feat: one tsconfig.json and different rules --- AGENTS.md | 8 +-- docs/development.md | 9 +-- eslint.config.mjs | 6 +- examples/acp-agent/tests/acp.snapshot.ts | 4 +- .../tests/snapshot-normalize.spec.ts | 2 +- .../coding-agent/tests/coding-task.e2e.ts | 2 +- examples/coding-agent/tests/full-loop.e2e.ts | 2 +- examples/coding-agent/tests/resume.e2e.ts | 2 +- package.json | 2 +- scripts/doc-typecheck.ts | 63 ++++++++----------- tsconfig.json | 40 +++++++++++- tsconfig.test.json | 10 --- tsconfig.typecheck.json | 40 ------------ vitest.config.ts | 8 +-- vitest.e2e.config.ts | 2 +- vitest.snapshot.config.ts | 2 +- 16 files changed, 89 insertions(+), 113 deletions(-) delete mode 100644 tsconfig.test.json delete mode 100644 tsconfig.typecheck.json diff --git a/AGENTS.md b/AGENTS.md index e7a6c9e7e9..4bb814cab7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -84,8 +84,7 @@ pnpm run test:snapshot # ACP snapshot tests (examples/*/tests/**/*.snapshot.ts) pnpm run test:snapshot:record # re-record fixtures + goldens against the real # API (needs DEEPSEEK_API_KEY); accept-the-diff = re-record # (or `pnpm run test:snapshot -u` to refresh goldens only) -pnpm run typecheck # tsc -b tsconfig.build.json (declarations) + tsc -p - # tsconfig.typecheck.json (tests/examples typecheck too) +pnpm run typecheck # tsc -b tsconfig.json pnpm run lint # eslint . pnpm run lint:fix # eslint . --fix pnpm run build # tsc emits lib/typings, then tsdown bundles runtime lib/index.* @@ -98,7 +97,8 @@ pnpm run verify-event-taxonomy # assert the event-taxonomy table in docs/archit # matches the interface Events declarations in source pnpm run verify-md-wrap # assert no hard-wrapped prose paragraphs in README.md, # docs/**/*.md, packages/*/README.md, AGENTS.md (one line per paragraph) -pnpm run doc-sync # doc-typecheck + verify-event-taxonomy + verify-md-wrap (CI runs this) +pnpm run verify-md-links # assert relative Markdown links resolve in checked docs +pnpm run doc-sync # doc-typecheck + verify-event-taxonomy + verify-md-wrap + verify-md-links (CI runs this) pnpm run demo:echo # run examples/echo-agent (no API key; type "echo hi" to # see a tool call) — the mock skeleton pnpm run demo:coding # run examples/coding-agent — the real agent (needs @@ -121,7 +121,7 @@ cordis.yml configs reference env vars with the `!!js` tag: `apiKey: !!js process **Lean on with-key e2e tests — we are DeepSeek and model inference is cheap.** A no-key test (mock adapter, or an operation that never reaches the model) is great for determinism and CI, but it can only prove the plumbing, not that the agent actually *works* against a real model. Do not ration real-API tests to save tokens: write many of them, cover the real flows (a real prompt that writes a file, a multi-turn conversation, tool use, cancellation mid-stream), and run them frequently while developing — locally and whenever you have a key in the environment. **Especially smoke tests**: a cheap with-key smoke test that boots the real example, sends one real prompt, and checks the world (a file on disk, a non-empty assistant turn) catches whole classes of "green unit tests, broken product" failures that mocks structurally cannot — the very gap that let the ACP inject bug ship (see [docs/postmortem/0001](docs/postmortem/0001-acp-default-export-drops-inject.md)). The self-skip rule is ONLY so CI (which has no secrets) stays green and so a contributor without a key isn't blocked — it is not a signal that real-API tests are expensive or second-class. When in doubt, add the with-key test AND run it. -Dev/test/demo run **unbuilt** via tsx + the `paths` map in the root `tsconfig.json` (`vitest` resolves through `tsconfig.test.json`). Building is only needed for publishing/consumption outside the repo — with one exception: `pnpm run lint`'s type-aware rules resolve vendor packages through their built declarations (`tsconfig.typecheck.json` → `vendor/*/lib`), so run `pnpm run typecheck` once after a fresh clone (CI does the same) or lint reports unresolved-type `no-unsafe-*` errors. +Dev/test/demo run **unbuilt** via tsx + the source `paths` map in the root `tsconfig.json` (`vitest` resolves through that same root config). Building is only needed for publishing/consumption outside the repo. Non-published code (`examples`, tests, and scripts) is checked by root `tsconfig.json`, which sets `noEmit` and references the package/vendor graph so those sources stay checked under their own tsconfig boundaries. ## Conventions diff --git a/docs/development.md b/docs/development.md index ff81ac4079..700b25251d 100644 --- a/docs/development.md +++ b/docs/development.md @@ -31,7 +31,7 @@ Run typecheck once after a fresh clone: pnpm run typecheck ``` -That first typecheck builds declaration output used by type-aware linting for vendored packages. Without it, `pnpm run lint` can report unresolved-type `no-unsafe-*` errors even when source code is fine. +That first typecheck runs the package/vendor build graph and the root no-emit `tsconfig.json` graph for examples, tests, and scripts. The root graph uses the same source `paths` map but relies on project references so vendored code is checked under its own tsconfig settings. If you are preparing to push from a fresh clone or worktree, also build once: @@ -89,20 +89,21 @@ Use these from the repo root: pnpm run test # unit tests pnpm run test:coverage # unit tests with per-file coverage gates pnpm run test:e2e # real-API tests; self-skips without DEEPSEEK_API_KEY -pnpm run typecheck # build declarations, then typecheck source, tests, and examples +pnpm run typecheck # build package/vendor outputs, then typecheck examples, tests, and scripts pnpm run lint # eslint . pnpm run lint:fix # eslint . --fix pnpm run doc-typecheck # compile checked TypeScript snippets in Markdown docs pnpm run verify-event-taxonomy # compare docs/architecture.md event names with source pnpm run verify-md-wrap # fail on hard-wrapped prose paragraphs in docs/README markdown -pnpm run doc-sync # doc-typecheck, event taxonomy, and markdown wrap verification +pnpm run verify-md-links # fail on broken relative Markdown links in checked docs +pnpm run doc-sync # doc-typecheck, event taxonomy, markdown wrap, and link verification pnpm run gen-module-graph # regenerate docs/module-graph.md from package peerDeps pnpm run verify-module-graph # fail if docs/module-graph.md is stale pnpm run build # emit lib/typings intermediates, then bundle lib/index.* runtime files pnpm run hygiene # knip, publint, and workspace constraints ``` -When changing package public behavior, update the relevant README or JSDoc in the same change. `pnpm run doc-sync` catches checked TypeScript snippets, event-taxonomy drift, and hard-wrapped markdown prose, but broader prose/API sync still needs review. +When changing package public behavior, update the relevant README or JSDoc in the same change. `pnpm run doc-sync` catches checked TypeScript snippets, event-taxonomy drift, hard-wrapped markdown prose, and broken relative Markdown links, but broader prose/API sync still needs review. ## Demos diff --git a/eslint.config.mjs b/eslint.config.mjs index a4f48af798..6e73e9418b 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -36,7 +36,7 @@ export default tseslint.config( ], languageOptions: { parserOptions: { - project: ['./tsconfig.typecheck.json'], + project: ['./packages/*/tsconfig.json', './tsconfig.json'], tsconfigRootDir: import.meta.dirname, }, }, @@ -81,13 +81,13 @@ export default tseslint.config( // --- tests: same rules, minus the friction that fights test ergonomics -- { - files: ['packages/*/tests/**/*.ts'], + files: ['packages/*/tests/**/*.ts', 'examples/*/tests/**/*.ts'], extends: [ ...tseslint.configs.strictTypeChecked, ], languageOptions: { parserOptions: { - project: ['./tsconfig.typecheck.json'], + project: ['./tsconfig.json'], tsconfigRootDir: import.meta.dirname, }, }, diff --git a/examples/acp-agent/tests/acp.snapshot.ts b/examples/acp-agent/tests/acp.snapshot.ts index 9f99ce9913..106586d099 100644 --- a/examples/acp-agent/tests/acp.snapshot.ts +++ b/examples/acp-agent/tests/acp.snapshot.ts @@ -3,8 +3,8 @@ import { existsSync } from 'node:fs' import { fileURLToPath } from 'node:url' import { dirname, join } from 'node:path' import { describe, expect, it } from 'vitest' -import { type InputScript, runScenario } from './snapshot-harness.ts' -import { type NormalizeContext, normalizeSessionLog, normalizeStdout } from './snapshot-normalize.ts' +import { type InputScript, runScenario } from './snapshot-harness' +import { type NormalizeContext, normalizeSessionLog, normalizeStdout } from './snapshot-normalize' /** * ACP snapshot tests (REPLAY by default, keyless). Each scenario under diff --git a/examples/acp-agent/tests/snapshot-normalize.spec.ts b/examples/acp-agent/tests/snapshot-normalize.spec.ts index bfdeab5dc8..33c1cabaea 100644 --- a/examples/acp-agent/tests/snapshot-normalize.spec.ts +++ b/examples/acp-agent/tests/snapshot-normalize.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { type NormalizeContext, normalizeSessionLog, normalizeStdout } from '../tests/snapshot-normalize.ts' +import { type NormalizeContext, normalizeSessionLog, normalizeStdout } from '../tests/snapshot-normalize' /** * Unit tests for the pure snapshot normalizers. Live as a *.spec.ts (runs in diff --git a/examples/coding-agent/tests/coding-task.e2e.ts b/examples/coding-agent/tests/coding-task.e2e.ts index 4684301725..e8a34950cb 100644 --- a/examples/coding-agent/tests/coding-task.e2e.ts +++ b/examples/coding-agent/tests/coding-task.e2e.ts @@ -4,7 +4,7 @@ import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, describe, expect, it } from 'vitest' import type { Context } from 'cordis' -import { codingHarness, finalText, SYSTEM_PROMPT, waitForIdle } from './harness.ts' +import { codingHarness, finalText, SYSTEM_PROMPT, waitForIdle } from './harness' /** * The swebench-style smoke test: a real model fixes a real bug in a temp diff --git a/examples/coding-agent/tests/full-loop.e2e.ts b/examples/coding-agent/tests/full-loop.e2e.ts index 93bc0b1fac..e73ae25236 100644 --- a/examples/coding-agent/tests/full-loop.e2e.ts +++ b/examples/coding-agent/tests/full-loop.e2e.ts @@ -1,6 +1,6 @@ import { afterEach, describe, expect, it } from 'vitest' import type { Context } from 'cordis' -import { codingHarness, finalText, SYSTEM_PROMPT, waitForIdle } from './harness.ts' +import { codingHarness, finalText, SYSTEM_PROMPT, waitForIdle } from './harness' /** * The first place a REAL model meets the REAL bash tool: the cheap canary diff --git a/examples/coding-agent/tests/resume.e2e.ts b/examples/coding-agent/tests/resume.e2e.ts index b720efa8c9..b5b7898830 100644 --- a/examples/coding-agent/tests/resume.e2e.ts +++ b/examples/coding-agent/tests/resume.e2e.ts @@ -4,7 +4,7 @@ import { join } from 'node:path' import { afterEach, describe, expect, it } from 'vitest' import type { Context } from 'cordis' import type { ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop' -import { codingHarness, finalText, SYSTEM_PROMPT, waitForIdle } from './harness.ts' +import { codingHarness, finalText, SYSTEM_PROMPT, waitForIdle } from './harness' /** * Proves durable conversation continuity end-to-end: run 1 tells the REAL model diff --git a/package.json b/package.json index f8f79ee63e..a1394cb9e5 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "scripts": { "build": "tsc -b tsconfig.build.json && tsdown", "clean:build": "rm -rf .typecheck packages/*/lib vendor/*/lib *.tsbuildinfo", - "typecheck": "tsc -b tsconfig.build.json && tsc -p tsconfig.typecheck.json", + "typecheck": "tsc -b tsconfig.json", "lint": "eslint .", "lint:fix": "eslint . --fix", "test": "vitest run", diff --git a/scripts/doc-typecheck.ts b/scripts/doc-typecheck.ts index 10068eb792..da86488e04 100644 --- a/scripts/doc-typecheck.ts +++ b/scripts/doc-typecheck.ts @@ -3,12 +3,12 @@ * Markdown so documentation can't drift from the API it documents. * * Every ```ts block in README.md, docs/** and packages/* /README.md is - * extracted to a temp file and compiled with `tsc --noEmit` against the - * workspace sources (resolved through the same `paths` map vitest uses, so no - * build is required first). A block that is a deliberate sketch rather than - * compilable code opts out with an explicit ` ```ts ignore-check ` info string - * — the opt-out is visible in the source, and this script reports the ratio so - * the escape hatch can't quietly become the norm. + * extracted to a temp typecheck project and compiled against the workspace + * sources through the same project-reference boundaries used by repo + * typecheck. A block that is a deliberate sketch rather than compilable code + * opts out with an explicit ` ```ts ignore-check ` info string — the opt-out + * is visible in the source, and this script reports the ratio so the escape + * hatch can't quietly become the norm. * * Run: `tsx scripts/doc-typecheck.ts`. */ @@ -59,41 +59,27 @@ function extractBlocks(absPath: string): Block[] { return blocks } -/** - * Read the workspace `paths` map from tsconfig.typecheck.json (JSONC). This map - * resolves vendored packages to their BUILT declarations (`lib`) and harness - * packages to source (`src`) — the same resolution `pnpm run lint`/`typecheck` use. - * Resolving vendor to `lib` (not `src`) is essential: otherwise tsc type-checks - * raw vendor source and floods the run with unrelated errors. Requires the - * vendor `lib/` to exist (a fresh clone runs `pnpm run build` first; CI does too). - */ -function workspacePaths(): Record { - const raw = readFileSync(join(root, 'tsconfig.typecheck.json'), 'utf8') - // Strip // line comments and /* */ block comments so JSON.parse accepts it. - const stripped = raw - .replace(/\/\*[\s\S]*?\*\//g, '') - .replace(/(^|[^:])\/\/.*$/gm, '$1') - return (JSON.parse(stripped) as { compilerOptions: { paths: Record } }) - .compilerOptions.paths +/** Reuse the repo typecheck graph references from a temp project one directory below root. */ +function workspaceReferences(): { path: string }[] { + const raw = readFileSync(join(root, 'tsconfig.json'), 'utf8') + const { references } = JSON.parse(raw) as { references: { path: string }[] } + return references.map(({ path }) => { + const relativeToTemp = path.startsWith('./') ? `../${path.slice(2)}` : `../${path}` + return { path: relativeToTemp } + }) } -/** The standalone tsconfig for the temp project (copies base resolution, no - * composite/declaration settings that would fight `--noEmit`). */ +/** The standalone tsconfig for the temp typecheck project. */ function tempTsconfig(): string { return JSON.stringify({ + extends: '../tsconfig.json', compilerOptions: { - target: 'es2024', - module: 'esnext', - moduleResolution: 'bundler', - allowImportingTsExtensions: true, - strict: true, - noEmit: true, - skipLibCheck: true, - types: ['node'], - baseUrl: root, - ignoreDeprecations: '6.0', - paths: workspacePaths(), + noUnusedLocals: false, + noUnusedParameters: false, + tsBuildInfoFile: './tsconfig.tsbuildinfo', }, + include: ['block-*.ts'], + references: workspaceReferences(), }) } @@ -125,11 +111,12 @@ try { }) try { - execFileSync('node_modules/.bin/tsc', ['-p', join(tmp, 'tsconfig.json')], { cwd: root, stdio: 'pipe' }) + execFileSync('node_modules/.bin/tsc', ['-b', join(tmp, 'tsconfig.json')], { cwd: root, stdio: 'pipe' }) } catch (error: unknown) { - const out = (error as { stdout?: Buffer }).stdout?.toString() ?? '' + const failed = error as { stdout?: Buffer; stderr?: Buffer } + const out = `${failed.stdout?.toString() ?? ''}${failed.stderr?.toString() ?? ''}` // Rewrite "block-N.ts(line,col)" to the real "file:fenceLine" for triage. - const remapped = out.replace(/block-(\d+)\.ts\((\d+),(\d+)\)/g, (_m, idx: string, ln: string, col: string) => { + const remapped = out.replace(/(?:[^\s:()]*[/\\])?block-(\d+)\.ts\((\d+),(\d+)\)/g, (_m, idx: string, ln: string, col: string) => { const block = fileForBlock.get(`block-${idx}.ts`) if (!block) return `block-${idx}.ts(${ln},${col})` return `${block.file} (block at line ${block.line}, +${ln}:${col})` diff --git a/tsconfig.json b/tsconfig.json index 725f31659f..61268f8bf9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,4 +1,42 @@ { "extends": "./tsconfig.base.json", - "files": [] + "compilerOptions": { + "noEmit": true + }, + "include": [ + "examples/*/src/**/*.ts", + "examples/*/start.ts", + "examples/*/tests/**/*.ts", + "packages/*/tests/**/*.ts", + "scripts/**/*.ts" + ], + "references": [ + { "path": "./vendor/cosmokit" }, + { "path": "./vendor/schemastery" }, + { "path": "./vendor/cordis" }, + { "path": "./vendor/loader" }, + { "path": "./vendor/include" }, + { "path": "./vendor/group" }, + { "path": "./vendor/timer" }, + { "path": "./vendor/hmr" }, + { "path": "./vendor/logger-console" }, + { "path": "./packages/llm" }, + { "path": "./packages/session" }, + { "path": "./packages/session-persistence" }, + { "path": "./packages/session-persistence-jsonl" }, + { "path": "./packages/session-persistence-sqlite" }, + { "path": "./packages/system-prompt" }, + { "path": "./packages/agent" }, + { "path": "./packages/tools" }, + { "path": "./packages/agent-loop" }, + { "path": "./packages/bash" }, + { "path": "./packages/llm-deepseek" }, + { "path": "./packages/llm-pi-ai" }, + { "path": "./packages/bash-local" }, + { "path": "./packages/tool-bash" }, + { "path": "./packages/invariants" }, + { "path": "./packages/acp" }, + { "path": "./packages/ui-stdio" }, + { "path": "./packages/llm-replay" } + ] } diff --git a/tsconfig.test.json b/tsconfig.test.json deleted file mode 100644 index 5976d5b43c..0000000000 --- a/tsconfig.test.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "noEmit": true, - "emitDeclarationOnly": false, - "composite": false, - "types": ["node"] - }, - "include": ["vendor/*/src", "packages/*/src", "packages/*/tests", "examples"] -} diff --git a/tsconfig.typecheck.json b/tsconfig.typecheck.json deleted file mode 100644 index 77e2326775..0000000000 --- a/tsconfig.typecheck.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "extends": "./tsconfig.base.json", - "compilerOptions": { - "noEmit": true, - "emitDeclarationOnly": false, - "composite": false, - "incremental": false, - "types": ["node"], - "paths": { - "cordis": ["./vendor/cordis/lib"], - "cosmokit": ["./vendor/cosmokit/lib"], - "schemastery": ["./vendor/schemastery/lib"], - "@cordisjs/plugin-loader": ["./vendor/loader/lib"], - "@cordisjs/plugin-include": ["./vendor/include/lib"], - "@cordisjs/plugin-group": ["./vendor/group/lib"], - "@cordisjs/plugin-timer": ["./vendor/timer/lib"], - "@cordisjs/plugin-hmr": ["./vendor/hmr/lib"], - "@cordisjs/plugin-logger-console": ["./vendor/logger-console/lib/shared"], - "@deepseek-ai/dsh-llm": ["./packages/llm/src"], - "@deepseek-ai/dsh-session": ["./packages/session/src"], - "@deepseek-ai/dsh-session-persistence": ["./packages/session-persistence/src"], - "@deepseek-ai/dsh-session-persistence-jsonl": ["./packages/session-persistence-jsonl/src"], - "@deepseek-ai/dsh-session-persistence-sqlite": ["./packages/session-persistence-sqlite/src"], - "@deepseek-ai/dsh-system-prompt": ["./packages/system-prompt/src"], - "@deepseek-ai/dsh-tools": ["./packages/tools/src"], - "@deepseek-ai/dsh-agent": ["./packages/agent/src"], - "@deepseek-ai/dsh-agent-loop": ["./packages/agent-loop/src"], - "@deepseek-ai/dsh-bash": ["./packages/bash/src"], - "@deepseek-ai/dsh-llm-deepseek": ["./packages/llm-deepseek/src"], - "@deepseek-ai/dsh-llm-pi-ai": ["./packages/llm-pi-ai/src"], - "@deepseek-ai/dsh-bash-local": ["./packages/bash-local/src"], - "@deepseek-ai/dsh-tool-bash": ["./packages/tool-bash/src"], - "@deepseek-ai/dsh-invariants": ["./packages/invariants/src"], - "@deepseek-ai/dsh-acp": ["./packages/acp/src"], - "@deepseek-ai/dsh-ui-stdio": ["./packages/ui-stdio/src"], - "@deepseek-ai/dsh-llm-replay": ["./packages/llm-replay/src"] - } - }, - "include": ["packages/*/src", "packages/*/tests", "examples", "scripts"] -} diff --git a/vitest.config.ts b/vitest.config.ts index 0878477fb4..6d8608d86e 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -5,9 +5,9 @@ export default defineConfig({ // Vite ≥8 warns that this plugin can be replaced by the native (experimental) // `resolve.tsconfigPaths: true`. It cannot — keep the plugin. Tests run // unbuilt (see AGENTS.md): bare workspace names like `cordis` or - // `@deepseek-ai/dsh-llm` must resolve to src/, and the only place that - // mapping exists is the root tsconfig.json `paths` map inherited by - // tsconfig.test.json. The native option is a bare boolean: for each + // `@deepseek-ai/dsh-llm` must resolve to src/, and that mapping comes from + // the root tsconfig.json paths map. The native option is a bare boolean: + // for each // importing file it discovers the NEAREST tsconfig.json and applies that // file's own `paths`. Every workspace under packages/* and vendor/* has its // own tsconfig.json without `paths`, so native resolution maps nothing, @@ -17,7 +17,7 @@ export default defineConfig({ // 15 workspace tsconfigs — including vendor/* ones, which are pinned // upstream copies (vendor/README.md). The plugin's `projects` option // instead applies the one root map to every importer. - plugins: [tsconfigPaths({ projects: ['./tsconfig.test.json'] })], + plugins: [tsconfigPaths({ projects: ['./tsconfig.json'] })], test: { include: ['packages/*/tests/**/*.spec.ts', 'examples/*/tests/**/*.spec.ts'], coverage: { diff --git a/vitest.e2e.config.ts b/vitest.e2e.config.ts index 9316d660da..903e38cafb 100644 --- a/vitest.e2e.config.ts +++ b/vitest.e2e.config.ts @@ -22,7 +22,7 @@ try { export default defineConfig({ // Same resolution note as vitest.config.ts: bare workspace names resolve // through the root tsconfig paths map; the native option cannot do this. - plugins: [tsconfigPaths({ projects: ['./tsconfig.test.json'] })], + plugins: [tsconfigPaths({ projects: ['./tsconfig.json'] })], test: { include: ['packages/*/tests/**/*.e2e.ts', 'examples/*/tests/**/*.e2e.ts'], // Real model calls: generous timeouts, and retries for transient flakes diff --git a/vitest.snapshot.config.ts b/vitest.snapshot.config.ts index 6dc4144044..ecc8d911aa 100644 --- a/vitest.snapshot.config.ts +++ b/vitest.snapshot.config.ts @@ -24,7 +24,7 @@ if (process.env.DSH_SNAPSHOT === 'record') { export default defineConfig({ // Same resolution note as vitest.config.ts: bare workspace names resolve // through the root tsconfig paths map; the native option cannot do this. - plugins: [tsconfigPaths({ projects: ['./tsconfig.test.json'] })], + plugins: [tsconfigPaths({ projects: ['./tsconfig.json'] })], test: { include: ['examples/*/tests/**/*.snapshot.ts'], // Each test boots a subprocess; give it room, and run files one at a time