From 4c401458da5ceef80d5c27305d0cdb586e66a46c Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Wed, 14 Oct 2026 15:20:03 +0800 Subject: [PATCH] fix(ci): track imported compiler inputs and package scopes Include imported declarations, nested Node package scopes and authored documentation helpers in compiler task ownership. Schedule static checks for documentation package scopes. Three real cold/warm compiler invalidation cases passed under bounded Flameox captures; the complete matrix remains CI verification. Fixes #3164 Co-authored-by: N0zoM1z0 <161784452+N0zoM1z0@users.noreply.github.com> --- scripts/ci/plan.mjs | 7 +- .../boundary/filesystem/ciChangeScope.test.ts | 9 + .../filesystem/staticTaskCache.test.ts | 184 +++++++++++++++++- turbo.json | 17 +- 4 files changed, 208 insertions(+), 9 deletions(-) diff --git a/scripts/ci/plan.mjs b/scripts/ci/plan.mjs index 2d68d60c73..4b7d315cfc 100644 --- a/scripts/ci/plan.mjs +++ b/scripts/ci/plan.mjs @@ -175,8 +175,11 @@ if (tier === "reused") { file.startsWith(".github/ISSUE_TEMPLATE/") ) { selected.docs = true; - // Authored documentation code also belongs to the static checkers. - selected.code ||= /^docs\/.*\.(?:[cm]?[jt]s|[jt]sx)$/su.test(file); + // Authored code and its Node package scopes belong to static checkers. + selected.code ||= + file.startsWith("docs/") && + (/\.(?:[cm]?[jt]s|[jt]sx)$/u.test(file) || + file.endsWith("/package.json")); selected.website ||= matchesAny(file, SCOPES.website); continue; } diff --git a/tests/boundary/filesystem/ciChangeScope.test.ts b/tests/boundary/filesystem/ciChangeScope.test.ts index 3ea3e21735..968da5aa61 100644 --- a/tests/boundary/filesystem/ciChangeScope.test.ts +++ b/tests/boundary/filesystem/ciChangeScope.test.ts @@ -983,6 +983,15 @@ it.each([ { code: true, package: true, browser: true, evm: true }, ], ["docs/.vitepress/config.ts", { code: true, docs: true, package: false }], + [ + "docs/package.json", + { code: true, docs: true, runtime: false, package: false }, + ], + [ + "docs/.vitepress/package.json", + { code: true, docs: true, runtime: false, package: false }, + ], + ["docs/data.json", { code: false, docs: true, package: false }], ["CHANGELOG.md", { code: false, docs: true, package: false }], ["SECURITY.md", { code: false, docs: true, package: false }], [".github/pull_request_template.md", { code: false, docs: true }], diff --git a/tests/boundary/filesystem/staticTaskCache.test.ts b/tests/boundary/filesystem/staticTaskCache.test.ts index 2529f3bf42..374d354000 100644 --- a/tests/boundary/filesystem/staticTaskCache.test.ts +++ b/tests/boundary/filesystem/staticTaskCache.test.ts @@ -39,14 +39,14 @@ const writeTaskFixture = async ( root: string, taskNames: string[], scripts: Record, + imports?: Record, ) => { const config = await readRootConfig(); const tasks = Object.fromEntries( taskNames.map((name) => { const task = config.tasks[name]; if (task === undefined) throw new Error(`Missing Turbo task ${name}`); - // These tests measure a task's own inputs. Drop dependency edges so the - // unrelated compile task cannot add fixture-specific inputs or outputs. + // Measure each task's own input ownership without dependency hashes. const { dependsOn: _dependsOn, ...definition } = task; return [name, definition]; }), @@ -73,8 +73,10 @@ const writeTaskFixture = async ( name: "rea-static-task-cache-fixture", version: "1.0.0", private: true, + type: "module", packageManager: "npm@11.16.0", scripts, + ...(imports === undefined ? {} : { imports }), }), ); await writeFile( @@ -201,6 +203,184 @@ it("restores a real lint report across cold, warm, and fresh consumer checkouts" ); }); +it.each([ + { + label: "imported documentation helper", + path: "docs/.vitepress/helper.ts", + content: 'export const value: number = "invalid";\n', + diagnostic: "TS2322", + invalidatesCompile: false, + }, + { + label: "documentation package scope", + path: "docs/package.json", + content: '{"type":"commonjs"}\n', + diagnostic: "CommonJS", + invalidatesCompile: false, + }, + { + label: "source package scope", + path: "src/package.json", + content: '{"type":"commonjs"}\n', + diagnostic: "CommonJS", + invalidatesCompile: true, + }, + { + label: "test package scope", + path: "tests/package.json", + content: '{"type":"commonjs"}\n', + diagnostic: "CommonJS", + invalidatesCompile: false, + }, + { + label: "declaration package scope", + path: "scripts/package.json", + content: '{"imports":{"#compiler-scope":"./shadow-types.d.mts"}}\n', + diagnostic: "TS2322", + invalidatesCompile: false, + }, + { + label: "source declaration", + path: "src/types.d.mts", + content: "export type SourceValue = 2;\n", + diagnostic: "TS2322", + invalidatesCompile: true, + }, + { + label: "test declaration", + path: "tests/types.d.cts", + content: 'export type TestValue = "shadow";\n', + diagnostic: "TS2322", + invalidatesCompile: false, + }, + { + label: "script declaration", + path: "scripts/value.d.cts", + content: 'export type ScriptValue = "shadow";\n', + diagnostic: "TS2322", + invalidatesCompile: false, + }, +])( + "rejects a changed $label after warming real compiler tasks", + async ({ path, content, diagnostic, invalidatesCompile }) => { + const root = await createTestTempDirectory("rea-compiler-cache-inputs-"); + const cache = await createTestTempDirectory("rea-compiler-cache-store-"); + await mkdir(join(root, "docs/.vitepress"), { recursive: true }); + for (const directory of ["src", "tests", "scripts"]) + await mkdir(join(root, directory)); + await symlink( + join(repository, "node_modules"), + join(root, "node_modules"), + "junction", + ); + await writeTaskFixture( + root, + ["compile", "typecheck"], + { + // Exercise the real compiler phase with the production task inputs. + compile: "tsc -p tsconfig.build.json", + typecheck: "tsc --noEmit", + }, + { "#compiler-scope": "./scripts/root-types.d.mts" }, + ); + for (const name of [ + "tsconfig.json", + "tsconfig.build.json", + ".nvmrc", + ".npmrc", + ]) + await writeFile(join(root, name), await readFile(join(repository, name))); + await writeFile( + join(root, ".gitignore"), + "node_modules/\n.cache/\n.turbo/\ndist/\n", + ); + await writeFile( + join(root, "src/main.ts"), + 'import type { SourceValue } from "./types.mjs";\nexport const value: SourceValue = 1;\n', + ); + await writeFile( + join(root, "src/types.d.mts"), + "export type SourceValue = 1;\n", + ); + await writeFile( + join(root, "docs/.vitepress/helper.ts"), + "export const value: number = 1;\n", + ); + await writeFile( + join(root, "docs/.vitepress/config.ts"), + 'import { value } from "./helper.js";\nexport const configValue: number = value;\n', + ); + await writeFile( + join(root, "scripts/root-types.d.mts"), + 'export type Scope = "root";\n', + ); + await writeFile( + join(root, "scripts/shadow-types.d.mts"), + 'export type Scope = "shadow";\n', + ); + await writeFile( + join(root, "scripts/api.d.mts"), + 'import type { Scope } from "#compiler-scope";\nexport type ApiScope = Scope;\n', + ); + await writeFile( + join(root, "tests/use-script.ts"), + 'import type { ApiScope } from "../scripts/api.mjs";\nimport type { TestValue } from "./types.cjs";\nimport type { ScriptValue } from "../scripts/value.cjs";\nexport const scope: ApiScope = "root";\nexport const testValue: TestValue = "root";\nexport const scriptValue: ScriptValue = "root";\n', + ); + await writeFile( + join(root, "tests/types.d.cts"), + 'export type TestValue = "root";\n', + ); + await writeFile( + join(root, "scripts/value.d.cts"), + 'export type ScriptValue = "root";\n', + ); + await workflowGit(root, ["init", "--initial-branch=main"]); + await workflowGit(root, ["add", "."]); + await workflowGit(root, ["commit", "-m", "test: seed compiler cache"]); + const tasks = ["compile", "typecheck"]; + const hashes = new Map(); + for (const task of tasks) { + const cold = runTurbo(root, task, cache); + expect(cold.error).toBeUndefined(); + expect(cold.status, cold.stdout + cold.stderr).toBe(0); + expect(cold.stdout).toMatch(/Cached:\s+0 cached/u); + hashes.set(task, taskHash(root, task, cache)); + } + await writeFile( + join(root, "docs/guide.md"), + "# Documentation-only change\n", + ); + for (const directory of ["cache", "dist"]) { + await mkdir(join(root, "docs/.vitepress", directory)); + await writeFile( + join(root, "docs/.vitepress", directory, "generated.ts"), + "export const generated = 1;\n", + ); + } + for (const task of tasks) { + expect(taskHash(root, task, cache)).toBe(hashes.get(task)); + const warm = runTurbo(root, task, cache); + expect(warm.status, warm.stdout + warm.stderr).toBe(0); + expect(warm.stdout).toMatch(/Cached:\s+1 cached/u); + } + await writeFile(join(root, path), content); + for (const task of tasks) { + const invalidates = task === "typecheck" || invalidatesCompile; + const result = runTurbo(root, task, cache); + expect(result.error).toBeUndefined(); + if (invalidates) { + expect(result.status, result.stdout + result.stderr).toBeGreaterThan(0); + expect(result.stdout + result.stderr).toContain(diagnostic); + expect(taskHash(root, task, cache)).not.toBe(hashes.get(task)); + } else { + expect(result.status, result.stdout + result.stderr).toBe(0); + expect(result.stdout).toMatch(/Cached:\s+1 cached/u); + expect(taskHash(root, task, cache)).toBe(hashes.get(task)); + } + } + }, +); + it("invalidates checker task hashes for Node, ignore, and VitePress config changes", async () => { const root = await createTestTempDirectory("rea-static-cache-inputs-"); const cache = await createTestTempDirectory("rea-static-cache-hashes-"); diff --git a/turbo.json b/turbo.json index 35e7ece044..8476c9ba0e 100644 --- a/turbo.json +++ b/turbo.json @@ -14,7 +14,8 @@ "scripts/clean-build-output.mjs", "scripts/bundle-runtime.mjs", "scripts/generate-package-metadata.mjs", - "src/**/*.ts", + "src/**/*.{cts,mts,ts,tsx}", + "src/**/package.json", "!src/**/*.test.ts", "!src/**/*.fixture.ts", "tsconfig.json", @@ -26,13 +27,19 @@ "inputs": [ "package.json", "package-lock.json", - "src/**/*.ts", - "tests/**/*.ts", + "src/**/*.{cts,mts,ts,tsx}", + "src/**/package.json", + "tests/**/*.{cts,mts,ts,tsx}", + "tests/**/package.json", "tsconfig.json", "tsconfig.build.json", "vitest.config.ts", - "scripts/**/*.d.mts", - "docs/.vitepress/config.ts" + "scripts/**/*.d.{cts,mts,ts}", + "scripts/**/package.json", + "docs/**/*.{cjs,cts,js,jsx,mjs,mts,ts,tsx}", + "docs/**/package.json", + "!docs/.vitepress/cache/**", + "!docs/.vitepress/dist/**" ], "outputs": [] },