refactor(vitest): resolve every lane through the tsconfig.base.json facade

tsconfig.vitest.json is deleted; its job (a paths map applying to all
test files) is inherent in tsconfig.base.json having no include —
vite-tsconfig-paths treats that as match-all. All four vitest configs
now pin the same facade: the unit config gains a shared pathsPlugin()
helper, the web lane drops its handwritten webserver alias (apps/web
tests are covered by match-all), and the snapshot lane leaves the root
solution (which no longer carries paths) and stops resolving client
imports through package exports. tsconfig.base.json documents the
facade role and bans include/files; the CI eslint cache key hashes the
four graph tsconfigs; the eslint tests-block comment states resolution
via the solution to tsconfig.host.json.

Per missions/tsconfig-single-graph-migration.md §3.
This commit is contained in:
imccyu
2026-07-23 03:59:06 +08:00
parent 19e6f7d907
commit e1cb3da755
8 changed files with 37 additions and 48 deletions
+5 -5
View File
@@ -58,7 +58,7 @@ jobs:
- uses: actions/cache/restore@v4
with:
path: .cache/eslint
key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'tsconfig.json', 'packages/*/*/tsconfig.json', 'examples/*/tsconfig.json') }}
key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'tsconfig.json', 'tsconfig.base.json', 'tsconfig.base.client.json', 'tsconfig.host.json', 'tsconfig.client.json', 'packages/*/*/tsconfig.json', 'examples/*/tsconfig.json') }}
restore-keys: |
${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full-
@@ -173,7 +173,7 @@ jobs:
- uses: actions/cache/restore@v4
with:
path: .cache/eslint
key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'tsconfig.json', 'packages/*/*/tsconfig.json', 'examples/*/tsconfig.json') }}
key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'tsconfig.json', 'tsconfig.base.json', 'tsconfig.base.client.json', 'tsconfig.host.json', 'tsconfig.client.json', 'packages/*/*/tsconfig.json', 'examples/*/tsconfig.json') }}
restore-keys: |
${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full-
@@ -237,7 +237,7 @@ jobs:
- uses: actions/cache@v4
with:
path: .cache/eslint
key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'tsconfig.json', 'packages/*/*/tsconfig.json', 'examples/*/tsconfig.json') }}
key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'tsconfig.json', 'tsconfig.base.json', 'tsconfig.base.client.json', 'tsconfig.host.json', 'tsconfig.client.json', 'packages/*/*/tsconfig.json', 'examples/*/tsconfig.json') }}
restore-keys: |
${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full-
@@ -309,7 +309,7 @@ jobs:
- uses: actions/cache@v4
with:
path: .cache/eslint
key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'tsconfig.json', 'packages/*/*/tsconfig.json', 'examples/*/tsconfig.json') }}
key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'tsconfig.json', 'tsconfig.base.json', 'tsconfig.base.client.json', 'tsconfig.host.json', 'tsconfig.client.json', 'packages/*/*/tsconfig.json', 'examples/*/tsconfig.json') }}
restore-keys: |
${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full-
@@ -525,7 +525,7 @@ jobs:
if: matrix.platform == 'linux'
with:
path: .cache/eslint
key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'tsconfig.json', 'packages/*/*/tsconfig.json', 'examples/*/tsconfig.json') }}
key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'tsconfig.json', 'tsconfig.base.json', 'tsconfig.base.client.json', 'tsconfig.host.json', 'tsconfig.client.json', 'packages/*/*/tsconfig.json', 'examples/*/tsconfig.json') }}
restore-keys: |
${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full-
+2 -1
View File
@@ -86,7 +86,8 @@ export default tseslint.config(
languageOptions: {
parserOptions: {
// Same shared project service as the src block: test files resolve
// through the root tsconfig (its include covers every tests/ tree).
// through the root solution to tsconfig.host.json (its include covers
// every host tests/ tree).
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
+6 -2
View File
@@ -1,4 +1,7 @@
{
// Doubles as the resolution facade for vite-tsconfig-paths (vitest configs
// point here). NEVER add include/files to this file: it would leak into
// every extending package project and narrow the facade's match-all scope.
"compilerOptions": {
"target": "es2024",
"module": "esnext",
@@ -87,8 +90,9 @@
// One wildcard maps every @deepseek-ai/dsh-<name> to its source. Package
// dir names are unique across groups, so first-on-disk-wins resolution is
// unambiguous; adding a package under an existing group needs no edit
// here. The build graph's project references (tsconfig.build.json) stay
// explicit — TS project references have no wildcard form.
// here. The aggregates' project references (tsconfig.host.json /
// tsconfig.client.json) stay explicit — TS project references have no
// wildcard form.
"@deepseek-ai/dsh-host-apiproxy": ["./packages/host/apiproxy/src"],
"@deepseek-ai/dsh-host-apiproxy/client": ["./packages/host/apiproxy/src/fetch/client.ts"],
"@deepseek-ai/dsh-host-apiproxy/*": ["./packages/host/apiproxy/src/*"],
-15
View File
@@ -1,15 +0,0 @@
{
// Path-map scope for vite-tsconfig-paths in vitest only (never a tsc -b target):
// widens include to package src and .tsx so bare workspace imports resolve to source.
"extends": "./tsconfig.base.json",
"include": [
"examples/*/src/**/*.ts",
"examples/*/start.ts",
"examples/*/tests/**/*.ts",
"packages/*/*/src/**/*.ts",
"packages/*/*/src/**/*.tsx",
"packages/*/*/tests/**/*.ts",
"packages/*/*/tests/**/*.tsx",
"scripts/**/*.ts"
]
}
+9 -8
View File
@@ -1,6 +1,12 @@
import tsconfigPaths from 'vite-tsconfig-paths'
import { defineConfig } from 'vitest/config'
// Resolution facade shared by every plugin instance below: tsconfig.base.json
// has no include, which vite-tsconfig-paths treats as match-all, so its paths
// map applies to every test file. paths must win over package exports so built
// lib/ never loads a second module-singleton copy.
const pathsPlugin = (): ReturnType<typeof tsconfigPaths> => tsconfigPaths({ projects: ['./tsconfig.base.json'] })
const windowsUnsupportedPackages = process.platform === 'win32'
? [
'packages/bash/*',
@@ -40,12 +46,7 @@ const processBoundTests = [
]
export default defineConfig({
// Native path resolution reads each package's nearest tsconfig, but only the root defines
// workspace paths. Keep this plugin pinned to the root map so bare package imports resolve
// to source — with built lib/ present, manifest-exports fallthrough would load a second
// copy of module singletons. tsconfig.vitest.json widens include to .tsx specs (the root
// include stops at .ts for tsc -b; the plugin scopes applicability by include).
plugins: [tsconfigPaths({ projects: ['./tsconfig.vitest.json'] })],
plugins: [pathsPlugin()],
test: {
setupFiles: ['./scripts/test-invariants.ts'],
// .tsx: client component specs (jsdom via per-file @vitest-environment pragma).
@@ -55,7 +56,7 @@ export default defineConfig({
// for lower startup/IPC overhead; only explicit process-bound suites fork.
projects: [
{
plugins: [tsconfigPaths({ projects: ['./tsconfig.vitest.json'] })],
plugins: [pathsPlugin()],
test: {
name: 'thread-safe',
pool: 'threads',
@@ -68,7 +69,7 @@ export default defineConfig({
},
},
{
plugins: [tsconfigPaths({ projects: ['./tsconfig.vitest.json'] })],
plugins: [pathsPlugin()],
test: {
name: 'process-bound',
pool: 'forks',
+7 -7
View File
@@ -29,13 +29,13 @@ const e2eMaxWorkers = positiveIntFromEnv('DSH_E2E_MAX_WORKERS', DEFAULT_E2E_MAX_
export default defineConfig({
// Same resolution note as vitest.config.ts: bare workspace names resolve
// through the vitest-scoped tsconfig paths map (its include spans package
// src, so client-package sources get mapping too — the root tsconfig
// excludes packages/client, which would drop /client subpath imports onto
// package exports and load browser dist bundles into node). Built-artifact
// e2e suites are unaffected: their built-ness lives in subprocesses and
// createRequire lookups, which bypass vite resolution entirely.
plugins: [tsconfigPaths({ projects: ['./tsconfig.vitest.json'] })],
// through the tsconfig.base.json paths facade (no include = match-all, so
// client-package sources get mapping too — dropping /client subpath imports
// onto package exports would load browser dist bundles into node).
// Built-artifact e2e suites are unaffected: their built-ness lives in
// subprocesses and createRequire lookups, which bypass vite resolution
// entirely.
plugins: [tsconfigPaths({ projects: ['./tsconfig.base.json'] })],
test: {
setupFiles: ['./scripts/test-invariants.ts'],
include: ['packages/*/*/tests/**/*.e2e.ts', 'examples/*/tests/**/*.e2e.ts'],
+3 -2
View File
@@ -36,8 +36,9 @@ 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.json'] })],
// through the tsconfig.base.json paths facade; the native option cannot do
// this (the root tsconfig is a solution file with no paths).
plugins: [tsconfigPaths({ projects: ['./tsconfig.base.json'] })],
test: {
setupFiles: ['./scripts/test-invariants.ts'],
include: [
+5 -8
View File
@@ -1,4 +1,4 @@
import { fileURLToPath } from 'node:url'
import tsconfigPaths from 'vite-tsconfig-paths'
import { defineConfig } from 'vitest/config'
// Web smoke lane (GUI, gate-exempt — not part of the CI sequence yet): built
@@ -13,13 +13,10 @@ try {
}
export default defineConfig({
resolve: {
alias: {
// apps/web/tests is outside the root tsconfig include, so the shared
// tsconfig-paths plugin never maps it; alias the one bare import to source.
'@deepseek-ai/dsh-host-webserver': fileURLToPath(new URL('packages/host/webserver/src/index.ts', import.meta.url)),
},
},
// Same resolution note as vitest.config.ts: the tsconfig.base.json paths
// facade has no include (match-all), so apps/web/tests resolves bare
// workspace imports to source like every other lane.
plugins: [tsconfigPaths({ projects: ['./tsconfig.base.json'] })],
test: {
include: ['apps/web/tests/**/*.e2e.ts'],
// Browser boot + real-model turns are slow; files share one browser, run serial.