Ver Fonte

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.
imccyu há 1 mês atrás
pai
commit
e1cb3da755

+ 5 - 5
.github/workflows/ci.yml

@@ -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
eslint.config.mjs

@@ -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
tsconfig.base.json

@@ -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/*"],

+ 0 - 15
tsconfig.vitest.json

@@ -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
vitest.config.ts

@@ -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
vitest.e2e.config.ts

@@ -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
vitest.snapshot.config.ts

@@ -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
vitest.web.config.ts

@@ -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.