@ttt-productions/repo-guards 0.1.6 → 0.1.7

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.
@@ -9,6 +9,7 @@ import { type LocalOnlyVariablesFacts } from './local-only-variables.js';
9
9
  import { type ProjectScriptFacts } from './project-scripts.js';
10
10
  import { type SecretBindingFacts } from './secret-bindings.js';
11
11
  import { type ShellSafeFacts } from './shell-safe.js';
12
+ import { type TestCaseModuleLoadFacts } from './test-case-module-loads.js';
12
13
  import { type TestSourceRootFacts } from './test-source-root.js';
13
14
  import { type WalkerReadFailureFacts } from './walker-read-failure.js';
14
15
  import { type WorkerBoundedBodyFacts } from './worker-bounded-body.js';
@@ -18,6 +19,7 @@ export interface RepoGuardFacts {
18
19
  shellSafeChildProcesses: ShellSafeFacts;
19
20
  walkerReadFailure: WalkerReadFailureFacts;
20
21
  testSourceRoot: TestSourceRootFacts | Absent;
22
+ testCaseModuleLoads: TestCaseModuleLoadFacts;
21
23
  localOnlyVariables: LocalOnlyVariablesFacts;
22
24
  workerConfigIsolation: WorkerConfigFacts | Absent;
23
25
  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,EAAkB,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtE,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,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,CAkBnB,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,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,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,CAmBnB,CAAC"}
@@ -10,6 +10,7 @@ import { localOnlyVariablesGuard } from './local-only-variables.js';
10
10
  import { projectScriptGuard } from './project-scripts.js';
11
11
  import { secretBindingGuard } from './secret-bindings.js';
12
12
  import { shellSafeGuard } from './shell-safe.js';
13
+ import { testCaseModuleLoadGuard } from './test-case-module-loads.js';
13
14
  import { testSourceRootGuard } from './test-source-root.js';
14
15
  import { walkerReadFailureGuard } from './walker-read-failure.js';
15
16
  import { workerBoundedBodyGuard } from './worker-bounded-body.js';
@@ -18,6 +19,7 @@ export const REPO_GUARDS = Object.freeze([
18
19
  shellSafeGuard,
19
20
  walkerReadFailureGuard,
20
21
  testSourceRootGuard,
22
+ testCaseModuleLoadGuard,
21
23
  localOnlyVariablesGuard,
22
24
  workerConfigGuard,
23
25
  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,cAAc,EAAuB,MAAM,iBAAiB,CAAC;AACtE,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;AAuB/E,MAAM,CAAC,MAAM,WAAW,GAAiD,MAAM,CAAC,MAAM,CAAC;IACrF,cAAc;IACd,sBAAsB;IACtB,mBAAmB;IACnB,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 { shellSafeGuard, type ShellSafeFacts } from './shell-safe.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 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 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,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;AAwB/E,MAAM,CAAC,MAAM,WAAW,GAAiD,MAAM,CAAC,MAAM,CAAC;IACrF,cAAc;IACd,sBAAsB;IACtB,mBAAmB;IACnB,uBAAuB;IACvB,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 { 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 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 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"]}
@@ -0,0 +1,28 @@
1
+ import type { GenericGuardDefinition } from '../../node/suite/config.js';
2
+ import { type FileView } from '../../reader/view.js';
3
+ export interface TestCaseModuleLoad {
4
+ index: number;
5
+ detail: string;
6
+ }
7
+ /**
8
+ * Every place a test file loads code inside a case: a module-registry reset, or a dynamic `import()`
9
+ * of a local module run from a case, a hook, or a helper. A string is read as a sample test file.
10
+ */
11
+ export declare function testCaseModuleLoads(source: FileView | string, aliases: readonly string[]): TestCaseModuleLoad[];
12
+ export interface TestCaseModuleLoadFacts {
13
+ /**
14
+ * The prefixes the app's own import aliases begin with (`@/`), beside relative paths; `[]` when
15
+ * the app has none.
16
+ */
17
+ localAliases: readonly string[];
18
+ /**
19
+ * The test files that must load a module fresh per case, each with its why (code that reads its
20
+ * environment once at load, tested under several environments). One that no longer does is stale.
21
+ */
22
+ reviewed: ReadonlyArray<{
23
+ path: string;
24
+ why: string;
25
+ }>;
26
+ }
27
+ export declare const testCaseModuleLoadGuard: GenericGuardDefinition<TestCaseModuleLoadFacts>;
28
+ //# sourceMappingURL=test-case-module-loads.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-case-module-loads.d.ts","sourceRoot":"","sources":["../../../src/modules/repo/test-case-module-loads.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAGzE,OAAO,EAAc,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAwCjE,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,QAAQ,GAAG,MAAM,EACzB,OAAO,EAAE,SAAS,MAAM,EAAE,GACzB,kBAAkB,EAAE,CAoBtB;AASD,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC;;;OAGG;IACH,QAAQ,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACxD;AAED,eAAO,MAAM,uBAAuB,EAAE,sBAAsB,CAAC,uBAAuB,CAmDnF,CAAC"}
@@ -0,0 +1,128 @@
1
+ // A test loads the code it tests once, when its file loads — never inside a test case. A dynamic
2
+ // `import()` of a local module run from a test, a hook, or a helper they call pays the module
3
+ // graph's cold load inside that case's timeout, so the first case of a file is slow for no work of
4
+ // its own and, under a full parallel run, crosses the timeout; a module-registry reset re-pays that
5
+ // load for every case. A static import (or a top-level `await import()`) loads it at collection,
6
+ // where no case is timed. A file that truly needs a fresh module per case — code that reads its
7
+ // environment once at load and is tested under several environments — is a reviewed entry with its
8
+ // why; one that no longer does either is stale.
9
+ import ts from 'typescript';
10
+ import { viewOfText } from '../../reader/view.js';
11
+ import { forEachNode } from '../../support/ast.js';
12
+ import { namesProblem, objectProblem, reviewedProblems } from '../../support/facts.js';
13
+ /** The test-runner calls that empty the module registry, so the next import loads every module anew. */
14
+ const REGISTRY_RESETS = new Set(['vi.resetModules', 'jest.resetModules']);
15
+ /** Calls whose callback runs while the file is collected, never inside a timed case. */
16
+ const COLLECTION_CALLBACKS = new Set([
17
+ 'vi.hoisted',
18
+ 'vi.mock',
19
+ 'vi.doMock',
20
+ 'jest.mock',
21
+ 'jest.doMock',
22
+ ]);
23
+ const dottedCallee = (call) => ts.isPropertyAccessExpression(call.expression) && ts.isIdentifier(call.expression.expression)
24
+ ? `${call.expression.expression.text}.${call.expression.name.text}`
25
+ : null;
26
+ /** A specifier naming the app's own code: a relative path, or one of the app's import aliases. */
27
+ const isLocal = (spec, aliases) => spec.startsWith('./') || spec.startsWith('../') || aliases.some((a) => spec.startsWith(a));
28
+ /**
29
+ * Whether code at `node` runs inside a function a test runs — a case, a hook, or a helper — rather
30
+ * than at collection: inside any function that is not the callback of a collection-time call.
31
+ */
32
+ function runsInsideACase(node) {
33
+ for (let at = node.parent; at; at = at.parent) {
34
+ if (!ts.isFunctionLike(at))
35
+ continue;
36
+ const call = at.parent;
37
+ if (call && ts.isCallExpression(call) && COLLECTION_CALLBACKS.has(dottedCallee(call) ?? ''))
38
+ return false;
39
+ return true;
40
+ }
41
+ return false;
42
+ }
43
+ /**
44
+ * Every place a test file loads code inside a case: a module-registry reset, or a dynamic `import()`
45
+ * of a local module run from a case, a hook, or a helper. A string is read as a sample test file.
46
+ */
47
+ export function testCaseModuleLoads(source, aliases) {
48
+ const file = typeof source === 'string' ? viewOfText('sample.test.ts', source) : source;
49
+ const sf = file.sourceFile;
50
+ const hits = [];
51
+ forEachNode(sf, (node) => {
52
+ if (ts.isCallExpression(node) && REGISTRY_RESETS.has(dottedCallee(node) ?? ''))
53
+ hits.push({
54
+ index: node.getStart(sf),
55
+ detail: `${dottedCallee(node)}() reloads every module for the cases after it`,
56
+ });
57
+ });
58
+ for (const load of file.imports) {
59
+ if (load.kind !== 'dynamic' || load.typeOnly || !isLocal(load.spec, aliases))
60
+ continue;
61
+ if (!runsInsideACase(load.node))
62
+ continue;
63
+ hits.push({
64
+ index: load.node.getStart(sf),
65
+ detail: `import('${load.spec}') loads the module inside a test case`,
66
+ });
67
+ }
68
+ return hits.sort((a, b) => a.index - b.index);
69
+ }
70
+ const SCOPE = {
71
+ roots: ['app', 'functions', 'scripts', 'workers'],
72
+ kinds: ['code'],
73
+ tests: 'only',
74
+ optionalRoots: ['functions', 'scripts', 'workers'],
75
+ };
76
+ export const testCaseModuleLoadGuard = {
77
+ id: 'testCaseModuleLoads',
78
+ absentWhen: false,
79
+ factProblems(facts) {
80
+ const problems = [];
81
+ if (!objectProblem(facts, 'the facts', problems))
82
+ return problems;
83
+ namesProblem(facts.localAliases, 'localAliases', problems, { nonEmpty: false });
84
+ reviewedProblems(facts.reviewed, 'reviewed', problems);
85
+ return problems;
86
+ },
87
+ build(facts) {
88
+ const reviewed = new Map(facts.reviewed.map((entry) => [entry.path, entry]));
89
+ return [
90
+ {
91
+ title: 'a test loads the code it tests when its file loads, never inside a test case',
92
+ description: "No test, hook, or helper a test calls resets the module registry or runs a dynamic import() of the app's own " +
93
+ "code: the module graph's cold load then lands inside one case's timeout — the file's first case is slow for no " +
94
+ 'work of its own, and under a full parallel run it times out. Import the code at the top of the file (set any ' +
95
+ 'environment it reads at load in vi.hoisted first). A file that must load a module fresh per case is a reviewed ' +
96
+ 'entry with its why; an entry whose file no longer does is stale.',
97
+ timeoutMs: 120_000,
98
+ run(context) {
99
+ const findings = [];
100
+ const seen = new Set();
101
+ for (const file of context.files(SCOPE)) {
102
+ const loads = testCaseModuleLoads(file, facts.localAliases);
103
+ if (!loads.length)
104
+ continue;
105
+ if (reviewed.has(file.path)) {
106
+ seen.add(file.path);
107
+ continue;
108
+ }
109
+ for (const load of loads)
110
+ findings.push({
111
+ file: file.path,
112
+ line: file.lineOf(load.index),
113
+ detail: load.detail,
114
+ });
115
+ }
116
+ for (const path of reviewed.keys())
117
+ if (!seen.has(path))
118
+ findings.push({
119
+ file: path,
120
+ detail: 'stale reviewed entry: the file no longer loads a module inside a test case',
121
+ });
122
+ return findings;
123
+ },
124
+ },
125
+ ];
126
+ },
127
+ };
128
+ //# sourceMappingURL=test-case-module-loads.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-case-module-loads.js","sourceRoot":"","sources":["../../../src/modules/repo/test-case-module-loads.ts"],"names":[],"mappings":"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","sourcesContent":["// A test loads the code it tests once, when its file loads — never inside a test case. A dynamic\n// `import()` of a local module run from a test, a hook, or a helper they call pays the module\n// graph's cold load inside that case's timeout, so the first case of a file is slow for no work of\n// its own and, under a full parallel run, crosses the timeout; a module-registry reset re-pays that\n// load for every case. A static import (or a top-level `await import()`) loads it at collection,\n// where no case is timed. A file that truly needs a fresh module per case — code that reads its\n// environment once at load and is tested under several environments — is a reviewed entry with its\n// why; one that no longer does either is stale.\n\nimport ts from 'typescript';\nimport type { GenericGuardDefinition } from '../../node/suite/config.js';\nimport type { Finding } from '../../guard.js';\nimport type { Scope } from '../../node/reader.js';\nimport { viewOfText, type FileView } from '../../reader/view.js';\nimport { forEachNode } from '../../support/ast.js';\nimport { namesProblem, objectProblem, reviewedProblems } from '../../support/facts.js';\n\n/** The test-runner calls that empty the module registry, so the next import loads every module anew. */\nconst REGISTRY_RESETS: ReadonlySet<string> = new Set(['vi.resetModules', 'jest.resetModules']);\n\n/** Calls whose callback runs while the file is collected, never inside a timed case. */\nconst COLLECTION_CALLBACKS: ReadonlySet<string> = new Set([\n 'vi.hoisted',\n 'vi.mock',\n 'vi.doMock',\n 'jest.mock',\n 'jest.doMock',\n]);\n\nconst dottedCallee = (call: ts.CallExpression): string | null =>\n ts.isPropertyAccessExpression(call.expression) && ts.isIdentifier(call.expression.expression)\n ? `${call.expression.expression.text}.${call.expression.name.text}`\n : null;\n\n/** A specifier naming the app's own code: a relative path, or one of the app's import aliases. */\nconst isLocal = (spec: string, aliases: readonly string[]) =>\n spec.startsWith('./') || spec.startsWith('../') || aliases.some((a) => spec.startsWith(a));\n\n/**\n * Whether code at `node` runs inside a function a test runs — a case, a hook, or a helper — rather\n * than at collection: inside any function that is not the callback of a collection-time call.\n */\nfunction runsInsideACase(node: ts.Node): boolean {\n for (let at: ts.Node | undefined = node.parent; at; at = at.parent) {\n if (!ts.isFunctionLike(at)) continue;\n const call = at.parent;\n if (call && ts.isCallExpression(call) && COLLECTION_CALLBACKS.has(dottedCallee(call) ?? ''))\n return false;\n return true;\n }\n return false;\n}\n\nexport interface TestCaseModuleLoad {\n index: number;\n detail: string;\n}\n\n/**\n * Every place a test file loads code inside a case: a module-registry reset, or a dynamic `import()`\n * of a local module run from a case, a hook, or a helper. A string is read as a sample test file.\n */\nexport function testCaseModuleLoads(\n source: FileView | string,\n aliases: readonly string[],\n): TestCaseModuleLoad[] {\n const file = typeof source === 'string' ? viewOfText('sample.test.ts', source) : source;\n const sf = file.sourceFile;\n const hits: TestCaseModuleLoad[] = [];\n forEachNode(sf, (node) => {\n if (ts.isCallExpression(node) && REGISTRY_RESETS.has(dottedCallee(node) ?? ''))\n hits.push({\n index: node.getStart(sf),\n detail: `${dottedCallee(node)}() reloads every module for the cases after it`,\n });\n });\n for (const load of file.imports) {\n if (load.kind !== 'dynamic' || load.typeOnly || !isLocal(load.spec, aliases)) continue;\n if (!runsInsideACase(load.node)) continue;\n hits.push({\n index: load.node.getStart(sf),\n detail: `import('${load.spec}') loads the module inside a test case`,\n });\n }\n return hits.sort((a, b) => a.index - b.index);\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 TestCaseModuleLoadFacts {\n /**\n * The prefixes the app's own import aliases begin with (`@/`), beside relative paths; `[]` when\n * the app has none.\n */\n localAliases: readonly string[];\n /**\n * The test files that must load a module fresh per case, each with its why (code that reads its\n * environment once at load, tested under several environments). One that no longer does is stale.\n */\n reviewed: ReadonlyArray<{ path: string; why: string }>;\n}\n\nexport const testCaseModuleLoadGuard: GenericGuardDefinition<TestCaseModuleLoadFacts> = {\n id: 'testCaseModuleLoads',\n absentWhen: false,\n factProblems(facts) {\n const problems: string[] = [];\n if (!objectProblem(facts, 'the facts', problems)) return problems;\n namesProblem(facts.localAliases, 'localAliases', problems, { nonEmpty: false });\n reviewedProblems(facts.reviewed, 'reviewed', problems);\n return problems;\n },\n build(facts) {\n const reviewed = new Map(facts.reviewed.map((entry) => [entry.path, entry]));\n return [\n {\n title: 'a test loads the code it tests when its file loads, never inside a test case',\n description:\n \"No test, hook, or helper a test calls resets the module registry or runs a dynamic import() of the app's own \" +\n \"code: the module graph's cold load then lands inside one case's timeout — the file's first case is slow for no \" +\n 'work of its own, and under a full parallel run it times out. Import the code at the top of the file (set any ' +\n 'environment it reads at load in vi.hoisted first). A file that must load a module fresh per case is a reviewed ' +\n 'entry with its why; an entry whose file no longer does is stale.',\n timeoutMs: 120_000,\n run(context) {\n const findings: Finding[] = [];\n const seen = new Set<string>();\n for (const file of context.files(SCOPE)) {\n const loads = testCaseModuleLoads(file, facts.localAliases);\n if (!loads.length) continue;\n if (reviewed.has(file.path)) {\n seen.add(file.path);\n continue;\n }\n for (const load of loads)\n findings.push({\n file: file.path,\n line: file.lineOf(load.index),\n detail: load.detail,\n });\n }\n for (const path of reviewed.keys())\n if (!seen.has(path))\n findings.push({\n file: path,\n detail:\n 'stale reviewed entry: the file no longer loads a module inside a test case',\n });\n return findings;\n },\n },\n ];\n },\n};\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttt-productions/repo-guards",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/ttt-productions/ttt-packages.git",