@ttt-productions/repo-guards 0.1.10 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,6 +11,7 @@ import { type SecretBindingFacts } from './secret-bindings.js';
11
11
  import { type SharedFakeFacts } from './shared-fakes.js';
12
12
  import { type ShellSafeFacts } from './shell-safe.js';
13
13
  import { type TestCaseModuleLoadFacts } from './test-case-module-loads.js';
14
+ import { type TestTimeoutFacts } from './test-timeouts.js';
14
15
  import { type TestSourceRootFacts } from './test-source-root.js';
15
16
  import { type WalkerReadFailureFacts } from './walker-read-failure.js';
16
17
  import { type WorkerBoundedBodyFacts } from './worker-bounded-body.js';
@@ -22,6 +23,7 @@ export interface RepoGuardFacts {
22
23
  testSourceRoot: TestSourceRootFacts | Absent;
23
24
  testCaseModuleLoads: TestCaseModuleLoadFacts;
24
25
  sharedFakes: SharedFakeFacts;
26
+ testTimeouts: TestTimeoutFacts;
25
27
  localOnlyVariables: LocalOnlyVariablesFacts;
26
28
  workerConfigIsolation: WorkerConfigFacts | Absent;
27
29
  workerBoundedBody: WorkerBoundedBodyFacts | Absent;
@@ -1 +1 @@
1
- {"version":3,"file":"guards.d.ts","sourceRoot":"","sources":["../../../src/modules/repo/guards.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACjF,OAAO,EAKL,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACxB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAiB,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACnE,OAAO,EAA2B,KAAK,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAClG,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAClF,OAAO,EAAwB,KAAK,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACzF,OAAO,EAEL,KAAK,8BAA8B,EACpC,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAA2B,KAAK,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAClG,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AACnF,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AACnF,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC1E,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtE,OAAO,EAA2B,KAAK,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AACpG,OAAO,EAAuB,KAAK,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACtF,OAAO,EAA0B,KAAK,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAC/F,OAAO,EAA0B,KAAK,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAC/F,OAAO,EAAqB,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE/E,0DAA0D;AAC1D,MAAM,WAAW,cAAc;IAC7B,uBAAuB,EAAE,cAAc,CAAC;IACxC,iBAAiB,EAAE,sBAAsB,CAAC;IAC1C,cAAc,EAAE,mBAAmB,GAAG,MAAM,CAAC;IAC7C,mBAAmB,EAAE,uBAAuB,CAAC;IAC7C,WAAW,EAAE,eAAe,CAAC;IAC7B,kBAAkB,EAAE,uBAAuB,CAAC;IAC5C,qBAAqB,EAAE,iBAAiB,GAAG,MAAM,CAAC;IAClD,iBAAiB,EAAE,sBAAsB,GAAG,MAAM,CAAC;IACnD,aAAa,EAAE,kBAAkB,CAAC;IAClC,qBAAqB,EAAE,kBAAkB,CAAC;IAC1C,kBAAkB,EAAE,eAAe,CAAC;IACpC,iBAAiB,EAAE,sBAAsB,CAAC;IAC1C,cAAc,EAAE,kBAAkB,GAAG,MAAM,CAAC;IAC5C,cAAc,EAAE,kBAAkB,GAAG,MAAM,CAAC;IAC5C,aAAa,EAAE,kBAAkB,CAAC;IAClC,eAAe,EAAE,oBAAoB,GAAG,MAAM,CAAC;IAC/C,yBAAyB,EAAE,8BAA8B,CAAC;IAC1D,QAAQ,EAAE,aAAa,GAAG,MAAM,CAAC;IACjC,mBAAmB,EAAE,uBAAuB,GAAG,MAAM,CAAC;CACvD;AAED,eAAO,MAAM,WAAW,EAAE,aAAa,CAAC,sBAAsB,CAAC,KAAK,CAAC,CAoBnB,CAAC"}
1
+ {"version":3,"file":"guards.d.ts","sourceRoot":"","sources":["../../../src/modules/repo/guards.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACjF,OAAO,EAKL,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACxB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAiB,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACnE,OAAO,EAA2B,KAAK,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAClG,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAClF,OAAO,EAAwB,KAAK,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACzF,OAAO,EAEL,KAAK,8BAA8B,EACpC,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAA2B,KAAK,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAClG,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AACnF,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AACnF,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC1E,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtE,OAAO,EAA2B,KAAK,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AACpG,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC7E,OAAO,EAAuB,KAAK,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACtF,OAAO,EAA0B,KAAK,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAC/F,OAAO,EAA0B,KAAK,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAC/F,OAAO,EAAqB,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE/E,0DAA0D;AAC1D,MAAM,WAAW,cAAc;IAC7B,uBAAuB,EAAE,cAAc,CAAC;IACxC,iBAAiB,EAAE,sBAAsB,CAAC;IAC1C,cAAc,EAAE,mBAAmB,GAAG,MAAM,CAAC;IAC7C,mBAAmB,EAAE,uBAAuB,CAAC;IAC7C,WAAW,EAAE,eAAe,CAAC;IAC7B,YAAY,EAAE,gBAAgB,CAAC;IAC/B,kBAAkB,EAAE,uBAAuB,CAAC;IAC5C,qBAAqB,EAAE,iBAAiB,GAAG,MAAM,CAAC;IAClD,iBAAiB,EAAE,sBAAsB,GAAG,MAAM,CAAC;IACnD,aAAa,EAAE,kBAAkB,CAAC;IAClC,qBAAqB,EAAE,kBAAkB,CAAC;IAC1C,kBAAkB,EAAE,eAAe,CAAC;IACpC,iBAAiB,EAAE,sBAAsB,CAAC;IAC1C,cAAc,EAAE,kBAAkB,GAAG,MAAM,CAAC;IAC5C,cAAc,EAAE,kBAAkB,GAAG,MAAM,CAAC;IAC5C,aAAa,EAAE,kBAAkB,CAAC;IAClC,eAAe,EAAE,oBAAoB,GAAG,MAAM,CAAC;IAC/C,yBAAyB,EAAE,8BAA8B,CAAC;IAC1D,QAAQ,EAAE,aAAa,GAAG,MAAM,CAAC;IACjC,mBAAmB,EAAE,uBAAuB,GAAG,MAAM,CAAC;CACvD;AAED,eAAO,MAAM,WAAW,EAAE,aAAa,CAAC,sBAAsB,CAAC,KAAK,CAAC,CAqBnB,CAAC"}
@@ -12,6 +12,7 @@ import { secretBindingGuard } from './secret-bindings.js';
12
12
  import { sharedFakeGuard } from './shared-fakes.js';
13
13
  import { shellSafeGuard } from './shell-safe.js';
14
14
  import { testCaseModuleLoadGuard } from './test-case-module-loads.js';
15
+ import { testTimeoutGuard } from './test-timeouts.js';
15
16
  import { testSourceRootGuard } from './test-source-root.js';
16
17
  import { walkerReadFailureGuard } from './walker-read-failure.js';
17
18
  import { workerBoundedBodyGuard } from './worker-bounded-body.js';
@@ -22,6 +23,7 @@ export const REPO_GUARDS = Object.freeze([
22
23
  testSourceRootGuard,
23
24
  testCaseModuleLoadGuard,
24
25
  sharedFakeGuard,
26
+ testTimeoutGuard,
25
27
  localOnlyVariablesGuard,
26
28
  workerConfigGuard,
27
29
  workerBoundedBodyGuard,
@@ -1 +1 @@
1
- {"version":3,"file":"guards.js","sourceRoot":"","sources":["../../../src/modules/repo/guards.ts"],"names":[],"mappings":"AAAA,gJAAgJ;AAChJ,iCAAiC;AAGjC,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,GAKnB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,aAAa,EAAsB,MAAM,gBAAgB,CAAC;AACnE,OAAO,EAAE,uBAAuB,EAAgC,MAAM,2BAA2B,CAAC;AAClG,OAAO,EAAE,kBAAkB,EAA2B,MAAM,qBAAqB,CAAC;AAClF,OAAO,EAAE,oBAAoB,EAA6B,MAAM,wBAAwB,CAAC;AACzF,OAAO,EACL,8BAA8B,GAE/B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,uBAAuB,EAAgC,MAAM,2BAA2B,CAAC;AAClG,OAAO,EAAE,kBAAkB,EAA2B,MAAM,sBAAsB,CAAC;AACnF,OAAO,EAAE,kBAAkB,EAA2B,MAAM,sBAAsB,CAAC;AACnF,OAAO,EAAE,eAAe,EAAwB,MAAM,mBAAmB,CAAC;AAC1E,OAAO,EAAE,cAAc,EAAuB,MAAM,iBAAiB,CAAC;AACtE,OAAO,EAAE,uBAAuB,EAAgC,MAAM,6BAA6B,CAAC;AACpG,OAAO,EAAE,mBAAmB,EAA4B,MAAM,uBAAuB,CAAC;AACtF,OAAO,EAAE,sBAAsB,EAA+B,MAAM,0BAA0B,CAAC;AAC/F,OAAO,EAAE,sBAAsB,EAA+B,MAAM,0BAA0B,CAAC;AAC/F,OAAO,EAAE,iBAAiB,EAA0B,MAAM,oBAAoB,CAAC;AAyB/E,MAAM,CAAC,MAAM,WAAW,GAAiD,MAAM,CAAC,MAAM,CAAC;IACrF,cAAc;IACd,sBAAsB;IACtB,mBAAmB;IACnB,uBAAuB;IACvB,eAAe;IACf,uBAAuB;IACvB,iBAAiB;IACjB,sBAAsB;IACtB,kBAAkB;IAClB,kBAAkB;IAClB,eAAe;IACf,sBAAsB;IACtB,kBAAkB;IAClB,kBAAkB;IAClB,kBAAkB;IAClB,oBAAoB;IACpB,8BAA8B;IAC9B,aAAa;IACb,uBAAuB;CACwB,CAAC,CAAC","sourcesContent":["// The generic guards over the repository, its supply chain, and its tooling surface. The registry runs every guard listed here; an app supplies\n// each one's facts under its id.\n\nimport type { Absent, GenericGuardDefinition } from '../../node/suite/config.js';\nimport {\n entryPointGuard,\n isolatedRootsGuard,\n noOutsideCheckoutGuard,\n tsconfigAliasGuard,\n type EntryPointFacts,\n type IsolatedRootsFacts,\n type NoOutsideCheckoutFacts,\n type TsconfigAliasFacts,\n} from './cross-root.js';\nimport { ciParityGuard, type CiParityFacts } from './ci-parity.js';\nimport { dependencyOverrideGuard, type DependencyOverrideFacts } from './dependency-overrides.js';\nimport { documentDriftGuard, type DocumentDriftFacts } from './document-drift.js';\nimport { envPublicValuesGuard, type EnvPublicValuesFacts } from './env-public-values.js';\nimport {\n installedPackagesAgnosticGuard,\n type InstalledPackagesAgnosticFacts,\n} from './installed-packages-agnostic.js';\nimport { localOnlyVariablesGuard, type LocalOnlyVariablesFacts } from './local-only-variables.js';\nimport { projectScriptGuard, type ProjectScriptFacts } from './project-scripts.js';\nimport { secretBindingGuard, type SecretBindingFacts } from './secret-bindings.js';\nimport { sharedFakeGuard, type SharedFakeFacts } from './shared-fakes.js';\nimport { shellSafeGuard, type ShellSafeFacts } from './shell-safe.js';\nimport { testCaseModuleLoadGuard, type TestCaseModuleLoadFacts } from './test-case-module-loads.js';\nimport { testSourceRootGuard, type TestSourceRootFacts } from './test-source-root.js';\nimport { walkerReadFailureGuard, type WalkerReadFailureFacts } from './walker-read-failure.js';\nimport { workerBoundedBodyGuard, type WorkerBoundedBodyFacts } from './worker-bounded-body.js';\nimport { workerConfigGuard, type WorkerConfigFacts } from './worker-config.js';\n\n/** The facts each guard in this area needs, by its id. */\nexport interface RepoGuardFacts {\n shellSafeChildProcesses: ShellSafeFacts;\n walkerReadFailure: WalkerReadFailureFacts;\n testSourceRoot: TestSourceRootFacts | Absent;\n testCaseModuleLoads: TestCaseModuleLoadFacts;\n sharedFakes: SharedFakeFacts;\n localOnlyVariables: LocalOnlyVariablesFacts;\n workerConfigIsolation: WorkerConfigFacts | Absent;\n workerBoundedBody: WorkerBoundedBodyFacts | Absent;\n isolatedRoots: IsolatedRootsFacts;\n tsconfigAliasesInRoot: TsconfigAliasFacts;\n packageEntryPoints: EntryPointFacts;\n noOutsideCheckout: NoOutsideCheckoutFacts;\n projectScripts: ProjectScriptFacts | Absent;\n secretBindings: SecretBindingFacts | Absent;\n documentDrift: DocumentDriftFacts;\n envPublicValues: EnvPublicValuesFacts | Absent;\n installedPackagesAgnostic: InstalledPackagesAgnosticFacts;\n ciParity: CiParityFacts | Absent;\n dependencyOverrides: DependencyOverrideFacts | Absent;\n}\n\nexport const REPO_GUARDS: ReadonlyArray<GenericGuardDefinition<never>> = Object.freeze([\n shellSafeGuard,\n walkerReadFailureGuard,\n testSourceRootGuard,\n testCaseModuleLoadGuard,\n sharedFakeGuard,\n localOnlyVariablesGuard,\n workerConfigGuard,\n workerBoundedBodyGuard,\n isolatedRootsGuard,\n tsconfigAliasGuard,\n entryPointGuard,\n noOutsideCheckoutGuard,\n projectScriptGuard,\n secretBindingGuard,\n documentDriftGuard,\n envPublicValuesGuard,\n installedPackagesAgnosticGuard,\n ciParityGuard,\n dependencyOverrideGuard,\n] as ReadonlyArray<GenericGuardDefinition<never>>);\n"]}
1
+ {"version":3,"file":"guards.js","sourceRoot":"","sources":["../../../src/modules/repo/guards.ts"],"names":[],"mappings":"AAAA,gJAAgJ;AAChJ,iCAAiC;AAGjC,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,GAKnB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,aAAa,EAAsB,MAAM,gBAAgB,CAAC;AACnE,OAAO,EAAE,uBAAuB,EAAgC,MAAM,2BAA2B,CAAC;AAClG,OAAO,EAAE,kBAAkB,EAA2B,MAAM,qBAAqB,CAAC;AAClF,OAAO,EAAE,oBAAoB,EAA6B,MAAM,wBAAwB,CAAC;AACzF,OAAO,EACL,8BAA8B,GAE/B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,uBAAuB,EAAgC,MAAM,2BAA2B,CAAC;AAClG,OAAO,EAAE,kBAAkB,EAA2B,MAAM,sBAAsB,CAAC;AACnF,OAAO,EAAE,kBAAkB,EAA2B,MAAM,sBAAsB,CAAC;AACnF,OAAO,EAAE,eAAe,EAAwB,MAAM,mBAAmB,CAAC;AAC1E,OAAO,EAAE,cAAc,EAAuB,MAAM,iBAAiB,CAAC;AACtE,OAAO,EAAE,uBAAuB,EAAgC,MAAM,6BAA6B,CAAC;AACpG,OAAO,EAAE,gBAAgB,EAAyB,MAAM,oBAAoB,CAAC;AAC7E,OAAO,EAAE,mBAAmB,EAA4B,MAAM,uBAAuB,CAAC;AACtF,OAAO,EAAE,sBAAsB,EAA+B,MAAM,0BAA0B,CAAC;AAC/F,OAAO,EAAE,sBAAsB,EAA+B,MAAM,0BAA0B,CAAC;AAC/F,OAAO,EAAE,iBAAiB,EAA0B,MAAM,oBAAoB,CAAC;AA0B/E,MAAM,CAAC,MAAM,WAAW,GAAiD,MAAM,CAAC,MAAM,CAAC;IACrF,cAAc;IACd,sBAAsB;IACtB,mBAAmB;IACnB,uBAAuB;IACvB,eAAe;IACf,gBAAgB;IAChB,uBAAuB;IACvB,iBAAiB;IACjB,sBAAsB;IACtB,kBAAkB;IAClB,kBAAkB;IAClB,eAAe;IACf,sBAAsB;IACtB,kBAAkB;IAClB,kBAAkB;IAClB,kBAAkB;IAClB,oBAAoB;IACpB,8BAA8B;IAC9B,aAAa;IACb,uBAAuB;CACwB,CAAC,CAAC","sourcesContent":["// The generic guards over the repository, its supply chain, and its tooling surface. The registry runs every guard listed here; an app supplies\n// each one's facts under its id.\n\nimport type { Absent, GenericGuardDefinition } from '../../node/suite/config.js';\nimport {\n entryPointGuard,\n isolatedRootsGuard,\n noOutsideCheckoutGuard,\n tsconfigAliasGuard,\n type EntryPointFacts,\n type IsolatedRootsFacts,\n type NoOutsideCheckoutFacts,\n type TsconfigAliasFacts,\n} from './cross-root.js';\nimport { ciParityGuard, type CiParityFacts } from './ci-parity.js';\nimport { dependencyOverrideGuard, type DependencyOverrideFacts } from './dependency-overrides.js';\nimport { documentDriftGuard, type DocumentDriftFacts } from './document-drift.js';\nimport { envPublicValuesGuard, type EnvPublicValuesFacts } from './env-public-values.js';\nimport {\n installedPackagesAgnosticGuard,\n type InstalledPackagesAgnosticFacts,\n} from './installed-packages-agnostic.js';\nimport { localOnlyVariablesGuard, type LocalOnlyVariablesFacts } from './local-only-variables.js';\nimport { projectScriptGuard, type ProjectScriptFacts } from './project-scripts.js';\nimport { secretBindingGuard, type SecretBindingFacts } from './secret-bindings.js';\nimport { sharedFakeGuard, type SharedFakeFacts } from './shared-fakes.js';\nimport { shellSafeGuard, type ShellSafeFacts } from './shell-safe.js';\nimport { testCaseModuleLoadGuard, type TestCaseModuleLoadFacts } from './test-case-module-loads.js';\nimport { testTimeoutGuard, type TestTimeoutFacts } from './test-timeouts.js';\nimport { testSourceRootGuard, type TestSourceRootFacts } from './test-source-root.js';\nimport { walkerReadFailureGuard, type WalkerReadFailureFacts } from './walker-read-failure.js';\nimport { workerBoundedBodyGuard, type WorkerBoundedBodyFacts } from './worker-bounded-body.js';\nimport { workerConfigGuard, type WorkerConfigFacts } from './worker-config.js';\n\n/** The facts each guard in this area needs, by its id. */\nexport interface RepoGuardFacts {\n shellSafeChildProcesses: ShellSafeFacts;\n walkerReadFailure: WalkerReadFailureFacts;\n testSourceRoot: TestSourceRootFacts | Absent;\n testCaseModuleLoads: TestCaseModuleLoadFacts;\n sharedFakes: SharedFakeFacts;\n testTimeouts: TestTimeoutFacts;\n localOnlyVariables: LocalOnlyVariablesFacts;\n workerConfigIsolation: WorkerConfigFacts | Absent;\n workerBoundedBody: WorkerBoundedBodyFacts | Absent;\n isolatedRoots: IsolatedRootsFacts;\n tsconfigAliasesInRoot: TsconfigAliasFacts;\n packageEntryPoints: EntryPointFacts;\n noOutsideCheckout: NoOutsideCheckoutFacts;\n projectScripts: ProjectScriptFacts | Absent;\n secretBindings: SecretBindingFacts | Absent;\n documentDrift: DocumentDriftFacts;\n envPublicValues: EnvPublicValuesFacts | Absent;\n installedPackagesAgnostic: InstalledPackagesAgnosticFacts;\n ciParity: CiParityFacts | Absent;\n dependencyOverrides: DependencyOverrideFacts | Absent;\n}\n\nexport const REPO_GUARDS: ReadonlyArray<GenericGuardDefinition<never>> = Object.freeze([\n shellSafeGuard,\n walkerReadFailureGuard,\n testSourceRootGuard,\n testCaseModuleLoadGuard,\n sharedFakeGuard,\n testTimeoutGuard,\n localOnlyVariablesGuard,\n workerConfigGuard,\n workerBoundedBodyGuard,\n isolatedRootsGuard,\n tsconfigAliasGuard,\n entryPointGuard,\n noOutsideCheckoutGuard,\n projectScriptGuard,\n secretBindingGuard,\n documentDriftGuard,\n envPublicValuesGuard,\n installedPackagesAgnosticGuard,\n ciParityGuard,\n dependencyOverrideGuard,\n] as ReadonlyArray<GenericGuardDefinition<never>>);\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"shared-fakes.d.ts","sourceRoot":"","sources":["../../../src/modules/repo/shared-fakes.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,UAAU,EAAS,MAAM,sBAAsB,CAAC;AAC9D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AA8BzE,MAAM,WAAW,UAAU;IACzB,kGAAkG;IAClG,MAAM,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,yGAAyG;IACzG,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD,oEAAoE;IACpE,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;IAC7B,mGAAmG;IACnG,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;CACjC;AA4QD,eAAO,MAAM,eAAe,EAAE,sBAAsB,CAAC,eAAe,CA8BnE,CAAC;AAEF,MAAM,WAAW,gBAAgB;IAC/B,sGAAsG;IACtG,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IAClE,0GAA0G;IAC1G,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;CACjE;AAED,kFAAkF;AAClF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,eAAe,GAAG,gBAAgB,CA8C7F"}
1
+ {"version":3,"file":"shared-fakes.d.ts","sourceRoot":"","sources":["../../../src/modules/repo/shared-fakes.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,UAAU,EAAS,MAAM,sBAAsB,CAAC;AAC9D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAqBzE,MAAM,WAAW,UAAU;IACzB,kGAAkG;IAClG,MAAM,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,yGAAyG;IACzG,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD,oEAAoE;IACpE,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;IAC7B,mGAAmG;IACnG,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;CACjC;AAmOD,eAAO,MAAM,eAAe,EAAE,sBAAsB,CAAC,eAAe,CA8BnE,CAAC;AAEF,MAAM,WAAW,gBAAgB;IAC/B,sGAAsG;IACtG,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IAClE,0GAA0G;IAC1G,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;CACjE;AAED,kFAAkF;AAClF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,eAAe,GAAG,gBAAgB,CA8C7F"}
@@ -9,10 +9,10 @@
9
9
  // faked, or a helper declared, in several test files with no owner yet is a report, never a failure,
10
10
  // so new copies surface without the gate holding an inventory.
11
11
  import ts from 'typescript';
12
- import { isTestPath, kindOfFile } from '../../node/layout.js';
12
+ import { isTestPath } from '../../node/layout.js';
13
13
  import { forEachNode, lineOfNode, unwrap } from '../../support/ast.js';
14
14
  import { isText, objectProblem, pathProblem } from '../../support/facts.js';
15
- import { normalizePath, resolveRelative } from '../../support/paths.js';
15
+ import { aliasList, isLocalModule, resolveSpecifier } from '../../support/resolve-specifier.js';
16
16
  /** The test-runner calls that replace a module with a fake. */
17
17
  const MOCK_CALLS = new Set([
18
18
  'vi.mock',
@@ -20,54 +20,12 @@ const MOCK_CALLS = new Set([
20
20
  'jest.mock',
21
21
  'jest.doMock',
22
22
  ]);
23
- const CODE_EXTENSIONS = ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs'];
24
- // A specifier names a TypeScript file by its built extension: `./x.js` names `x.ts`.
25
- const BUILT_TO_SOURCE = {
26
- '.js': ['.ts', '.tsx'],
27
- '.jsx': ['.tsx'],
28
- '.mjs': ['.mts'],
29
- '.cjs': ['.cts'],
30
- };
31
23
  const SCOPE = {
32
24
  roots: ['app', 'functions', 'scripts', 'workers'],
33
25
  kinds: ['code'],
34
26
  tests: 'only',
35
27
  optionalRoots: ['functions', 'scripts', 'workers'],
36
28
  };
37
- const nameOf = (p) => p.slice(p.lastIndexOf('/') + 1);
38
- const extensionOf = (p) => {
39
- const name = nameOf(p);
40
- const dot = name.lastIndexOf('.');
41
- return dot <= 0 ? '' : name.slice(dot);
42
- };
43
- /** A fake's module names a local file when its last segment carries a code extension. */
44
- const isLocalModule = (module) => CODE_EXTENSIONS.includes(extensionOf(module));
45
- /**
46
- * What a specifier names, read from `fromPath`: a local module's repo-relative file (or, when no file
47
- * answers, the path it reached), or the package specifier itself.
48
- */
49
- function resolveSpecifier(reader, fromPath, spec, aliases) {
50
- let base = null;
51
- for (const [prefix, folder] of aliases) {
52
- if (spec === prefix.replace(/\/$/, '') || spec.startsWith(prefix)) {
53
- base = normalizePath(`${folder}/${spec.slice(prefix.length)}`).path;
54
- break;
55
- }
56
- }
57
- base ??= resolveRelative(fromPath, spec)?.path ?? null;
58
- if (base === null)
59
- return spec;
60
- const candidates = [base];
61
- const ext = extensionOf(base);
62
- for (const source of BUILT_TO_SOURCE[ext] ?? [])
63
- candidates.push(base.slice(0, -ext.length) + source);
64
- for (const e of CODE_EXTENSIONS)
65
- candidates.push(base + e);
66
- for (const e of CODE_EXTENSIONS)
67
- candidates.push(`${base}/index${e}`);
68
- return candidates.find((c) => kindOfFile(nameOf(c)) === 'code' && reader.exists(c)) ?? base;
69
- }
70
- const aliasList = (facts) => Object.entries(facts.importAliases).sort((a, b) => b[0].length - a[0].length);
71
29
  function mockSites(reader, file, aliases) {
72
30
  const dynamic = file.imports.filter((site) => site.kind === 'dynamic');
73
31
  const out = [];
@@ -147,7 +105,7 @@ function ownerRoot(reader, path, what) {
147
105
  }
148
106
  function checkFakes(reader, facts, files) {
149
107
  const findings = [];
150
- const aliases = aliasList(facts);
108
+ const aliases = aliasList(facts.importAliases);
151
109
  /** root → module → owner */
152
110
  const owners = new Map();
153
111
  for (const fake of facts.fakes) {
@@ -305,7 +263,7 @@ export const sharedFakeGuard = {
305
263
  };
306
264
  /** Report-only: what tests copy that has no shared owner yet. Never a failure. */
307
265
  export function sharedFakeReport(reader, facts) {
308
- const aliases = aliasList(facts);
266
+ const aliases = aliasList(facts.importAliases);
309
267
  const files = reader.files(SCOPE);
310
268
  const faked = new Set();
311
269
  for (const fake of facts.fakes)
@@ -1 +1 @@
1
- {"version":3,"file":"shared-fakes.js","sourceRoot":"","sources":["../../../src/modules/repo/shared-fakes.ts"],"names":[],"mappings":"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","sourcesContent":["// A module tests fake has one shared fake, and a helper tests repeat has one shared owner. A fake\n// written out again in each test file is one behavior copied many times: the copies drift apart, and\n// a test asserting on its own copy proves nothing about the others. A test that needs its own\n// behavior from a faked module takes the shared fake and passes its overrides — its mock's factory\n// loads the owner with `import()` — so the fake stays the owner's and the override stays the test's.\n//\n// The app names each shared fake (the module, and the test-code file that owns its fake) and each\n// shared-helper file, tree by tree: an owner governs the tests of the root it lives in. A module\n// faked, or a helper declared, in several test files with no owner yet is a report, never a failure,\n// so new copies surface without the gate holding an inventory.\n\nimport ts from 'typescript';\nimport type { Finding } from '../../guard.js';\nimport { isTestPath, kindOfFile } from '../../node/layout.js';\nimport type { RepoReader, Scope } from '../../node/reader.js';\nimport type { GenericGuardDefinition } from '../../node/suite/config.js';\nimport type { FileView } from '../../reader/view.js';\nimport { forEachNode, lineOfNode, unwrap } from '../../support/ast.js';\nimport { isText, objectProblem, pathProblem } from '../../support/facts.js';\nimport { normalizePath, resolveRelative } from '../../support/paths.js';\n\n/** The test-runner calls that replace a module with a fake. */\nconst MOCK_CALLS: ReadonlySet<string> = new Set([\n 'vi.mock',\n 'vi.doMock',\n 'jest.mock',\n 'jest.doMock',\n]);\n\nconst CODE_EXTENSIONS = ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs'];\n// A specifier names a TypeScript file by its built extension: `./x.js` names `x.ts`.\nconst BUILT_TO_SOURCE: Readonly<Record<string, readonly string[]>> = {\n '.js': ['.ts', '.tsx'],\n '.jsx': ['.tsx'],\n '.mjs': ['.mts'],\n '.cjs': ['.cts'],\n};\n\nconst SCOPE: Scope = {\n roots: ['app', 'functions', 'scripts', 'workers'],\n kinds: ['code'],\n tests: 'only',\n optionalRoots: ['functions', 'scripts', 'workers'],\n};\n\nexport interface SharedFake {\n /** A package specifier (`@scope/pkg/react`), or the repo-relative path of a local module file. */\n module: string;\n /** The repo-relative test-code file that owns the module's one fake. */\n owner: string;\n}\n\nexport interface SharedFakeFacts {\n /** Specifier prefix → repo-relative folder (`{ '@/': 'src/' }`), so a local specifier names its file. */\n importAliases: Readonly<Record<string, string>>;\n /** Every module with a shared fake; `[]` when there is none yet. */\n fakes: readonly SharedFake[];\n /** The repo-relative test-code files whose exports are the shared test helpers; `[]` when none. */\n helperOwners: readonly string[];\n}\n\nconst nameOf = (p: string) => p.slice(p.lastIndexOf('/') + 1);\nconst extensionOf = (p: string) => {\n const name = nameOf(p);\n const dot = name.lastIndexOf('.');\n return dot <= 0 ? '' : name.slice(dot);\n};\n\n/** A fake's module names a local file when its last segment carries a code extension. */\nconst isLocalModule = (module: string) => CODE_EXTENSIONS.includes(extensionOf(module));\n\n/**\n * What a specifier names, read from `fromPath`: a local module's repo-relative file (or, when no file\n * answers, the path it reached), or the package specifier itself.\n */\nfunction resolveSpecifier(\n reader: RepoReader,\n fromPath: string,\n spec: string,\n aliases: ReadonlyArray<[string, string]>,\n): string {\n let base: string | null = null;\n for (const [prefix, folder] of aliases) {\n if (spec === prefix.replace(/\\/$/, '') || spec.startsWith(prefix)) {\n base = normalizePath(`${folder}/${spec.slice(prefix.length)}`).path;\n break;\n }\n }\n base ??= resolveRelative(fromPath, spec)?.path ?? null;\n if (base === null) return spec;\n const candidates = [base];\n const ext = extensionOf(base);\n for (const source of BUILT_TO_SOURCE[ext] ?? [])\n candidates.push(base.slice(0, -ext.length) + source);\n for (const e of CODE_EXTENSIONS) candidates.push(base + e);\n for (const e of CODE_EXTENSIONS) candidates.push(`${base}/index${e}`);\n return candidates.find((c) => kindOfFile(nameOf(c)) === 'code' && reader.exists(c)) ?? base;\n}\n\nconst aliasList = (facts: SharedFakeFacts): Array<[string, string]> =>\n Object.entries(facts.importAliases).sort((a, b) => b[0].length - a[0].length);\n\n/** One fake a test writes: the mock call, the module it names, and the modules its factory loads. */\ninterface MockSite {\n node: ts.CallExpression;\n module: string;\n factory: ts.Expression | undefined;\n /** Every module the factory loads with `import()`. */\n factoryLoads: string[];\n}\n\nfunction mockSites(\n reader: RepoReader,\n file: FileView,\n aliases: Array<[string, string]>,\n): MockSite[] {\n const dynamic = file.imports.filter((site) => site.kind === 'dynamic');\n const out: MockSite[] = [];\n for (const site of file.imports) {\n if (site.kind !== 'mock' || !MOCK_CALLS.has(site.callee ?? '')) continue;\n const call = site.node as ts.CallExpression;\n const factory = call.arguments[1];\n const factoryLoads = factory\n ? dynamic\n .filter((load) => load.node.pos >= factory.pos && load.node.end <= factory.end)\n .map((load) => resolveSpecifier(reader, file.path, load.spec, aliases))\n : [];\n out.push({\n node: call,\n module: resolveSpecifier(reader, file.path, site.spec, aliases),\n factory,\n factoryLoads,\n });\n }\n return out;\n}\n\nconst isFunctionValue = (node: ts.Expression | undefined) => {\n if (!node) return false;\n const value = unwrap(node);\n return ts.isArrowFunction(value) || ts.isFunctionExpression(value);\n};\n\n/** Every helper a file declares: a function declaration, or a variable holding a function. */\nfunction helperDeclarations(file: FileView): Array<{ name: string; node: ts.Node }> {\n const out: Array<{ name: string; node: ts.Node }> = [];\n forEachNode(file.sourceFile, (node) => {\n if (ts.isFunctionDeclaration(node) && node.name) out.push({ name: node.name.text, node });\n else if (\n ts.isVariableDeclaration(node) &&\n ts.isIdentifier(node.name) &&\n isFunctionValue(node.initializer)\n )\n out.push({ name: node.name.text, node });\n });\n return out;\n}\n\nconst hasExportModifier = (node: ts.Node) =>\n ts.canHaveModifiers(node) &&\n (ts.getModifiers(node) ?? []).some((m) => m.kind === ts.SyntaxKind.ExportKeyword);\n\n/** The names a shared-helper file declares and exports. */\nfunction exportedDeclarations(file: FileView): Set<string> {\n const names = new Set<string>();\n for (const statement of file.sourceFile.statements) {\n if (ts.isVariableStatement(statement) && hasExportModifier(statement)) {\n for (const d of statement.declarationList.declarations)\n if (ts.isIdentifier(d.name)) names.add(d.name.text);\n } else if (\n (ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) &&\n statement.name &&\n hasExportModifier(statement)\n ) {\n names.add(statement.name.text);\n } else if (\n ts.isExportDeclaration(statement) &&\n !statement.moduleSpecifier &&\n statement.exportClause &&\n ts.isNamedExports(statement.exportClause)\n ) {\n for (const el of statement.exportClause.elements) names.add(el.name.text);\n }\n }\n return names;\n}\n\n/** An owner file, checked: it exists and is test code. Its problem in words, or its root's name. */\nfunction ownerRoot(\n reader: RepoReader,\n path: string,\n what: string,\n): { root?: string; problem?: string } {\n if (!reader.exists(path)) return { problem: `${what} ${path} no longer exists` };\n if (!isTestPath(reader.layout, path))\n return {\n problem: `${what} ${path} is not test code — a shared fake or helper lives with the tests`,\n };\n return { root: reader.file(path).rootName };\n}\n\nfunction checkFakes(reader: RepoReader, facts: SharedFakeFacts, files: FileView[]): Finding[] {\n const findings: Finding[] = [];\n const aliases = aliasList(facts);\n /** root → module → owner */\n const owners = new Map<string, Map<string, string>>();\n for (const fake of facts.fakes) {\n if (isLocalModule(fake.module) && !reader.exists(fake.module))\n findings.push({\n file: fake.owner,\n detail: `the faked module ${fake.module} no longer exists — drop its shared fake`,\n });\n const { root, problem } = ownerRoot(reader, fake.owner, 'the shared fake owner');\n if (problem) {\n findings.push({ file: fake.owner, detail: problem });\n continue;\n }\n const byModule = owners.get(root!) ?? new Map<string, string>();\n const already = byModule.get(fake.module);\n if (already && already !== fake.owner)\n findings.push({\n file: fake.owner,\n detail: `${fake.module} has two shared fakes in one tree (${already} and ${fake.owner}) — keep one`,\n });\n else byModule.set(fake.module, fake.owner);\n owners.set(root!, byModule);\n }\n const ownerFiles = new Set(facts.fakes.map((f) => f.owner));\n for (const file of files) {\n if (ownerFiles.has(file.path)) continue;\n const byModule = owners.get(file.rootName);\n if (!byModule) continue;\n for (const mock of mockSites(reader, file, aliases)) {\n const owner = byModule.get(mock.module);\n if (!owner || mock.factoryLoads.includes(owner)) continue;\n findings.push({\n file: file.path,\n line: lineOfNode(file, mock.node),\n detail: mock.factory\n ? `fakes ${mock.module} by hand — its fake is ${owner}: load it in the mock's factory and pass this test's overrides`\n : `fakes ${mock.module} with no factory — its fake is ${owner}: load it in the mock's factory`,\n });\n }\n }\n return findings;\n}\n\nfunction sharedHelpers(\n reader: RepoReader,\n facts: SharedFakeFacts,\n findings: Finding[] | null,\n): Map<string, Map<string, string>> {\n /** root → helper name → owner */\n const helpers = new Map<string, Map<string, string>>();\n for (const owner of facts.helperOwners) {\n const { root, problem } = ownerRoot(reader, owner, 'the shared helper file');\n if (problem) {\n findings?.push({ file: owner, detail: problem });\n continue;\n }\n const byName = helpers.get(root!) ?? new Map<string, string>();\n for (const name of exportedDeclarations(reader.file(owner))) byName.set(name, owner);\n helpers.set(root!, byName);\n }\n return helpers;\n}\n\nfunction checkHelpers(reader: RepoReader, facts: SharedFakeFacts, files: FileView[]): Finding[] {\n const findings: Finding[] = [];\n const helpers = sharedHelpers(reader, facts, findings);\n const ownerFiles = new Set([...facts.helperOwners, ...facts.fakes.map((f) => f.owner)]);\n for (const file of files) {\n if (ownerFiles.has(file.path)) continue;\n const byName = helpers.get(file.rootName);\n if (!byName) continue;\n for (const { name, node } of helperDeclarations(file)) {\n const owner = byName.get(name);\n if (owner)\n findings.push({\n file: file.path,\n line: lineOfNode(file, node),\n detail: `declares ${name}, which the shared helpers in ${owner} export — import it from there`,\n });\n }\n }\n return findings;\n}\n\nfunction factProblems(facts: SharedFakeFacts): string[] {\n const problems: string[] = [];\n if (!objectProblem(facts, 'the facts', problems)) return problems;\n if (objectProblem(facts.importAliases, 'importAliases', problems))\n for (const [prefix, folder] of Object.entries(facts.importAliases))\n if (!prefix || !isText(folder))\n problems.push(`importAliases ${prefix} needs a target folder`);\n if (!Array.isArray(facts.fakes))\n problems.push('fakes is missing (declare [] when there is none)');\n else {\n const seen = new Set<string>();\n for (const fake of facts.fakes as unknown[]) {\n const entry = (fake && typeof fake === 'object' ? fake : {}) as Record<string, unknown>;\n if (!isText(entry.module)) {\n problems.push('an entry of fakes names no module');\n continue;\n }\n const label = `fakes entry ${entry.module}`;\n if (isLocalModule(entry.module)) pathProblem(entry.module, `${label}'s module`, problems);\n pathProblem(entry.owner, `${label}'s owner`, problems);\n const key = `${entry.module}\\0${String(entry.owner)}`;\n if (seen.has(key))\n problems.push(`fakes lists ${entry.module} with ${String(entry.owner)} twice`);\n seen.add(key);\n }\n }\n if (!Array.isArray(facts.helperOwners))\n problems.push('helperOwners is missing (declare [] when there is none)');\n else {\n const seen = new Set<string>();\n for (const owner of facts.helperOwners as unknown[]) {\n pathProblem(owner, 'a helperOwners entry', problems);\n if (isText(owner) && seen.has(owner)) problems.push(`helperOwners lists ${owner} twice`);\n if (isText(owner)) seen.add(owner);\n }\n }\n return problems;\n}\n\nexport const sharedFakeGuard: GenericGuardDefinition<SharedFakeFacts> = {\n id: 'sharedFakes',\n absentWhen: false,\n factProblems,\n build(facts) {\n return [\n {\n title: 'a test fakes a module that has a shared fake only through that fake',\n description:\n 'A module with a shared fake is faked through it alone: a test that needs its own behavior writes a mock whose ' +\n 'factory loads the shared fake with import() and passes its overrides. A test that writes the fake out again, or ' +\n 'mocks the module with no factory, is a second copy that drifts. A shared fake whose owner or module no longer ' +\n 'exists, or that is not test code, fails too.',\n timeoutMs: 120_000,\n run(context) {\n return checkFakes(context.reader, facts, context.files(SCOPE));\n },\n },\n {\n title: 'a test never re-declares a helper the shared test helpers export',\n description:\n 'A helper the shared test-helper files export is imported from there, never declared again in a test file of the ' +\n 'same tree. A shared helper file that no longer exists, or that is not test code, fails too.',\n timeoutMs: 120_000,\n run(context) {\n return checkHelpers(context.reader, facts, context.files(SCOPE));\n },\n },\n ];\n },\n};\n\nexport interface SharedFakeReport {\n /** Modules faked in more than one test file of a tree with no shared fake there, most files first. */\n modules: Array<{ root: string; module: string; files: string[] }>;\n /** Helpers declared in more than one test file of a tree with no shared owner there, most files first. */\n helpers: Array<{ root: string; name: string; files: string[] }>;\n}\n\n/** Report-only: what tests copy that has no shared owner yet. Never a failure. */\nexport function sharedFakeReport(reader: RepoReader, facts: SharedFakeFacts): SharedFakeReport {\n const aliases = aliasList(facts);\n const files = reader.files(SCOPE);\n const faked = new Set<string>();\n for (const fake of facts.fakes)\n if (reader.exists(fake.owner)) faked.add(`${reader.file(fake.owner).rootName}\\0${fake.module}`);\n const helpers = sharedHelpers(reader, facts, null);\n const ownerFiles = new Set([...facts.helperOwners, ...facts.fakes.map((f) => f.owner)]);\n const moduleFiles = new Map<string, Set<string>>();\n const helperFiles = new Map<string, Set<string>>();\n const note = (map: Map<string, Set<string>>, key: string, path: string) =>\n map.set(key, (map.get(key) ?? new Set()).add(path));\n for (const file of files) {\n if (ownerFiles.has(file.path)) continue;\n for (const mock of mockSites(reader, file, aliases)) {\n const key = `${file.rootName}\\0${mock.module}`;\n if (!faked.has(key)) note(moduleFiles, key, file.path);\n }\n for (const { name } of helperDeclarations(file))\n if (!helpers.get(file.rootName)?.has(name))\n note(helperFiles, `${file.rootName}\\0${name}`, file.path);\n }\n const repeated = (map: Map<string, Set<string>>) =>\n [...map]\n .filter(([, paths]) => paths.size > 1)\n .map(([key, paths]) => {\n const [root, subject] = key.split('\\0');\n return { root, subject, files: [...paths].sort() };\n })\n .sort(\n (a, b) =>\n b.files.length - a.files.length ||\n (a.root === b.root ? (a.subject < b.subject ? -1 : 1) : a.root < b.root ? -1 : 1),\n );\n return {\n modules: repeated(moduleFiles).map(({ root, subject, files: f }) => ({\n root,\n module: subject,\n files: f,\n })),\n helpers: repeated(helperFiles).map(({ root, subject, files: f }) => ({\n root,\n name: subject,\n files: f,\n })),\n };\n}\n"]}
1
+ {"version":3,"file":"shared-fakes.js","sourceRoot":"","sources":["../../../src/modules/repo/shared-fakes.ts"],"names":[],"mappings":"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","sourcesContent":["// A module tests fake has one shared fake, and a helper tests repeat has one shared owner. A fake\n// written out again in each test file is one behavior copied many times: the copies drift apart, and\n// a test asserting on its own copy proves nothing about the others. A test that needs its own\n// behavior from a faked module takes the shared fake and passes its overrides — its mock's factory\n// loads the owner with `import()` — so the fake stays the owner's and the override stays the test's.\n//\n// The app names each shared fake (the module, and the test-code file that owns its fake) and each\n// shared-helper file, tree by tree: an owner governs the tests of the root it lives in. A module\n// faked, or a helper declared, in several test files with no owner yet is a report, never a failure,\n// so new copies surface without the gate holding an inventory.\n\nimport ts from 'typescript';\nimport type { Finding } from '../../guard.js';\nimport { isTestPath } from '../../node/layout.js';\nimport type { RepoReader, Scope } from '../../node/reader.js';\nimport type { GenericGuardDefinition } from '../../node/suite/config.js';\nimport type { FileView } from '../../reader/view.js';\nimport { forEachNode, lineOfNode, unwrap } from '../../support/ast.js';\nimport { isText, objectProblem, pathProblem } from '../../support/facts.js';\nimport { aliasList, isLocalModule, resolveSpecifier } from '../../support/resolve-specifier.js';\n\n/** The test-runner calls that replace a module with a fake. */\nconst MOCK_CALLS: ReadonlySet<string> = new Set([\n 'vi.mock',\n 'vi.doMock',\n 'jest.mock',\n 'jest.doMock',\n]);\n\nconst SCOPE: Scope = {\n roots: ['app', 'functions', 'scripts', 'workers'],\n kinds: ['code'],\n tests: 'only',\n optionalRoots: ['functions', 'scripts', 'workers'],\n};\n\nexport interface SharedFake {\n /** A package specifier (`@scope/pkg/react`), or the repo-relative path of a local module file. */\n module: string;\n /** The repo-relative test-code file that owns the module's one fake. */\n owner: string;\n}\n\nexport interface SharedFakeFacts {\n /** Specifier prefix → repo-relative folder (`{ '@/': 'src/' }`), so a local specifier names its file. */\n importAliases: Readonly<Record<string, string>>;\n /** Every module with a shared fake; `[]` when there is none yet. */\n fakes: readonly SharedFake[];\n /** The repo-relative test-code files whose exports are the shared test helpers; `[]` when none. */\n helperOwners: readonly string[];\n}\n\n/** One fake a test writes: the mock call, the module it names, and the modules its factory loads. */\ninterface MockSite {\n node: ts.CallExpression;\n module: string;\n factory: ts.Expression | undefined;\n /** Every module the factory loads with `import()`. */\n factoryLoads: string[];\n}\n\nfunction mockSites(\n reader: RepoReader,\n file: FileView,\n aliases: Array<[string, string]>,\n): MockSite[] {\n const dynamic = file.imports.filter((site) => site.kind === 'dynamic');\n const out: MockSite[] = [];\n for (const site of file.imports) {\n if (site.kind !== 'mock' || !MOCK_CALLS.has(site.callee ?? '')) continue;\n const call = site.node as ts.CallExpression;\n const factory = call.arguments[1];\n const factoryLoads = factory\n ? dynamic\n .filter((load) => load.node.pos >= factory.pos && load.node.end <= factory.end)\n .map((load) => resolveSpecifier(reader, file.path, load.spec, aliases))\n : [];\n out.push({\n node: call,\n module: resolveSpecifier(reader, file.path, site.spec, aliases),\n factory,\n factoryLoads,\n });\n }\n return out;\n}\n\nconst isFunctionValue = (node: ts.Expression | undefined) => {\n if (!node) return false;\n const value = unwrap(node);\n return ts.isArrowFunction(value) || ts.isFunctionExpression(value);\n};\n\n/** Every helper a file declares: a function declaration, or a variable holding a function. */\nfunction helperDeclarations(file: FileView): Array<{ name: string; node: ts.Node }> {\n const out: Array<{ name: string; node: ts.Node }> = [];\n forEachNode(file.sourceFile, (node) => {\n if (ts.isFunctionDeclaration(node) && node.name) out.push({ name: node.name.text, node });\n else if (\n ts.isVariableDeclaration(node) &&\n ts.isIdentifier(node.name) &&\n isFunctionValue(node.initializer)\n )\n out.push({ name: node.name.text, node });\n });\n return out;\n}\n\nconst hasExportModifier = (node: ts.Node) =>\n ts.canHaveModifiers(node) &&\n (ts.getModifiers(node) ?? []).some((m) => m.kind === ts.SyntaxKind.ExportKeyword);\n\n/** The names a shared-helper file declares and exports. */\nfunction exportedDeclarations(file: FileView): Set<string> {\n const names = new Set<string>();\n for (const statement of file.sourceFile.statements) {\n if (ts.isVariableStatement(statement) && hasExportModifier(statement)) {\n for (const d of statement.declarationList.declarations)\n if (ts.isIdentifier(d.name)) names.add(d.name.text);\n } else if (\n (ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) &&\n statement.name &&\n hasExportModifier(statement)\n ) {\n names.add(statement.name.text);\n } else if (\n ts.isExportDeclaration(statement) &&\n !statement.moduleSpecifier &&\n statement.exportClause &&\n ts.isNamedExports(statement.exportClause)\n ) {\n for (const el of statement.exportClause.elements) names.add(el.name.text);\n }\n }\n return names;\n}\n\n/** An owner file, checked: it exists and is test code. Its problem in words, or its root's name. */\nfunction ownerRoot(\n reader: RepoReader,\n path: string,\n what: string,\n): { root?: string; problem?: string } {\n if (!reader.exists(path)) return { problem: `${what} ${path} no longer exists` };\n if (!isTestPath(reader.layout, path))\n return {\n problem: `${what} ${path} is not test code — a shared fake or helper lives with the tests`,\n };\n return { root: reader.file(path).rootName };\n}\n\nfunction checkFakes(reader: RepoReader, facts: SharedFakeFacts, files: FileView[]): Finding[] {\n const findings: Finding[] = [];\n const aliases = aliasList(facts.importAliases);\n /** root → module → owner */\n const owners = new Map<string, Map<string, string>>();\n for (const fake of facts.fakes) {\n if (isLocalModule(fake.module) && !reader.exists(fake.module))\n findings.push({\n file: fake.owner,\n detail: `the faked module ${fake.module} no longer exists — drop its shared fake`,\n });\n const { root, problem } = ownerRoot(reader, fake.owner, 'the shared fake owner');\n if (problem) {\n findings.push({ file: fake.owner, detail: problem });\n continue;\n }\n const byModule = owners.get(root!) ?? new Map<string, string>();\n const already = byModule.get(fake.module);\n if (already && already !== fake.owner)\n findings.push({\n file: fake.owner,\n detail: `${fake.module} has two shared fakes in one tree (${already} and ${fake.owner}) — keep one`,\n });\n else byModule.set(fake.module, fake.owner);\n owners.set(root!, byModule);\n }\n const ownerFiles = new Set(facts.fakes.map((f) => f.owner));\n for (const file of files) {\n if (ownerFiles.has(file.path)) continue;\n const byModule = owners.get(file.rootName);\n if (!byModule) continue;\n for (const mock of mockSites(reader, file, aliases)) {\n const owner = byModule.get(mock.module);\n if (!owner || mock.factoryLoads.includes(owner)) continue;\n findings.push({\n file: file.path,\n line: lineOfNode(file, mock.node),\n detail: mock.factory\n ? `fakes ${mock.module} by hand — its fake is ${owner}: load it in the mock's factory and pass this test's overrides`\n : `fakes ${mock.module} with no factory — its fake is ${owner}: load it in the mock's factory`,\n });\n }\n }\n return findings;\n}\n\nfunction sharedHelpers(\n reader: RepoReader,\n facts: SharedFakeFacts,\n findings: Finding[] | null,\n): Map<string, Map<string, string>> {\n /** root → helper name → owner */\n const helpers = new Map<string, Map<string, string>>();\n for (const owner of facts.helperOwners) {\n const { root, problem } = ownerRoot(reader, owner, 'the shared helper file');\n if (problem) {\n findings?.push({ file: owner, detail: problem });\n continue;\n }\n const byName = helpers.get(root!) ?? new Map<string, string>();\n for (const name of exportedDeclarations(reader.file(owner))) byName.set(name, owner);\n helpers.set(root!, byName);\n }\n return helpers;\n}\n\nfunction checkHelpers(reader: RepoReader, facts: SharedFakeFacts, files: FileView[]): Finding[] {\n const findings: Finding[] = [];\n const helpers = sharedHelpers(reader, facts, findings);\n const ownerFiles = new Set([...facts.helperOwners, ...facts.fakes.map((f) => f.owner)]);\n for (const file of files) {\n if (ownerFiles.has(file.path)) continue;\n const byName = helpers.get(file.rootName);\n if (!byName) continue;\n for (const { name, node } of helperDeclarations(file)) {\n const owner = byName.get(name);\n if (owner)\n findings.push({\n file: file.path,\n line: lineOfNode(file, node),\n detail: `declares ${name}, which the shared helpers in ${owner} export — import it from there`,\n });\n }\n }\n return findings;\n}\n\nfunction factProblems(facts: SharedFakeFacts): string[] {\n const problems: string[] = [];\n if (!objectProblem(facts, 'the facts', problems)) return problems;\n if (objectProblem(facts.importAliases, 'importAliases', problems))\n for (const [prefix, folder] of Object.entries(facts.importAliases))\n if (!prefix || !isText(folder))\n problems.push(`importAliases ${prefix} needs a target folder`);\n if (!Array.isArray(facts.fakes))\n problems.push('fakes is missing (declare [] when there is none)');\n else {\n const seen = new Set<string>();\n for (const fake of facts.fakes as unknown[]) {\n const entry = (fake && typeof fake === 'object' ? fake : {}) as Record<string, unknown>;\n if (!isText(entry.module)) {\n problems.push('an entry of fakes names no module');\n continue;\n }\n const label = `fakes entry ${entry.module}`;\n if (isLocalModule(entry.module)) pathProblem(entry.module, `${label}'s module`, problems);\n pathProblem(entry.owner, `${label}'s owner`, problems);\n const key = `${entry.module}\\0${String(entry.owner)}`;\n if (seen.has(key))\n problems.push(`fakes lists ${entry.module} with ${String(entry.owner)} twice`);\n seen.add(key);\n }\n }\n if (!Array.isArray(facts.helperOwners))\n problems.push('helperOwners is missing (declare [] when there is none)');\n else {\n const seen = new Set<string>();\n for (const owner of facts.helperOwners as unknown[]) {\n pathProblem(owner, 'a helperOwners entry', problems);\n if (isText(owner) && seen.has(owner)) problems.push(`helperOwners lists ${owner} twice`);\n if (isText(owner)) seen.add(owner);\n }\n }\n return problems;\n}\n\nexport const sharedFakeGuard: GenericGuardDefinition<SharedFakeFacts> = {\n id: 'sharedFakes',\n absentWhen: false,\n factProblems,\n build(facts) {\n return [\n {\n title: 'a test fakes a module that has a shared fake only through that fake',\n description:\n 'A module with a shared fake is faked through it alone: a test that needs its own behavior writes a mock whose ' +\n 'factory loads the shared fake with import() and passes its overrides. A test that writes the fake out again, or ' +\n 'mocks the module with no factory, is a second copy that drifts. A shared fake whose owner or module no longer ' +\n 'exists, or that is not test code, fails too.',\n timeoutMs: 120_000,\n run(context) {\n return checkFakes(context.reader, facts, context.files(SCOPE));\n },\n },\n {\n title: 'a test never re-declares a helper the shared test helpers export',\n description:\n 'A helper the shared test-helper files export is imported from there, never declared again in a test file of the ' +\n 'same tree. A shared helper file that no longer exists, or that is not test code, fails too.',\n timeoutMs: 120_000,\n run(context) {\n return checkHelpers(context.reader, facts, context.files(SCOPE));\n },\n },\n ];\n },\n};\n\nexport interface SharedFakeReport {\n /** Modules faked in more than one test file of a tree with no shared fake there, most files first. */\n modules: Array<{ root: string; module: string; files: string[] }>;\n /** Helpers declared in more than one test file of a tree with no shared owner there, most files first. */\n helpers: Array<{ root: string; name: string; files: string[] }>;\n}\n\n/** Report-only: what tests copy that has no shared owner yet. Never a failure. */\nexport function sharedFakeReport(reader: RepoReader, facts: SharedFakeFacts): SharedFakeReport {\n const aliases = aliasList(facts.importAliases);\n const files = reader.files(SCOPE);\n const faked = new Set<string>();\n for (const fake of facts.fakes)\n if (reader.exists(fake.owner)) faked.add(`${reader.file(fake.owner).rootName}\\0${fake.module}`);\n const helpers = sharedHelpers(reader, facts, null);\n const ownerFiles = new Set([...facts.helperOwners, ...facts.fakes.map((f) => f.owner)]);\n const moduleFiles = new Map<string, Set<string>>();\n const helperFiles = new Map<string, Set<string>>();\n const note = (map: Map<string, Set<string>>, key: string, path: string) =>\n map.set(key, (map.get(key) ?? new Set()).add(path));\n for (const file of files) {\n if (ownerFiles.has(file.path)) continue;\n for (const mock of mockSites(reader, file, aliases)) {\n const key = `${file.rootName}\\0${mock.module}`;\n if (!faked.has(key)) note(moduleFiles, key, file.path);\n }\n for (const { name } of helperDeclarations(file))\n if (!helpers.get(file.rootName)?.has(name))\n note(helperFiles, `${file.rootName}\\0${name}`, file.path);\n }\n const repeated = (map: Map<string, Set<string>>) =>\n [...map]\n .filter(([, paths]) => paths.size > 1)\n .map(([key, paths]) => {\n const [root, subject] = key.split('\\0');\n return { root, subject, files: [...paths].sort() };\n })\n .sort(\n (a, b) =>\n b.files.length - a.files.length ||\n (a.root === b.root ? (a.subject < b.subject ? -1 : 1) : a.root < b.root ? -1 : 1),\n );\n return {\n modules: repeated(moduleFiles).map(({ root, subject, files: f }) => ({\n root,\n module: subject,\n files: f,\n })),\n helpers: repeated(helperFiles).map(({ root, subject, files: f }) => ({\n root,\n name: subject,\n files: f,\n })),\n };\n}\n"]}
@@ -0,0 +1,45 @@
1
+ import ts from 'typescript';
2
+ import type { RepoReader } from '../../node/reader.js';
3
+ import type { GenericGuardDefinition } from '../../node/suite/config.js';
4
+ import { type FileView } from '../../reader/view.js';
5
+ /** One place a limit is set: the expression that gives it, and what it limits. */
6
+ interface LimitSite {
7
+ value: ts.Expression;
8
+ what: string;
9
+ }
10
+ /** Every place a test file sets a limit. A string is read as a sample test file. */
11
+ export declare function limitSites(source: FileView | string): Array<LimitSite & {
12
+ node: ts.Node;
13
+ }>;
14
+ export interface TestTimeoutFacts {
15
+ /** Specifier prefix → repo-relative folder (`{ '@/': 'src/' }`), so an import names its file. */
16
+ importAliases: Readonly<Record<string, string>>;
17
+ /**
18
+ * The repo-relative test-code files that declare the timeout constants: each exported numeric
19
+ * `const` is one budget a test may name. `[]` when the app declares none — then no test sets a
20
+ * limit of its own.
21
+ */
22
+ timeoutFiles: readonly string[];
23
+ }
24
+ export declare const testTimeoutGuard: GenericGuardDefinition<TestTimeoutFacts>;
25
+ export interface SlowTestCase {
26
+ file: string;
27
+ name: string;
28
+ durationMs: number;
29
+ }
30
+ /**
31
+ * Report-only: every test case at or over `thresholdMs` in a test file that imports no budget from
32
+ * a timeout file — the ones a busier machine pushes past the default limit. Read from the runner's
33
+ * JSON results (`--reporter=json`); a case is named by its file and full name. Never a failure.
34
+ */
35
+ export declare function slowTestReport(reader: RepoReader, facts: TestTimeoutFacts, results: ReadonlyArray<{
36
+ testResults: ReadonlyArray<{
37
+ name: string;
38
+ assertionResults: ReadonlyArray<{
39
+ fullName: string;
40
+ duration?: number | null;
41
+ }>;
42
+ }>;
43
+ }>, thresholdMs: number): SlowTestCase[];
44
+ export {};
45
+ //# sourceMappingURL=test-timeouts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-timeouts.d.ts","sourceRoot":"","sources":["../../../src/modules/repo/test-timeouts.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,MAAM,YAAY,CAAC;AAG5B,OAAO,KAAK,EAAE,UAAU,EAAS,MAAM,sBAAsB,CAAC;AAC9D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,EAAc,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AA4CjE,kFAAkF;AAClF,UAAU,SAAS;IACjB,KAAK,EAAE,EAAE,CAAC,UAAU,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;CACd;AA4CD,oFAAoF;AACpF,wBAAgB,UAAU,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAC,SAAS,GAAG;IAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAA;CAAE,CAAC,CAuD1F;AAED,MAAM,WAAW,gBAAgB;IAC/B,iGAAiG;IACjG,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD;;;;OAIG;IACH,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;CACjC;AA4FD,eAAO,MAAM,gBAAgB,EAAE,sBAAsB,CAAC,gBAAgB,CAoBrE,CAAC;AAEF,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,gBAAgB,EACvB,OAAO,EAAE,aAAa,CAAC;IACrB,WAAW,EAAE,aAAa,CAAC;QACzB,IAAI,EAAE,MAAM,CAAC;QACb,gBAAgB,EAAE,aAAa,CAAC;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;SAAE,CAAC,CAAC;KACjF,CAAC,CAAC;CACJ,CAAC,EACF,WAAW,EAAE,MAAM,GAClB,YAAY,EAAE,CAkChB"}
@@ -0,0 +1,299 @@
1
+ // A test's time limit is a named budget, never a number. Every test runs on the runner's default
2
+ // limit; one that needs longer — real work such as spawning a media encoder — names a constant the
3
+ // app declares in its timeout files, so the budgets live in one place, every test that needs more is
4
+ // found by that name, and no test raises its own limit quietly to get past a slow run. The same holds
5
+ // for a test library's own waits (`waitFor`, `findBy…`), which give up on their own clock.
6
+ //
7
+ // The constants are the numeric `const`s the app's timeout files export; a test uses one only by
8
+ // importing it from that file. A number written where a limit is set — a test, suite, or hook
9
+ // timeout, `vi.setConfig`, a wait's `timeout`, a library's `asyncUtilTimeout` — fails, and so does a
10
+ // name that is no such constant.
11
+ import ts from 'typescript';
12
+ import { isTestPath } from '../../node/layout.js';
13
+ import { viewOfText } from '../../reader/view.js';
14
+ import { forEachNode, lineOfNode, unwrap } from '../../support/ast.js';
15
+ import { isText, objectProblem, pathProblem } from '../../support/facts.js';
16
+ import { aliasList, resolveSpecifier } from '../../support/resolve-specifier.js';
17
+ const SCOPE = {
18
+ roots: ['app', 'functions', 'scripts', 'workers'],
19
+ kinds: ['code'],
20
+ tests: 'only',
21
+ optionalRoots: ['functions', 'scripts', 'workers'],
22
+ };
23
+ /** The test-runner functions whose calls set a test's or a suite's limit. */
24
+ const RUNNERS = new Set(['it', 'test', 'describe', 'suite', 'bench']);
25
+ /** The hooks whose second argument is their limit. */
26
+ const HOOKS = new Set(['beforeAll', 'afterAll', 'beforeEach', 'afterEach']);
27
+ /** The waits that take `{ timeout }` beside what they wait for. */
28
+ const WAITS = new Set(['waitFor', 'waitForElementToBeRemoved']);
29
+ const isFindQuery = (name) => /^find(All)?By[A-Z]/.test(name);
30
+ /** The name a call chain starts from: `it.skipIf(x).each(t)(…)` → `it`. */
31
+ function rootName(expression) {
32
+ let at = expression;
33
+ for (;;) {
34
+ if (ts.isPropertyAccessExpression(at))
35
+ at = at.expression;
36
+ else if (ts.isCallExpression(at))
37
+ at = at.expression;
38
+ else if (ts.isTaggedTemplateExpression(at))
39
+ at = at.tag;
40
+ else
41
+ break;
42
+ }
43
+ return ts.isIdentifier(at) ? at.text : undefined;
44
+ }
45
+ const dotted = (expression) => ts.isPropertyAccessExpression(expression) && ts.isIdentifier(expression.expression)
46
+ ? `${expression.expression.text}.${expression.name.text}`
47
+ : undefined;
48
+ const ownName = (expression) => ts.isIdentifier(expression)
49
+ ? expression.text
50
+ : ts.isPropertyAccessExpression(expression)
51
+ ? expression.name.text
52
+ : undefined;
53
+ /** A same-file `const X = { … }`, so `waitFor(fn, WAIT)` reads the object `WAIT` holds. */
54
+ function objectBehind(file, expression) {
55
+ const value = unwrap(expression);
56
+ if (ts.isObjectLiteralExpression(value))
57
+ return value;
58
+ if (!ts.isIdentifier(value))
59
+ return null;
60
+ let found = null;
61
+ forEachNode(file.sourceFile, (node) => {
62
+ if (!found &&
63
+ ts.isVariableDeclaration(node) &&
64
+ ts.isIdentifier(node.name) &&
65
+ node.name.text === value.text &&
66
+ node.initializer &&
67
+ ts.isObjectLiteralExpression(unwrap(node.initializer)))
68
+ found = unwrap(node.initializer);
69
+ });
70
+ return found;
71
+ }
72
+ function limitsIn(object, keys, what) {
73
+ const out = [];
74
+ for (const prop of object.properties) {
75
+ if (ts.isPropertyAssignment(prop) &&
76
+ ts.isIdentifier(prop.name) &&
77
+ keys.includes(prop.name.text))
78
+ out.push({ value: prop.initializer, what: `${what} ${prop.name.text}` });
79
+ else if (ts.isShorthandPropertyAssignment(prop) && keys.includes(prop.name.text))
80
+ out.push({ value: prop.name, what: `${what} ${prop.name.text}` });
81
+ }
82
+ return out;
83
+ }
84
+ /** Every place a test file sets a limit. A string is read as a sample test file. */
85
+ export function limitSites(source) {
86
+ const file = typeof source === 'string' ? viewOfText('sample.test.ts', source) : source;
87
+ const out = [];
88
+ const add = (sites, node) => {
89
+ for (const site of sites)
90
+ out.push({ ...site, node });
91
+ };
92
+ forEachNode(file.sourceFile, (node) => {
93
+ if (!ts.isCallExpression(node))
94
+ return;
95
+ const callee = node.expression;
96
+ const root = rootName(callee);
97
+ const name = ownName(callee);
98
+ const call = dotted(callee);
99
+ if (call === 'vi.setConfig' || call === 'jest.setTimeout') {
100
+ if (call === 'jest.setTimeout' && node.arguments[0])
101
+ add([{ value: node.arguments[0], what: 'the test timeout' }], node);
102
+ const object = node.arguments[0] && objectBehind(file, node.arguments[0]);
103
+ if (object)
104
+ add(limitsIn(object, ['testTimeout', 'hookTimeout'], 'the config'), node);
105
+ return;
106
+ }
107
+ if (name === 'configure' && node.arguments[0]) {
108
+ const object = objectBehind(file, node.arguments[0]);
109
+ if (object)
110
+ add(limitsIn(object, ['asyncUtilTimeout'], 'the test library'), node);
111
+ return;
112
+ }
113
+ if (root && HOOKS.has(root) && ts.isIdentifier(callee) && node.arguments[1]) {
114
+ add([{ value: node.arguments[1], what: `the ${root} timeout` }], node);
115
+ return;
116
+ }
117
+ if (root && RUNNERS.has(root) && !(name && WAITS.has(name))) {
118
+ // The title comes first; a limit is a later number, or a later options object's `timeout`.
119
+ for (const arg of node.arguments.slice(1)) {
120
+ const value = unwrap(arg);
121
+ if (ts.isArrowFunction(value) || ts.isFunctionExpression(value))
122
+ continue;
123
+ if (ts.isObjectLiteralExpression(value)) {
124
+ add(limitsIn(value, ['timeout'], `the ${root} option`), node);
125
+ continue;
126
+ }
127
+ if (ts.isIdentifier(value) ||
128
+ ts.isNumericLiteral(value) ||
129
+ ts.isBinaryExpression(value) ||
130
+ ts.isPrefixUnaryExpression(value))
131
+ add([{ value: arg, what: `the ${root} timeout` }], node);
132
+ }
133
+ return;
134
+ }
135
+ if (name && (WAITS.has(name) || isFindQuery(name))) {
136
+ for (const arg of node.arguments.slice(1)) {
137
+ const object = objectBehind(file, arg);
138
+ if (object)
139
+ add(limitsIn(object, ['timeout'], `the ${name} wait`), node);
140
+ }
141
+ }
142
+ });
143
+ return out;
144
+ }
145
+ /** The numeric `const`s a timeout file exports. */
146
+ function exportedBudgets(file) {
147
+ const names = new Set();
148
+ for (const statement of file.sourceFile.statements) {
149
+ if (!ts.isVariableStatement(statement) ||
150
+ !(ts.getModifiers(statement) ?? []).some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ||
151
+ !(statement.declarationList.flags & ts.NodeFlags.Const))
152
+ continue;
153
+ for (const d of statement.declarationList.declarations) {
154
+ const value = d.initializer && unwrap(d.initializer);
155
+ if (ts.isIdentifier(d.name) && value && ts.isNumericLiteral(value))
156
+ names.add(d.name.text);
157
+ }
158
+ }
159
+ return names;
160
+ }
161
+ function check(reader, facts, files) {
162
+ const findings = [];
163
+ const budgetsOf = new Map();
164
+ for (const path of facts.timeoutFiles) {
165
+ if (!reader.exists(path)) {
166
+ findings.push({ file: path, detail: `the timeout file ${path} no longer exists` });
167
+ continue;
168
+ }
169
+ if (!isTestPath(reader.layout, path)) {
170
+ findings.push({
171
+ file: path,
172
+ detail: `the timeout file ${path} is not test code — the budgets live with the tests`,
173
+ });
174
+ continue;
175
+ }
176
+ const budgets = exportedBudgets(reader.file(path));
177
+ if (!budgets.size)
178
+ findings.push({
179
+ file: path,
180
+ detail: `the timeout file ${path} exports no numeric const budget`,
181
+ });
182
+ budgetsOf.set(path, budgets);
183
+ }
184
+ const aliases = aliasList(facts.importAliases);
185
+ for (const file of files) {
186
+ if (budgetsOf.has(file.path))
187
+ continue;
188
+ const sites = limitSites(file);
189
+ if (!sites.length)
190
+ continue;
191
+ // The budgets this file imports, by local name.
192
+ const imported = new Set();
193
+ for (const site of file.imports) {
194
+ if (site.kind !== 'import')
195
+ continue;
196
+ const owner = resolveSpecifier(reader, file.path, site.spec, aliases);
197
+ const budgets = budgetsOf.get(owner);
198
+ if (!budgets)
199
+ continue;
200
+ for (const named of site.named)
201
+ if (budgets.has(named.imported))
202
+ imported.add(named.local);
203
+ }
204
+ for (const site of sites) {
205
+ const value = unwrap(site.value);
206
+ if (ts.isIdentifier(value) && imported.has(value.text))
207
+ continue;
208
+ findings.push({
209
+ file: file.path,
210
+ line: lineOfNode(file, site.node),
211
+ detail: ts.isIdentifier(value)
212
+ ? `${site.what} is ${value.text}, which is no budget imported from a timeout file — name one of its constants`
213
+ : `${site.what} is written as a number — leave the default, or name a budget from a timeout file`,
214
+ });
215
+ }
216
+ }
217
+ return findings;
218
+ }
219
+ function factProblems(facts) {
220
+ const problems = [];
221
+ if (!objectProblem(facts, 'the facts', problems))
222
+ return problems;
223
+ if (objectProblem(facts.importAliases, 'importAliases', problems))
224
+ for (const [prefix, folder] of Object.entries(facts.importAliases))
225
+ if (!prefix || !isText(folder))
226
+ problems.push(`importAliases ${prefix} needs a target folder`);
227
+ if (!Array.isArray(facts.timeoutFiles))
228
+ problems.push('timeoutFiles is missing (declare [] when there is none)');
229
+ else {
230
+ const seen = new Set();
231
+ for (const path of facts.timeoutFiles) {
232
+ pathProblem(path, 'a timeoutFiles entry', problems);
233
+ if (isText(path) && seen.has(path))
234
+ problems.push(`timeoutFiles lists ${path} twice`);
235
+ if (isText(path))
236
+ seen.add(path);
237
+ }
238
+ }
239
+ return problems;
240
+ }
241
+ export const testTimeoutGuard = {
242
+ id: 'testTimeouts',
243
+ absentWhen: false,
244
+ factProblems,
245
+ build(facts) {
246
+ return [
247
+ {
248
+ title: 'a test sets a time limit only by naming a budget from a timeout file',
249
+ description: 'Every test runs on the default limit. A test, suite, hook, config, or wait that needs longer names a numeric ' +
250
+ "constant exported by one of the app's timeout files, imported from it — never a number written in place, and " +
251
+ 'never a name that is no such constant. A timeout file that no longer exists, is not test code, or exports no ' +
252
+ 'budget fails too.',
253
+ timeoutMs: 120_000,
254
+ run(context) {
255
+ return check(context.reader, facts, context.files(SCOPE));
256
+ },
257
+ },
258
+ ];
259
+ },
260
+ };
261
+ /**
262
+ * Report-only: every test case at or over `thresholdMs` in a test file that imports no budget from
263
+ * a timeout file — the ones a busier machine pushes past the default limit. Read from the runner's
264
+ * JSON results (`--reporter=json`); a case is named by its file and full name. Never a failure.
265
+ */
266
+ export function slowTestReport(reader, facts, results, thresholdMs) {
267
+ const aliases = aliasList(facts.importAliases);
268
+ const budgetFiles = new Set(facts.timeoutFiles);
269
+ const root = reader.layout.repoRoot.replace(/\\/g, '/').replace(/\/$/, '');
270
+ const usesBudget = new Map();
271
+ const marked = (rel) => {
272
+ if (!usesBudget.has(rel)) {
273
+ let uses = false;
274
+ if (reader.exists(rel)) {
275
+ for (const site of reader.file(rel).imports)
276
+ if (site.kind === 'import' &&
277
+ budgetFiles.has(resolveSpecifier(reader, rel, site.spec, aliases)))
278
+ uses = true;
279
+ }
280
+ usesBudget.set(rel, uses);
281
+ }
282
+ return usesBudget.get(rel);
283
+ };
284
+ const out = [];
285
+ for (const result of results)
286
+ for (const testFile of result.testResults) {
287
+ const abs = testFile.name.replace(/\\/g, '/');
288
+ const rel = abs.toLowerCase().startsWith(`${root.toLowerCase()}/`)
289
+ ? abs.slice(root.length + 1)
290
+ : abs;
291
+ for (const test of testFile.assertionResults) {
292
+ const durationMs = test.duration ?? 0;
293
+ if (durationMs >= thresholdMs && !marked(rel))
294
+ out.push({ file: rel, name: test.fullName, durationMs });
295
+ }
296
+ }
297
+ return out.sort((a, b) => b.durationMs - a.durationMs || (a.file < b.file ? -1 : 1));
298
+ }
299
+ //# sourceMappingURL=test-timeouts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-timeouts.js","sourceRoot":"","sources":["../../../src/modules/repo/test-timeouts.ts"],"names":[],"mappings":"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","sourcesContent":["// A test's time limit is a named budget, never a number. Every test runs on the runner's default\n// limit; one that needs longer — real work such as spawning a media encoder — names a constant the\n// app declares in its timeout files, so the budgets live in one place, every test that needs more is\n// found by that name, and no test raises its own limit quietly to get past a slow run. The same holds\n// for a test library's own waits (`waitFor`, `findBy…`), which give up on their own clock.\n//\n// The constants are the numeric `const`s the app's timeout files export; a test uses one only by\n// importing it from that file. A number written where a limit is set — a test, suite, or hook\n// timeout, `vi.setConfig`, a wait's `timeout`, a library's `asyncUtilTimeout` — fails, and so does a\n// name that is no such constant.\n\nimport ts from 'typescript';\nimport type { Finding } from '../../guard.js';\nimport { isTestPath } from '../../node/layout.js';\nimport type { RepoReader, Scope } from '../../node/reader.js';\nimport type { GenericGuardDefinition } from '../../node/suite/config.js';\nimport { viewOfText, type FileView } from '../../reader/view.js';\nimport { forEachNode, lineOfNode, unwrap } from '../../support/ast.js';\nimport { isText, objectProblem, pathProblem } from '../../support/facts.js';\nimport { aliasList, resolveSpecifier } from '../../support/resolve-specifier.js';\n\nconst SCOPE: Scope = {\n roots: ['app', 'functions', 'scripts', 'workers'],\n kinds: ['code'],\n tests: 'only',\n optionalRoots: ['functions', 'scripts', 'workers'],\n};\n\n/** The test-runner functions whose calls set a test's or a suite's limit. */\nconst RUNNERS: ReadonlySet<string> = new Set(['it', 'test', 'describe', 'suite', 'bench']);\n/** The hooks whose second argument is their limit. */\nconst HOOKS: ReadonlySet<string> = new Set(['beforeAll', 'afterAll', 'beforeEach', 'afterEach']);\n/** The waits that take `{ timeout }` beside what they wait for. */\nconst WAITS: ReadonlySet<string> = new Set(['waitFor', 'waitForElementToBeRemoved']);\nconst isFindQuery = (name: string) => /^find(All)?By[A-Z]/.test(name);\n\n/** The name a call chain starts from: `it.skipIf(x).each(t)(…)` → `it`. */\nfunction rootName(expression: ts.Expression): string | undefined {\n let at: ts.Expression = expression;\n for (;;) {\n if (ts.isPropertyAccessExpression(at)) at = at.expression;\n else if (ts.isCallExpression(at)) at = at.expression;\n else if (ts.isTaggedTemplateExpression(at)) at = at.tag;\n else break;\n }\n return ts.isIdentifier(at) ? at.text : undefined;\n}\n\nconst dotted = (expression: ts.Expression): string | undefined =>\n ts.isPropertyAccessExpression(expression) && ts.isIdentifier(expression.expression)\n ? `${expression.expression.text}.${expression.name.text}`\n : undefined;\n\nconst ownName = (expression: ts.Expression): string | undefined =>\n ts.isIdentifier(expression)\n ? expression.text\n : ts.isPropertyAccessExpression(expression)\n ? expression.name.text\n : undefined;\n\n/** One place a limit is set: the expression that gives it, and what it limits. */\ninterface LimitSite {\n value: ts.Expression;\n what: string;\n}\n\n/** A same-file `const X = { … }`, so `waitFor(fn, WAIT)` reads the object `WAIT` holds. */\nfunction objectBehind(\n file: FileView,\n expression: ts.Expression,\n): ts.ObjectLiteralExpression | null {\n const value = unwrap(expression);\n if (ts.isObjectLiteralExpression(value)) return value;\n if (!ts.isIdentifier(value)) return null;\n let found: ts.ObjectLiteralExpression | null = null;\n forEachNode(file.sourceFile, (node) => {\n if (\n !found &&\n ts.isVariableDeclaration(node) &&\n ts.isIdentifier(node.name) &&\n node.name.text === value.text &&\n node.initializer &&\n ts.isObjectLiteralExpression(unwrap(node.initializer))\n )\n found = unwrap(node.initializer) as ts.ObjectLiteralExpression;\n });\n return found;\n}\n\nfunction limitsIn(\n object: ts.ObjectLiteralExpression,\n keys: readonly string[],\n what: string,\n): LimitSite[] {\n const out: LimitSite[] = [];\n for (const prop of object.properties) {\n if (\n ts.isPropertyAssignment(prop) &&\n ts.isIdentifier(prop.name) &&\n keys.includes(prop.name.text)\n )\n out.push({ value: prop.initializer, what: `${what} ${prop.name.text}` });\n else if (ts.isShorthandPropertyAssignment(prop) && keys.includes(prop.name.text))\n out.push({ value: prop.name, what: `${what} ${prop.name.text}` });\n }\n return out;\n}\n\n/** Every place a test file sets a limit. A string is read as a sample test file. */\nexport function limitSites(source: FileView | string): Array<LimitSite & { node: ts.Node }> {\n const file = typeof source === 'string' ? viewOfText('sample.test.ts', source) : source;\n const out: Array<LimitSite & { node: ts.Node }> = [];\n const add = (sites: LimitSite[], node: ts.Node) => {\n for (const site of sites) out.push({ ...site, node });\n };\n forEachNode(file.sourceFile, (node) => {\n if (!ts.isCallExpression(node)) return;\n const callee = node.expression;\n const root = rootName(callee);\n const name = ownName(callee);\n const call = dotted(callee);\n if (call === 'vi.setConfig' || call === 'jest.setTimeout') {\n if (call === 'jest.setTimeout' && node.arguments[0])\n add([{ value: node.arguments[0], what: 'the test timeout' }], node);\n const object = node.arguments[0] && objectBehind(file, node.arguments[0]);\n if (object) add(limitsIn(object, ['testTimeout', 'hookTimeout'], 'the config'), node);\n return;\n }\n if (name === 'configure' && node.arguments[0]) {\n const object = objectBehind(file, node.arguments[0]);\n if (object) add(limitsIn(object, ['asyncUtilTimeout'], 'the test library'), node);\n return;\n }\n if (root && HOOKS.has(root) && ts.isIdentifier(callee) && node.arguments[1]) {\n add([{ value: node.arguments[1], what: `the ${root} timeout` }], node);\n return;\n }\n if (root && RUNNERS.has(root) && !(name && WAITS.has(name))) {\n // The title comes first; a limit is a later number, or a later options object's `timeout`.\n for (const arg of node.arguments.slice(1)) {\n const value = unwrap(arg);\n if (ts.isArrowFunction(value) || ts.isFunctionExpression(value)) continue;\n if (ts.isObjectLiteralExpression(value)) {\n add(limitsIn(value, ['timeout'], `the ${root} option`), node);\n continue;\n }\n if (\n ts.isIdentifier(value) ||\n ts.isNumericLiteral(value) ||\n ts.isBinaryExpression(value) ||\n ts.isPrefixUnaryExpression(value)\n )\n add([{ value: arg, what: `the ${root} timeout` }], node);\n }\n return;\n }\n if (name && (WAITS.has(name) || isFindQuery(name))) {\n for (const arg of node.arguments.slice(1)) {\n const object = objectBehind(file, arg);\n if (object) add(limitsIn(object, ['timeout'], `the ${name} wait`), node);\n }\n }\n });\n return out;\n}\n\nexport interface TestTimeoutFacts {\n /** Specifier prefix → repo-relative folder (`{ '@/': 'src/' }`), so an import names its file. */\n importAliases: Readonly<Record<string, string>>;\n /**\n * The repo-relative test-code files that declare the timeout constants: each exported numeric\n * `const` is one budget a test may name. `[]` when the app declares none — then no test sets a\n * limit of its own.\n */\n timeoutFiles: readonly string[];\n}\n\n/** The numeric `const`s a timeout file exports. */\nfunction exportedBudgets(file: FileView): Set<string> {\n const names = new Set<string>();\n for (const statement of file.sourceFile.statements) {\n if (\n !ts.isVariableStatement(statement) ||\n !(ts.getModifiers(statement) ?? []).some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ||\n !(statement.declarationList.flags & ts.NodeFlags.Const)\n )\n continue;\n for (const d of statement.declarationList.declarations) {\n const value = d.initializer && unwrap(d.initializer);\n if (ts.isIdentifier(d.name) && value && ts.isNumericLiteral(value)) names.add(d.name.text);\n }\n }\n return names;\n}\n\nfunction check(reader: RepoReader, facts: TestTimeoutFacts, files: FileView[]): Finding[] {\n const findings: Finding[] = [];\n const budgetsOf = new Map<string, Set<string>>();\n for (const path of facts.timeoutFiles) {\n if (!reader.exists(path)) {\n findings.push({ file: path, detail: `the timeout file ${path} no longer exists` });\n continue;\n }\n if (!isTestPath(reader.layout, path)) {\n findings.push({\n file: path,\n detail: `the timeout file ${path} is not test code — the budgets live with the tests`,\n });\n continue;\n }\n const budgets = exportedBudgets(reader.file(path));\n if (!budgets.size)\n findings.push({\n file: path,\n detail: `the timeout file ${path} exports no numeric const budget`,\n });\n budgetsOf.set(path, budgets);\n }\n const aliases = aliasList(facts.importAliases);\n for (const file of files) {\n if (budgetsOf.has(file.path)) continue;\n const sites = limitSites(file);\n if (!sites.length) continue;\n // The budgets this file imports, by local name.\n const imported = new Set<string>();\n for (const site of file.imports) {\n if (site.kind !== 'import') continue;\n const owner = resolveSpecifier(reader, file.path, site.spec, aliases);\n const budgets = budgetsOf.get(owner);\n if (!budgets) continue;\n for (const named of site.named) if (budgets.has(named.imported)) imported.add(named.local);\n }\n for (const site of sites) {\n const value = unwrap(site.value);\n if (ts.isIdentifier(value) && imported.has(value.text)) continue;\n findings.push({\n file: file.path,\n line: lineOfNode(file, site.node),\n detail: ts.isIdentifier(value)\n ? `${site.what} is ${value.text}, which is no budget imported from a timeout file — name one of its constants`\n : `${site.what} is written as a number — leave the default, or name a budget from a timeout file`,\n });\n }\n }\n return findings;\n}\n\nfunction factProblems(facts: TestTimeoutFacts): string[] {\n const problems: string[] = [];\n if (!objectProblem(facts, 'the facts', problems)) return problems;\n if (objectProblem(facts.importAliases, 'importAliases', problems))\n for (const [prefix, folder] of Object.entries(facts.importAliases))\n if (!prefix || !isText(folder))\n problems.push(`importAliases ${prefix} needs a target folder`);\n if (!Array.isArray(facts.timeoutFiles))\n problems.push('timeoutFiles is missing (declare [] when there is none)');\n else {\n const seen = new Set<string>();\n for (const path of facts.timeoutFiles as unknown[]) {\n pathProblem(path, 'a timeoutFiles entry', problems);\n if (isText(path) && seen.has(path)) problems.push(`timeoutFiles lists ${path} twice`);\n if (isText(path)) seen.add(path);\n }\n }\n return problems;\n}\n\nexport const testTimeoutGuard: GenericGuardDefinition<TestTimeoutFacts> = {\n id: 'testTimeouts',\n absentWhen: false,\n factProblems,\n build(facts) {\n return [\n {\n title: 'a test sets a time limit only by naming a budget from a timeout file',\n description:\n 'Every test runs on the default limit. A test, suite, hook, config, or wait that needs longer names a numeric ' +\n \"constant exported by one of the app's timeout files, imported from it — never a number written in place, and \" +\n 'never a name that is no such constant. A timeout file that no longer exists, is not test code, or exports no ' +\n 'budget fails too.',\n timeoutMs: 120_000,\n run(context) {\n return check(context.reader, facts, context.files(SCOPE));\n },\n },\n ];\n },\n};\n\nexport interface SlowTestCase {\n file: string;\n name: string;\n durationMs: number;\n}\n\n/**\n * Report-only: every test case at or over `thresholdMs` in a test file that imports no budget from\n * a timeout file — the ones a busier machine pushes past the default limit. Read from the runner's\n * JSON results (`--reporter=json`); a case is named by its file and full name. Never a failure.\n */\nexport function slowTestReport(\n reader: RepoReader,\n facts: TestTimeoutFacts,\n results: ReadonlyArray<{\n testResults: ReadonlyArray<{\n name: string;\n assertionResults: ReadonlyArray<{ fullName: string; duration?: number | null }>;\n }>;\n }>,\n thresholdMs: number,\n): SlowTestCase[] {\n const aliases = aliasList(facts.importAliases);\n const budgetFiles = new Set(facts.timeoutFiles);\n const root = reader.layout.repoRoot.replace(/\\\\/g, '/').replace(/\\/$/, '');\n const usesBudget = new Map<string, boolean>();\n const marked = (rel: string): boolean => {\n if (!usesBudget.has(rel)) {\n let uses = false;\n if (reader.exists(rel)) {\n for (const site of reader.file(rel).imports)\n if (\n site.kind === 'import' &&\n budgetFiles.has(resolveSpecifier(reader, rel, site.spec, aliases))\n )\n uses = true;\n }\n usesBudget.set(rel, uses);\n }\n return usesBudget.get(rel)!;\n };\n const out: SlowTestCase[] = [];\n for (const result of results)\n for (const testFile of result.testResults) {\n const abs = testFile.name.replace(/\\\\/g, '/');\n const rel = abs.toLowerCase().startsWith(`${root.toLowerCase()}/`)\n ? abs.slice(root.length + 1)\n : abs;\n for (const test of testFile.assertionResults) {\n const durationMs = test.duration ?? 0;\n if (durationMs >= thresholdMs && !marked(rel))\n out.push({ file: rel, name: test.fullName, durationMs });\n }\n }\n return out.sort((a, b) => b.durationMs - a.durationMs || (a.file < b.file ? -1 : 1));\n}\n"]}
@@ -1,2 +1,2 @@
1
- export { duplicateExportsReport, sharedFakeReport, unusedIndexMemberReport, writeGuardCoverageReport, } from './reports.js';
1
+ export { duplicateExportsReport, sharedFakeReport, slowTestReport, unusedIndexMemberReport, writeGuardCoverageReport, } from './reports.js';
2
2
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/node/reports/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,gBAAgB,EAChB,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/node/reports/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,gBAAgB,EAChB,cAAc,EACd,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,cAAc,CAAC"}
@@ -1,2 +1,2 @@
1
- export { duplicateExportsReport, sharedFakeReport, unusedIndexMemberReport, writeGuardCoverageReport, } from './reports.js';
1
+ export { duplicateExportsReport, sharedFakeReport, slowTestReport, unusedIndexMemberReport, writeGuardCoverageReport, } from './reports.js';
2
2
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/node/reports/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,gBAAgB,EAChB,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,cAAc,CAAC","sourcesContent":["export {\n duplicateExportsReport,\n sharedFakeReport,\n unusedIndexMemberReport,\n writeGuardCoverageReport,\n} from './reports.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/node/reports/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,gBAAgB,EAChB,cAAc,EACd,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,cAAc,CAAC","sourcesContent":["export {\n duplicateExportsReport,\n sharedFakeReport,\n slowTestReport,\n unusedIndexMemberReport,\n writeGuardCoverageReport,\n} from './reports.js';\n"]}
@@ -3,6 +3,7 @@ import { type WriteGuardFacts } from '../../modules/firestore/guard.js';
3
3
  import { type ImportPathFacts } from '../../modules/import-paths/guard.js';
4
4
  import { type DuplicateExportFacts } from '../../modules/repo/duplicate-exports.js';
5
5
  import { type SharedFakeFacts } from '../../modules/repo/shared-fakes.js';
6
+ import { type TestTimeoutFacts } from '../../modules/repo/test-timeouts.js';
6
7
  /** Folder index members nothing outside the folder imports — candidates to drop from the index. */
7
8
  export declare function unusedIndexMemberReport(layout: RepoLayout, facts: ImportPathFacts): string;
8
9
  /**
@@ -10,6 +11,12 @@ export declare function unusedIndexMemberReport(layout: RepoLayout, facts: Impor
10
11
  * candidates for a shared fake or helper. Never a gate failure.
11
12
  */
12
13
  export declare function sharedFakeReport(layout: RepoLayout, facts: SharedFakeFacts): string;
14
+ /**
15
+ * Test cases at or over `thresholdMs` in files that name no timeout budget — the ones a busier
16
+ * machine pushes past the default limit — read from the test runner's JSON results files
17
+ * (`--reporter=json --outputFile=…`, absolute paths). Never a gate failure.
18
+ */
19
+ export declare function slowTestReport(layout: RepoLayout, facts: TestTimeoutFacts, resultFiles: readonly string[], thresholdMs?: number): string;
13
20
  /**
14
21
  * The write guard's full coverage table — writes seen, targets resolved, payloads parsed and
15
22
  * verified, exceptions applied, violations — per writer root. `failed` is what the gate would say.
@@ -1 +1 @@
1
- {"version":3,"file":"reports.d.ts","sourceRoot":"","sources":["../../../src/node/reports/reports.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/C,OAAO,EAAiB,KAAK,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACvF,OAAO,EAAsB,KAAK,eAAe,EAAE,MAAM,qCAAqC,CAAC;AAC/F,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,oCAAoC,CAAC;AAE5C,mGAAmG;AACnG,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,eAAe,GAAG,MAAM,CAQ1F;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,eAAe,GAAG,MAAM,CAcnF;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC,CAM5C;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,oBAAoB,GAC1B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAUlC"}
1
+ {"version":3,"file":"reports.d.ts","sourceRoot":"","sources":["../../../src/node/reports/reports.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/C,OAAO,EAAiB,KAAK,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACvF,OAAO,EAAsB,KAAK,eAAe,EAAE,MAAM,qCAAqC,CAAC;AAC/F,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAEL,KAAK,gBAAgB,EACtB,MAAM,qCAAqC,CAAC;AAE7C,mGAAmG;AACnG,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,eAAe,GAAG,MAAM,CAQ1F;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,eAAe,GAAG,MAAM,CAcnF;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,gBAAgB,EACvB,WAAW,EAAE,SAAS,MAAM,EAAE,EAC9B,WAAW,SAAO,GACjB,MAAM,CASR;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC,CAM5C;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,oBAAoB,GAC1B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAUlC"}
@@ -1,10 +1,12 @@
1
1
  // Reports run by hand, never in the gate: what they list is a judgment call, not a boundary.
2
+ import fs from 'node:fs';
2
3
  import { createRepoReader } from '../reader.js';
3
4
  import { formatReport as formatWriteGuardReport } from '../../modules/firestore/analyzer.js';
4
5
  import { runWriteGuard } from '../../modules/firestore/guard.js';
5
6
  import { unusedIndexMembers } from '../../modules/import-paths/guard.js';
6
7
  import { duplicateExportsReport as runDuplicateExports, } from '../../modules/repo/duplicate-exports.js';
7
8
  import { sharedFakeReport as runSharedFakeReport, } from '../../modules/repo/shared-fakes.js';
9
+ import { slowTestReport as runSlowTestReport, } from '../../modules/repo/test-timeouts.js';
8
10
  /** Folder index members nothing outside the folder imports — candidates to drop from the index. */
9
11
  export function unusedIndexMemberReport(layout, facts) {
10
12
  const report = unusedIndexMembers(createRepoReader(layout), facts);
@@ -36,6 +38,21 @@ export function sharedFakeReport(layout, facts) {
36
38
  }
37
39
  return lines.join('\n');
38
40
  }
41
+ /**
42
+ * Test cases at or over `thresholdMs` in files that name no timeout budget — the ones a busier
43
+ * machine pushes past the default limit — read from the test runner's JSON results files
44
+ * (`--reporter=json --outputFile=…`, absolute paths). Never a gate failure.
45
+ */
46
+ export function slowTestReport(layout, facts, resultFiles, thresholdMs = 1000) {
47
+ const results = resultFiles.map((file) => JSON.parse(fs.readFileSync(file, 'utf8')));
48
+ const slow = runSlowTestReport(createRepoReader(layout), facts, results, thresholdMs);
49
+ if (!slow.length)
50
+ return `slow tests: none — every case at or over ${thresholdMs} ms names a timeout budget.`;
51
+ return [
52
+ `slow tests (report only) — cases at or over ${thresholdMs} ms whose file names no timeout budget:`,
53
+ ...slow.map((c) => ` ${Math.round(c.durationMs)} ms ${c.file} > ${c.name}`),
54
+ ].join('\n');
55
+ }
39
56
  /**
40
57
  * The write guard's full coverage table — writes seen, targets resolved, payloads parsed and
41
58
  * verified, exceptions applied, violations — per writer root. `failed` is what the gate would say.
@@ -1 +1 @@
1
- {"version":3,"file":"reports.js","sourceRoot":"","sources":["../../../src/node/reports/reports.ts"],"names":[],"mappings":"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","sourcesContent":["// Reports run by hand, never in the gate: what they list is a judgment call, not a boundary.\n\nimport { createRepoReader } from '../reader.js';\nimport type { RepoLayout } from '../layout.js';\nimport { formatReport as formatWriteGuardReport } from '../../modules/firestore/analyzer.js';\nimport { runWriteGuard, type WriteGuardFacts } from '../../modules/firestore/guard.js';\nimport { unusedIndexMembers, type ImportPathFacts } from '../../modules/import-paths/guard.js';\nimport {\n duplicateExportsReport as runDuplicateExports,\n type DuplicateExportFacts,\n} from '../../modules/repo/duplicate-exports.js';\nimport {\n sharedFakeReport as runSharedFakeReport,\n type SharedFakeFacts,\n} from '../../modules/repo/shared-fakes.js';\n\n/** Folder index members nothing outside the folder imports — candidates to drop from the index. */\nexport function unusedIndexMemberReport(layout: RepoLayout, facts: ImportPathFacts): string {\n const report = unusedIndexMembers(createRepoReader(layout), facts);\n if (!report.length)\n return 'unused index members: none — every index member is imported from outside its folder.';\n return [\n 'unused index members (report only):',\n ...report.map((r) => ` ${r.file}: ${r.unused.join(', ')}`),\n ].join('\\n');\n}\n\n/**\n * Modules faked, and helpers declared, in several test files of one tree with no shared owner there —\n * candidates for a shared fake or helper. Never a gate failure.\n */\nexport function sharedFakeReport(layout: RepoLayout, facts: SharedFakeFacts): string {\n const { modules, helpers } = runSharedFakeReport(createRepoReader(layout), facts);\n if (!modules.length && !helpers.length)\n return 'shared fakes: none — no module or helper is copied across test files without an owner.';\n const lines = ['shared fakes (report only):'];\n if (modules.length) {\n lines.push(' modules faked in several test files with no shared fake:');\n for (const m of modules) lines.push(` ${m.root}: ${m.module} — ${m.files.length} files`);\n }\n if (helpers.length) {\n lines.push(' helpers declared in several test files with no shared owner:');\n for (const h of helpers) lines.push(` ${h.root}: ${h.name} — ${h.files.length} files`);\n }\n return lines.join('\\n');\n}\n\n/**\n * The write guard's full coverage table — writes seen, targets resolved, payloads parsed and\n * verified, exceptions applied, violations — per writer root. `failed` is what the gate would say.\n */\nexport async function writeGuardCoverageReport(\n layout: RepoLayout,\n facts: WriteGuardFacts,\n): Promise<{ text: string; failed: boolean }> {\n const result = await runWriteGuard(createRepoReader(layout), facts);\n return {\n text: formatWriteGuardReport(result),\n failed: result.violations.length > 0 || result.staleExceptions.length > 0,\n };\n}\n\n/**\n * Exported names declared in more than one module, outside the reviewed legitimate ones. Whether two\n * exports are one concept is a judgment, so this never fails a gate: each listed name is either\n * consolidated onto its owner or added to the reviewed list with its why.\n */\nexport function duplicateExportsReport(\n layout: RepoLayout,\n facts: DuplicateExportFacts,\n): { text: string; clean: boolean } {\n const { text, result } = runDuplicateExports(createRepoReader(layout), facts);\n const groups = [\n result.appValues,\n result.functionsValues,\n result.appTypes,\n result.functionsTypes,\n result.crossTree,\n ];\n return { text, clean: groups.every((g) => g.length === 0) };\n}\n"]}
1
+ {"version":3,"file":"reports.js","sourceRoot":"","sources":["../../../src/node/reports/reports.ts"],"names":[],"mappings":"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","sourcesContent":["// Reports run by hand, never in the gate: what they list is a judgment call, not a boundary.\n\nimport fs from 'node:fs';\nimport { createRepoReader } from '../reader.js';\nimport type { RepoLayout } from '../layout.js';\nimport { formatReport as formatWriteGuardReport } from '../../modules/firestore/analyzer.js';\nimport { runWriteGuard, type WriteGuardFacts } from '../../modules/firestore/guard.js';\nimport { unusedIndexMembers, type ImportPathFacts } from '../../modules/import-paths/guard.js';\nimport {\n duplicateExportsReport as runDuplicateExports,\n type DuplicateExportFacts,\n} from '../../modules/repo/duplicate-exports.js';\nimport {\n sharedFakeReport as runSharedFakeReport,\n type SharedFakeFacts,\n} from '../../modules/repo/shared-fakes.js';\nimport {\n slowTestReport as runSlowTestReport,\n type TestTimeoutFacts,\n} from '../../modules/repo/test-timeouts.js';\n\n/** Folder index members nothing outside the folder imports — candidates to drop from the index. */\nexport function unusedIndexMemberReport(layout: RepoLayout, facts: ImportPathFacts): string {\n const report = unusedIndexMembers(createRepoReader(layout), facts);\n if (!report.length)\n return 'unused index members: none — every index member is imported from outside its folder.';\n return [\n 'unused index members (report only):',\n ...report.map((r) => ` ${r.file}: ${r.unused.join(', ')}`),\n ].join('\\n');\n}\n\n/**\n * Modules faked, and helpers declared, in several test files of one tree with no shared owner there —\n * candidates for a shared fake or helper. Never a gate failure.\n */\nexport function sharedFakeReport(layout: RepoLayout, facts: SharedFakeFacts): string {\n const { modules, helpers } = runSharedFakeReport(createRepoReader(layout), facts);\n if (!modules.length && !helpers.length)\n return 'shared fakes: none — no module or helper is copied across test files without an owner.';\n const lines = ['shared fakes (report only):'];\n if (modules.length) {\n lines.push(' modules faked in several test files with no shared fake:');\n for (const m of modules) lines.push(` ${m.root}: ${m.module} — ${m.files.length} files`);\n }\n if (helpers.length) {\n lines.push(' helpers declared in several test files with no shared owner:');\n for (const h of helpers) lines.push(` ${h.root}: ${h.name} — ${h.files.length} files`);\n }\n return lines.join('\\n');\n}\n\n/**\n * Test cases at or over `thresholdMs` in files that name no timeout budget — the ones a busier\n * machine pushes past the default limit — read from the test runner's JSON results files\n * (`--reporter=json --outputFile=…`, absolute paths). Never a gate failure.\n */\nexport function slowTestReport(\n layout: RepoLayout,\n facts: TestTimeoutFacts,\n resultFiles: readonly string[],\n thresholdMs = 1000,\n): string {\n const results = resultFiles.map((file) => JSON.parse(fs.readFileSync(file, 'utf8')));\n const slow = runSlowTestReport(createRepoReader(layout), facts, results, thresholdMs);\n if (!slow.length)\n return `slow tests: none — every case at or over ${thresholdMs} ms names a timeout budget.`;\n return [\n `slow tests (report only) — cases at or over ${thresholdMs} ms whose file names no timeout budget:`,\n ...slow.map((c) => ` ${Math.round(c.durationMs)} ms ${c.file} > ${c.name}`),\n ].join('\\n');\n}\n\n/**\n * The write guard's full coverage table — writes seen, targets resolved, payloads parsed and\n * verified, exceptions applied, violations — per writer root. `failed` is what the gate would say.\n */\nexport async function writeGuardCoverageReport(\n layout: RepoLayout,\n facts: WriteGuardFacts,\n): Promise<{ text: string; failed: boolean }> {\n const result = await runWriteGuard(createRepoReader(layout), facts);\n return {\n text: formatWriteGuardReport(result),\n failed: result.violations.length > 0 || result.staleExceptions.length > 0,\n };\n}\n\n/**\n * Exported names declared in more than one module, outside the reviewed legitimate ones. Whether two\n * exports are one concept is a judgment, so this never fails a gate: each listed name is either\n * consolidated onto its owner or added to the reviewed list with its why.\n */\nexport function duplicateExportsReport(\n layout: RepoLayout,\n facts: DuplicateExportFacts,\n): { text: string; clean: boolean } {\n const { text, result } = runDuplicateExports(createRepoReader(layout), facts);\n const groups = [\n result.appValues,\n result.functionsValues,\n result.appTypes,\n result.functionsTypes,\n result.crossTree,\n ];\n return { text, clean: groups.every((g) => g.length === 0) };\n}\n"]}
@@ -0,0 +1,11 @@
1
+ import type { RepoReader } from '../node/reader.js';
2
+ /** A path names a local module file when its last segment carries a code extension. */
3
+ export declare const isLocalModule: (path: string) => boolean;
4
+ /** An app's import aliases, longest prefix first, so `@/a/` wins over `@/`. */
5
+ export declare const aliasList: (importAliases: Readonly<Record<string, string>>) => Array<[string, string]>;
6
+ /**
7
+ * What a specifier names, read from `fromPath`: a local module's repo-relative file (or, when no file
8
+ * answers, the path it reached), or the package specifier itself.
9
+ */
10
+ export declare function resolveSpecifier(reader: RepoReader, fromPath: string, spec: string, aliases: ReadonlyArray<[string, string]>): string;
11
+ //# sourceMappingURL=resolve-specifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-specifier.d.ts","sourceRoot":"","sources":["../../src/support/resolve-specifier.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAmBpD,uFAAuF;AACvF,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,KAAG,OAAsD,CAAC;AAEpG,+EAA+E;AAC/E,eAAO,MAAM,SAAS,GACpB,eAAe,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,KAC9C,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CACgD,CAAC;AAE1E;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,UAAU,EAClB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,aAAa,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GACvC,MAAM,CAiBR"}
@@ -0,0 +1,48 @@
1
+ // What a module specifier names, read through the reader's disk: the one resolver the guards that
2
+ // match an import against a named file share. Internal to the package: no public entry exports it.
3
+ import { kindOfFile } from '../node/layout.js';
4
+ import { normalizePath, resolveRelative } from './paths.js';
5
+ const CODE_EXTENSIONS = ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs'];
6
+ // A specifier names a TypeScript file by its built extension: `./x.js` names `x.ts`.
7
+ const BUILT_TO_SOURCE = {
8
+ '.js': ['.ts', '.tsx'],
9
+ '.jsx': ['.tsx'],
10
+ '.mjs': ['.mts'],
11
+ '.cjs': ['.cts'],
12
+ };
13
+ const nameOf = (p) => p.slice(p.lastIndexOf('/') + 1);
14
+ const extensionOf = (p) => {
15
+ const name = nameOf(p);
16
+ const dot = name.lastIndexOf('.');
17
+ return dot <= 0 ? '' : name.slice(dot);
18
+ };
19
+ /** A path names a local module file when its last segment carries a code extension. */
20
+ export const isLocalModule = (path) => CODE_EXTENSIONS.includes(extensionOf(path));
21
+ /** An app's import aliases, longest prefix first, so `@/a/` wins over `@/`. */
22
+ export const aliasList = (importAliases) => Object.entries(importAliases).sort((a, b) => b[0].length - a[0].length);
23
+ /**
24
+ * What a specifier names, read from `fromPath`: a local module's repo-relative file (or, when no file
25
+ * answers, the path it reached), or the package specifier itself.
26
+ */
27
+ export function resolveSpecifier(reader, fromPath, spec, aliases) {
28
+ let base = null;
29
+ for (const [prefix, folder] of aliases) {
30
+ if (spec === prefix.replace(/\/$/, '') || spec.startsWith(prefix)) {
31
+ base = normalizePath(`${folder}/${spec.slice(prefix.length)}`).path;
32
+ break;
33
+ }
34
+ }
35
+ base ??= resolveRelative(fromPath, spec)?.path ?? null;
36
+ if (base === null)
37
+ return spec;
38
+ const candidates = [base];
39
+ const ext = extensionOf(base);
40
+ for (const source of BUILT_TO_SOURCE[ext] ?? [])
41
+ candidates.push(base.slice(0, -ext.length) + source);
42
+ for (const e of CODE_EXTENSIONS)
43
+ candidates.push(base + e);
44
+ for (const e of CODE_EXTENSIONS)
45
+ candidates.push(`${base}/index${e}`);
46
+ return candidates.find((c) => kindOfFile(nameOf(c)) === 'code' && reader.exists(c)) ?? base;
47
+ }
48
+ //# sourceMappingURL=resolve-specifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-specifier.js","sourceRoot":"","sources":["../../src/support/resolve-specifier.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,mGAAmG;AAEnG,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE/C,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE5D,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AACvF,qFAAqF;AACrF,MAAM,eAAe,GAAgD;IACnE,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,CAAC,MAAM,CAAC;IAChB,MAAM,EAAE,CAAC,MAAM,CAAC;IAChB,MAAM,EAAE,CAAC,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;AAC9D,MAAM,WAAW,GAAG,CAAC,CAAS,EAAE,EAAE;IAChC,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACvB,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAClC,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;AACzC,CAAC,CAAC;AAEF,uFAAuF;AACvF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,IAAY,EAAW,EAAE,CAAC,eAAe,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;AAEpG,+EAA+E;AAC/E,MAAM,CAAC,MAAM,SAAS,GAAG,CACvB,aAA+C,EACtB,EAAE,CAC3B,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1E;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAC9B,MAAkB,EAClB,QAAgB,EAChB,IAAY,EACZ,OAAwC;IAExC,IAAI,IAAI,GAAkB,IAAI,CAAC;IAC/B,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACvC,IAAI,IAAI,KAAK,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YAClE,IAAI,GAAG,aAAa,CAAC,GAAG,MAAM,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC;YACpE,MAAM;QACR,CAAC;IACH,CAAC;IACD,IAAI,KAAK,eAAe,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,IAAI,IAAI,IAAI,CAAC;IACvD,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/B,MAAM,UAAU,GAAG,CAAC,IAAI,CAAC,CAAC;IAC1B,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAC9B,KAAK,MAAM,MAAM,IAAI,eAAe,CAAC,GAAG,CAAC,IAAI,EAAE;QAC7C,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC;IACvD,KAAK,MAAM,CAAC,IAAI,eAAe;QAAE,UAAU,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;IAC3D,KAAK,MAAM,CAAC,IAAI,eAAe;QAAE,UAAU,CAAC,IAAI,CAAC,GAAG,IAAI,SAAS,CAAC,EAAE,CAAC,CAAC;IACtE,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;AAC9F,CAAC","sourcesContent":["// What a module specifier names, read through the reader's disk: the one resolver the guards that\n// match an import against a named file share. Internal to the package: no public entry exports it.\n\nimport { kindOfFile } from '../node/layout.js';\nimport type { RepoReader } from '../node/reader.js';\nimport { normalizePath, resolveRelative } from './paths.js';\n\nconst CODE_EXTENSIONS = ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs'];\n// A specifier names a TypeScript file by its built extension: `./x.js` names `x.ts`.\nconst BUILT_TO_SOURCE: Readonly<Record<string, readonly string[]>> = {\n '.js': ['.ts', '.tsx'],\n '.jsx': ['.tsx'],\n '.mjs': ['.mts'],\n '.cjs': ['.cts'],\n};\n\nconst nameOf = (p: string) => p.slice(p.lastIndexOf('/') + 1);\nconst extensionOf = (p: string) => {\n const name = nameOf(p);\n const dot = name.lastIndexOf('.');\n return dot <= 0 ? '' : name.slice(dot);\n};\n\n/** A path names a local module file when its last segment carries a code extension. */\nexport const isLocalModule = (path: string): boolean => CODE_EXTENSIONS.includes(extensionOf(path));\n\n/** An app's import aliases, longest prefix first, so `@/a/` wins over `@/`. */\nexport const aliasList = (\n importAliases: Readonly<Record<string, string>>,\n): Array<[string, string]> =>\n Object.entries(importAliases).sort((a, b) => b[0].length - a[0].length);\n\n/**\n * What a specifier names, read from `fromPath`: a local module's repo-relative file (or, when no file\n * answers, the path it reached), or the package specifier itself.\n */\nexport function resolveSpecifier(\n reader: RepoReader,\n fromPath: string,\n spec: string,\n aliases: ReadonlyArray<[string, string]>,\n): string {\n let base: string | null = null;\n for (const [prefix, folder] of aliases) {\n if (spec === prefix.replace(/\\/$/, '') || spec.startsWith(prefix)) {\n base = normalizePath(`${folder}/${spec.slice(prefix.length)}`).path;\n break;\n }\n }\n base ??= resolveRelative(fromPath, spec)?.path ?? null;\n if (base === null) return spec;\n const candidates = [base];\n const ext = extensionOf(base);\n for (const source of BUILT_TO_SOURCE[ext] ?? [])\n candidates.push(base.slice(0, -ext.length) + source);\n for (const e of CODE_EXTENSIONS) candidates.push(base + e);\n for (const e of CODE_EXTENSIONS) candidates.push(`${base}/index${e}`);\n return candidates.find((c) => kindOfFile(nameOf(c)) === 'code' && reader.exists(c)) ?? base;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttt-productions/repo-guards",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/ttt-productions/ttt-packages.git",