@gfdelarue/testkit 0.3.3 → 0.3.5
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.
- package/README.md +11 -0
- package/dist/app/doctor/index.js +1 -1
- package/dist/app/inspect/artifacts.d.ts +1 -0
- package/dist/app/inspect/artifacts.js +1 -1
- package/dist/app/inspect/index.js +1 -1
- package/dist/app/lint/index.js +1 -1
- package/dist/app/lint/native.js +1 -1
- package/dist/app/lint/policy.js +1 -1
- package/dist/app/lint/rules/data.js +1 -1
- package/dist/app/lint/rules/engine-imports.js +1 -1
- package/dist/app/lint/rules/syntax.js +1 -1
- package/dist/app/lint/rules/unit.js +1 -1
- package/dist/app/lint/shared.d.ts +1 -1
- package/dist/app/lint/shared.js +1 -1
- package/dist/app/run/orchestrator.js +1 -1
- package/dist/app/run/request.js +1 -1
- package/dist/app/setup/playwright.js +1 -1
- package/dist/app/status/index.d.ts +2 -0
- package/dist/app/status/index.js +1 -1
- package/dist/app/status/runs.d.ts +2 -2
- package/dist/app/status/runs.js +1 -1
- package/dist/app/stop/index.js +1 -1
- package/dist/app/typecheck/index.d.ts +2 -0
- package/dist/app/typecheck/index.js +4 -4
- package/dist/cli/commands/artifacts.js +2 -2
- package/dist/cli/commands/db/schema/refresh.js +1 -1
- package/dist/cli/commands/db/schema/verify.js +1 -1
- package/dist/cli/commands/doctor.js +1 -1
- package/dist/cli/operations/db/schema/refresh/operation.d.ts +1 -0
- package/dist/cli/operations/db/schema/refresh/operation.js +1 -1
- package/dist/cli/operations/db/schema/verify/operation.d.ts +1 -0
- package/dist/cli/operations/db/schema/verify/operation.js +1 -1
- package/dist/cli/operations/run/operation.js +1 -1
- package/dist/config/loader/discover-tests.d.ts +5 -0
- package/dist/config/loader/discover-tests.js +1 -1
- package/dist/config/loader/discovery.js +2 -2
- package/dist/config/loader/file-metadata.js +1 -1
- package/dist/config/loader/index.d.ts +2 -1
- package/dist/config/loader/index.js +1 -1
- package/dist/config/loader/load.d.ts +2 -0
- package/dist/config/loader/load.js +1 -1
- package/dist/config/loader/managed-configs.d.ts +2 -0
- package/dist/config/loader/managed-configs.js +1 -1
- package/dist/config/loader/paths.d.ts +1 -1
- package/dist/config/loader/paths.js +1 -1
- package/dist/config/loader/skip-config.d.ts +1 -1
- package/dist/config/loader/test-selection.d.ts +15 -0
- package/dist/config/loader/test-selection.js +1 -0
- package/dist/config/loader/validation.js +1 -1
- package/dist/database/index.d.ts +1 -3
- package/dist/database/index.js +1 -1
- package/dist/database/lifecycle-cleanup.d.ts +7 -0
- package/dist/database/lifecycle-cleanup.js +1 -0
- package/dist/database/local-postgres.js +1 -1
- package/dist/database/schema-source.js +3 -3
- package/dist/engines/adapters/evidence-files.js +1 -1
- package/dist/engines/catalog.d.ts +3 -8
- package/dist/engines/catalog.js +1 -1
- package/dist/engines/definitions.d.ts +8 -1
- package/dist/engines/definitions.js +1 -1
- package/dist/engines/generated-performance.d.ts +1 -0
- package/dist/engines/generated-performance.js +12 -0
- package/dist/engines/installations/node-vitest.d.ts +8 -22
- package/dist/engines/installations/node-vitest.js +1 -1
- package/dist/engines/installations/package-requirements.d.ts +28 -0
- package/dist/engines/installations/package-requirements.js +1 -0
- package/dist/engines/installations/playwright.d.ts +1 -11
- package/dist/engines/installations/playwright.js +1 -1
- package/dist/engines/k6/adapter/repository-configuration.d.ts +1 -1
- package/dist/engines/k6/adapter/repository-configuration.js +1 -1
- package/dist/engines/managed-module-resolution.js +111 -21
- package/dist/engines/manager.d.ts +1 -0
- package/dist/engines/manager.js +1 -1
- package/dist/engines/node-vitest/adapter/adapter.d.ts +1 -1
- package/dist/engines/node-vitest/adapter/adapter.js +1 -1
- package/dist/engines/node-vitest/adapter/consumer-typescript-paths.js +1 -1
- package/dist/engines/node-vitest/adapter/resolution.d.ts +1 -1
- package/dist/engines/node-vitest/adapter/resolution.js +1 -1
- package/dist/engines/node-vitest/adapter/workspace.js +33 -9
- package/dist/engines/node-vitest/runtime/runtime-input.d.ts +1 -0
- package/dist/engines/node-vitest/runtime/runtime-input.js +1 -1
- package/dist/engines/runtime-input-file.d.ts +3 -1
- package/dist/engines/runtime-input-file.js +1 -1
- package/dist/environment/lifecycle-controller.d.ts +1 -2
- package/dist/environment/lifecycle-controller.js +1 -1
- package/dist/environment/lifecycle.js +1 -1
- package/dist/environment/maintenance.d.ts +1 -1
- package/dist/environment/maintenance.js +1 -1
- package/dist/environment/ready-environment.d.ts +1 -1
- package/dist/environment/ready-environment.js +1 -1
- package/dist/environment/runtime-contexts.d.ts +1 -1
- package/dist/environment/runtime-contexts.js +1 -1
- package/dist/environment/runtime-preparation.js +1 -1
- package/dist/environment/services.js +1 -1
- package/dist/environment/template.d.ts +1 -1
- package/dist/environment/template.js +1 -1
- package/dist/evidence/bundles.js +1 -1
- package/dist/evidence/read-run.js +1 -1
- package/dist/evidence/reading.d.ts +1 -1
- package/dist/evidence/reading.js +1 -1
- package/dist/evidence/storage.js +2 -2
- package/dist/kernel/error-serialization.js +1 -1
- package/dist/kernel/work-metrics.d.ts +6 -0
- package/dist/kernel/work-metrics.js +1 -0
- package/dist/process/capture-output.js +1 -1
- package/dist/process/execa-bundle.json +1 -1
- package/dist/process/files/product-dir.d.ts +1 -0
- package/dist/process/files/product-dir.js +1 -0
- package/dist/process/locks.js +4 -4
- package/dist/process/readiness.js +1 -1
- package/dist/process/runs/inspection.d.ts +5 -0
- package/dist/process/runs/inspection.js +1 -0
- package/dist/process/runs/manifest.d.ts +1 -0
- package/dist/process/runs/manifest.js +1 -1
- package/dist/process/service-processes.d.ts +1 -1
- package/dist/process/service-processes.js +1 -1
- package/dist/steps/environment.d.ts +6 -0
- package/dist/steps/environment.js +1 -1
- package/dist/steps/index.d.ts +1 -1
- package/dist/steps/index.js +1 -1
- package/dist/toolchains/index.js +2 -2
- package/docs/engine-architecture.md +4 -1
- package/docs/test-types.md +9 -0
- package/package.json +2 -1
- package/testkit-config-schema.md +52 -0
- package/dist/cli/renderers/artifact-preview.d.ts +0 -4
- package/dist/cli/renderers/artifact-preview.js +0 -1
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{ownedGeneratedScript as o}from"./generated-performance.js";import{inspectManagedNodeModuleResolutionPolicy as s}from"./managed-module-resolution-policy.js";export function createManagedNodeModuleResolution(e){const t=s(e),n={...t.direct};for(const[a,i]of Object.entries(t.packageExports)){const r=i.import??i.default??i.require;r!==null&&(n[a]=r)}return Object.freeze({aliases:Object.freeze(n),bootstrapSource:c(t),dependencyRoots:t.managedImporterRoots})}export function managedNodeModuleResolutionBootstrapSource(e){return createManagedNodeModuleResolution(e).bootstrapSource}function c(e){return o(`
|
|
2
2
|
const fs = require("node:fs");
|
|
3
3
|
const path = require("node:path");
|
|
4
|
-
const { registerHooks } = require("node:module");
|
|
4
|
+
const { isBuiltin, registerHooks } = require("node:module");
|
|
5
5
|
const { fileURLToPath, pathToFileURL } = require("node:url");
|
|
6
6
|
|
|
7
|
+
let canonicalPaths = null;
|
|
7
8
|
const POLICY = Object.freeze(${JSON.stringify(e)});
|
|
8
9
|
const CONSUMER_ROOTS = Object.freeze(
|
|
9
10
|
POLICY.consumerRoots.map((root) => verifiedDirectory(root, "consumer root")),
|
|
@@ -15,6 +16,9 @@ const DIRECT = new Map(
|
|
|
15
16
|
]),
|
|
16
17
|
);
|
|
17
18
|
const PACKAGE_ROOTS = verifyPackageRoots(POLICY.packageRoots, "native");
|
|
19
|
+
const PRIVATE_NATIVE_SCOPES = Object.freeze(
|
|
20
|
+
POLICY.managedImporterRoots.filter((root) => !Object.values(PACKAGE_ROOTS).includes(root)),
|
|
21
|
+
);
|
|
18
22
|
const DIRECT_BRIDGE_ROOTS = Object.freeze(
|
|
19
23
|
POLICY.directBridgeRoots.map((root) =>
|
|
20
24
|
verifiedDirectory(root, "direct bridge root"),
|
|
@@ -68,7 +72,30 @@ if (existing !== undefined) {
|
|
|
68
72
|
// tools that register asynchronous loaders. Resolution is the complete
|
|
69
73
|
// policy here, so do not install a load hook that has no policy to enforce.
|
|
70
74
|
const hooks = registerHooks({
|
|
71
|
-
resolve(specifier, context,
|
|
75
|
+
resolve(specifier, context, nativeResolve) {
|
|
76
|
+
const nextResolve = (specifier, context) => {
|
|
77
|
+
canonicalPaths = new Map();
|
|
78
|
+
try { return __testkitWithOwner("external", "Native module resolution", () => nativeResolve(specifier, context)); }
|
|
79
|
+
finally { canonicalPaths = new Map(); }
|
|
80
|
+
};
|
|
81
|
+
return __testkitWithOwner("testkit", "Testkit managed resolution policy", () => {
|
|
82
|
+
const previousPaths = canonicalPaths;
|
|
83
|
+
canonicalPaths = new Map();
|
|
84
|
+
try {
|
|
85
|
+
// Builtins and physically native-internal file edges need no package graph scan.
|
|
86
|
+
// A downstream resolver may redirect either one, so inspect its result.
|
|
87
|
+
let nativeResult;
|
|
88
|
+
if (isBuiltin(specifier) && !DIRECT.has(specifier)) {
|
|
89
|
+
nativeResult = nextResolve(specifier, context);
|
|
90
|
+
if (isBuiltin(nativeResult.url)) return nativeResult;
|
|
91
|
+
}
|
|
92
|
+
const nativeOrigin = specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("file:") || path.isAbsolute(specifier)
|
|
93
|
+
? nativeFileOrigin(specifier, context.parentURL) : null;
|
|
94
|
+
if (nativeOrigin !== null) {
|
|
95
|
+
nativeResult = nextResolve(specifier, context);
|
|
96
|
+
if (sameNativePackageEdge(nativeOrigin, context.parentURL, nativeResult.url))
|
|
97
|
+
return nativeResult;
|
|
98
|
+
}
|
|
72
99
|
const conditions = normalizedConditions(context.conditions);
|
|
73
100
|
const classified = classifyResolvedSpecifier(specifier, context.parentURL);
|
|
74
101
|
requireAllowedResolvedSpecifier(classified);
|
|
@@ -106,7 +133,7 @@ if (existing !== undefined) {
|
|
|
106
133
|
forbiddenPackage,
|
|
107
134
|
);
|
|
108
135
|
}
|
|
109
|
-
const resolved = nextResolve(specifier, context);
|
|
136
|
+
const resolved = nativeResult ?? nextResolve(specifier, context);
|
|
110
137
|
const resolvedClassification = classifyResolvedSpecifier(
|
|
111
138
|
resolved.url,
|
|
112
139
|
context.parentURL,
|
|
@@ -121,6 +148,8 @@ if (existing !== undefined) {
|
|
|
121
148
|
if (redirected !== null) return redirected;
|
|
122
149
|
}
|
|
123
150
|
return resolved;
|
|
151
|
+
} finally { canonicalPaths = previousPaths; }
|
|
152
|
+
});
|
|
124
153
|
},
|
|
125
154
|
});
|
|
126
155
|
Object.defineProperty(globalThis, STATE, {
|
|
@@ -131,6 +160,66 @@ if (existing !== undefined) {
|
|
|
131
160
|
});
|
|
132
161
|
}
|
|
133
162
|
|
|
163
|
+
function recordResolutionWork(metric) {
|
|
164
|
+
globalThis[Symbol.for("testkit.work.observer")]?.observer?.(metric, 1);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function observedRealpath(file) {
|
|
168
|
+
recordResolutionWork("resolver.realpath");
|
|
169
|
+
// Match policy inspection on Windows: native realpath expands 8.3 names and
|
|
170
|
+
// changes their spelling, mixing physical paths with the inspected roots.
|
|
171
|
+
return process.platform === "win32" ? fs.realpathSync(file) : fs.realpathSync.native(file);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function observedRead(file, encoding) {
|
|
175
|
+
recordResolutionWork("resolver.read");
|
|
176
|
+
return fs.readFileSync(file, encoding);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function observedExists(file) {
|
|
180
|
+
recordResolutionWork("resolver.exists");
|
|
181
|
+
return fs.existsSync(file);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function nativeFileOrigin(specifier, parentURL) {
|
|
185
|
+
const requestedPath = specifier.startsWith("file:") ? fileURLToPath(specifier) : path.isAbsolute(specifier) ? specifier : null;
|
|
186
|
+
// A lexical miss only skips this optimization; all trust uses fresh physical paths.
|
|
187
|
+
if (requestedPath !== null && ![...Object.values(PACKAGE_ROOTS), ...PRIVATE_NATIVE_SCOPES].some(root => isPathWithin(root, requestedPath))) return null;
|
|
188
|
+
if (typeof parentURL !== "string" || !parentURL.startsWith("file:")) return null;
|
|
189
|
+
try {
|
|
190
|
+
const file = observedRealpath(fileURLToPath(parentURL));
|
|
191
|
+
const root = nativePackageRoot(file);
|
|
192
|
+
if (root === undefined) return null;
|
|
193
|
+
if (requestedPath !== null) {
|
|
194
|
+
const requested = observedRealpath(requestedPath);
|
|
195
|
+
if (!isPathWithin(root, requested)) return null;
|
|
196
|
+
}
|
|
197
|
+
return { file, root };
|
|
198
|
+
} catch { return null; }
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function nativePackageRoot(file) {
|
|
202
|
+
const selected = Object.values(PACKAGE_ROOTS).find((root) => isPathWithin(root, file));
|
|
203
|
+
if (selected !== undefined) return selected;
|
|
204
|
+
for (const scope of PRIVATE_NATIVE_SCOPES) {
|
|
205
|
+
if (!isPathWithin(scope, file)) continue;
|
|
206
|
+
const relative = path.relative(scope, file);
|
|
207
|
+
const separator = relative.indexOf(path.sep);
|
|
208
|
+
if (separator > 0) return path.join(scope, relative.slice(0, separator));
|
|
209
|
+
}
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function sameNativePackageEdge(origin, parentURL, targetURL) {
|
|
214
|
+
// Trust never survives another resolver changing the importer or its root.
|
|
215
|
+
if (observedRealpath(fileURLToPath(parentURL)) !== origin.file ||
|
|
216
|
+
observedRealpath(origin.root) !== origin.root)
|
|
217
|
+
throw new Error("Managed " + POLICY.engineLabel + " native importer changed during resolution");
|
|
218
|
+
if (typeof targetURL !== "string" || !targetURL.startsWith("file:")) return false;
|
|
219
|
+
try { return isPathWithin(origin.root, observedRealpath(fileURLToPath(targetURL))); }
|
|
220
|
+
catch { return false; }
|
|
221
|
+
}
|
|
222
|
+
|
|
134
223
|
function resolveManagedSpecifier(specifier, conditions, parentURL) {
|
|
135
224
|
const direct = DIRECT.get(specifier);
|
|
136
225
|
if (direct !== undefined)
|
|
@@ -185,9 +274,9 @@ function nearestPackageManifest(importerFile) {
|
|
|
185
274
|
let directory = path.dirname(importerFile);
|
|
186
275
|
while (true) {
|
|
187
276
|
const manifestFile = path.join(directory, "package.json");
|
|
188
|
-
if (
|
|
277
|
+
if (observedExists(manifestFile)) {
|
|
189
278
|
try {
|
|
190
|
-
const manifest = JSON.parse(
|
|
279
|
+
const manifest = JSON.parse(observedRead(manifestFile, "utf8"));
|
|
191
280
|
if (isObject(manifest)) return manifest;
|
|
192
281
|
} catch {}
|
|
193
282
|
return null;
|
|
@@ -376,10 +465,7 @@ function unclassified() {
|
|
|
376
465
|
function resolvedFileCandidates(file) {
|
|
377
466
|
const resolved = path.resolve(file);
|
|
378
467
|
const candidates = [];
|
|
379
|
-
|
|
380
|
-
const canonical = fs.realpathSync(file);
|
|
381
|
-
candidates.push(canonical);
|
|
382
|
-
} catch {}
|
|
468
|
+
candidates.push(canonicalPath(file));
|
|
383
469
|
if (!candidates.includes(resolved)) candidates.push(resolved);
|
|
384
470
|
return candidates;
|
|
385
471
|
}
|
|
@@ -391,8 +477,8 @@ function classifyPackageBoundaries(file, parentURL) {
|
|
|
391
477
|
if (scanBoundary !== null && canonicalPath(directory) === scanBoundary)
|
|
392
478
|
return null;
|
|
393
479
|
const manifestFile = path.join(directory, "package.json");
|
|
394
|
-
if (
|
|
395
|
-
const manifest = JSON.parse(
|
|
480
|
+
if (observedExists(manifestFile)) {
|
|
481
|
+
const manifest = JSON.parse(observedRead(manifestFile, "utf8"));
|
|
396
482
|
const packageName =
|
|
397
483
|
typeof manifest.name === "string" ? manifest.name : null;
|
|
398
484
|
if (packageName !== null && Object.hasOwn(PACKAGE_ROOTS, packageName))
|
|
@@ -637,7 +723,7 @@ function verifyPackageRoots(roots, kind) {
|
|
|
637
723
|
Object.entries(roots).map(([packageName, root]) => {
|
|
638
724
|
const canonical = canonicalPath(root);
|
|
639
725
|
const manifest = JSON.parse(
|
|
640
|
-
|
|
726
|
+
observedRead(path.join(canonical, "package.json"), "utf8"),
|
|
641
727
|
);
|
|
642
728
|
if (manifest.name !== packageName)
|
|
643
729
|
throw new Error(
|
|
@@ -658,7 +744,7 @@ function verifiedOptionalFile(file, label) {
|
|
|
658
744
|
}
|
|
659
745
|
|
|
660
746
|
function verifiedDirectory(directory, label) {
|
|
661
|
-
const resolved =
|
|
747
|
+
const resolved = observedRealpath(directory);
|
|
662
748
|
if (!fs.statSync(resolved).isDirectory())
|
|
663
749
|
throw new Error(
|
|
664
750
|
"Managed " + POLICY.engineLabel + " " + label + " is not a directory",
|
|
@@ -667,7 +753,7 @@ function verifiedDirectory(directory, label) {
|
|
|
667
753
|
}
|
|
668
754
|
|
|
669
755
|
function verifiedFile(file, label) {
|
|
670
|
-
const resolved =
|
|
756
|
+
const resolved = observedRealpath(file);
|
|
671
757
|
if (!fs.statSync(resolved).isFile())
|
|
672
758
|
throw new Error(
|
|
673
759
|
"Managed " + POLICY.engineLabel + " " + label + " is not a regular file",
|
|
@@ -676,11 +762,15 @@ function verifiedFile(file, label) {
|
|
|
676
762
|
}
|
|
677
763
|
|
|
678
764
|
function canonicalPath(file) {
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
765
|
+
// Share physical checks only within this policy decision. A later import or
|
|
766
|
+
// another resolver can change the filesystem and must see fresh identities.
|
|
767
|
+
const cached = canonicalPaths?.get(file);
|
|
768
|
+
if (cached !== undefined) return cached;
|
|
769
|
+
let canonical;
|
|
770
|
+
try { canonical = observedRealpath(file); }
|
|
771
|
+
catch { canonical = path.resolve(file); }
|
|
772
|
+
canonicalPaths?.set(file, canonical);
|
|
773
|
+
return canonical;
|
|
684
774
|
}
|
|
685
775
|
|
|
686
776
|
function sameFile(left, right) {
|
|
@@ -696,4 +786,4 @@ function isPathWithin(root, candidate) {
|
|
|
696
786
|
!path.isAbsolute(relative))
|
|
697
787
|
);
|
|
698
788
|
}
|
|
699
|
-
|
|
789
|
+
`,"Testkit managed resolution bootstrap")}
|
|
@@ -18,6 +18,7 @@ export declare class EngineExecutorLoadError extends Error {
|
|
|
18
18
|
export declare class EngineManager {
|
|
19
19
|
private readonly catalog;
|
|
20
20
|
private readonly productRoot;
|
|
21
|
+
private readonly installations;
|
|
21
22
|
constructor(options: Readonly<EngineManagerOptions>);
|
|
22
23
|
resolve<Id extends EngineId>(engineId: Id): InternalEngineRegistration<Id>;
|
|
23
24
|
withEngine<Id extends EngineId, Result>(engineId: Id, signal: AbortSignal, operation: (session: ResolvedEngineExecutionSession<Id>) => Result | PromiseLike<Result>): Promise<Result>;
|
package/dist/engines/manager.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ENGINE_PROTOCOL_LIMITS as d,beginEngineExecution as E,createEngineInvocationScope as
|
|
1
|
+
import{ENGINE_PROTOCOL_LIMITS as d,beginEngineExecution as E,createEngineInvocationScope as h}from"./api/index.js";import"../domain/identifiers.js";export class EngineExecutorLoadError extends Error{code="TESTKIT_ENGINE_EXECUTOR_LOAD_FAILED";engineId;constructor(t,o){super(`Resolved ${t} executor could not be loaded`,o),this.name="EngineExecutorLoadError",this.engineId=t}}export class EngineManager{catalog;productRoot;installations=new WeakMap;constructor(t){this.catalog=t.catalog,this.productRoot=t.productRoot}resolve(t){return this.catalog.resolve(t)}async withEngine(t,o,i){const c=this.resolve(t);o.throwIfAborted();let n=this.installations.get(o);n||(n=new Map,this.installations.set(o,n));let r=n.get(t);if(!r){const a=n;r=c.resolveInstallation({productRoot:this.productRoot,signal:o}).catch(l=>{throw a.delete(t),l}),a.set(t,r)}const s=await r;return o.throwIfAborted(),i(w(c,s))}}function w(e,t){return Object.freeze({engineId:e.id,async execute(o){u(o.signal);const i=E(o),c=h({timeoutMs:d.cleanupTimeoutMs});let n=null,r,s=!1;try{const l=await g(e);u(o.signal),n=i.acceptResult(await l(i.request,t,c.scope))}catch(l){s=!0,r=l}finally{i.close()}const a=await c.drain();if(s)throw a.length===0?r:new AggregateError([r,...a],`Engine invocation failed and ${a.length.toString()} cleanup handler(s) also failed`,{cause:r});if(a.length>0)throw new AggregateError(a,`${a.length.toString()} engine invocation cleanup handler(s) failed`);if(n===null)throw new Error("Engine invocation completed without a result");return i.commit(),n}})}function u(e){if(e.aborted)throw e.reason instanceof Error?e.reason:new Error("Resolved engine invocation was cancelled",{cause:e.reason})}async function g(e){try{return await e.loadExecutor()}catch(t){throw new EngineExecutorLoadError(e.id,{cause:t})}}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type ProcessClassification } from "../../adapters/execution.js";
|
|
2
2
|
import { type EngineExecutionRequest, type EngineExecutionResult, type EngineFailureEvidence, type EngineInvocationScope } from "../../api/index.js";
|
|
3
3
|
import { type ManagedProcessResult } from "../../../process/index.js";
|
|
4
|
-
import type
|
|
4
|
+
import { type NodeVitestInstallation } from "../../installations/node-vitest.js";
|
|
5
5
|
import { type CollectedNodeVitestEvidence } from "./evidence.js";
|
|
6
6
|
import { writeOwnedNodeVitestRuntimeInput, type NodeVitestRuntimeInputDocument } from "../runtime/runtime-input.js";
|
|
7
7
|
import { writeNodeVitestWorkspace } from "./workspace.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{makeTempDirAsync as R}from"../../../process/files/temp-dir.js";import{classifyManagedProcessResult as
|
|
1
|
+
import{makeTempDirAsync as R}from"../../../process/files/temp-dir.js";import{classifyManagedProcessResult as z,terminalOutcome as I,processOutputEvidence as W,emptyEvidence as J,failureFromError as l,firstOutputLine as h,processExitFailure as H,toError as x,executeEngineTasks as Y,managedProcessFailure as G}from"../../adapters/execution.js";import{requireRootDirectory as Q,requireAbsoluteWorkspacePath as M,requireConsumerFile as P,safeSegment as L}from"../../adapters/workspace.js";import p from"node:fs/promises";import c from"node:path";import{pathToFileURL as X}from"node:url";import{isEngineTaskDeadlineExceededReason as Z,resolveEngineTaskInputs as q}from"../../api/index.js";import{createInvocationProcessEnvironment as tt,runManagedProcess as et}from"../../../process/index.js";import{preflightNodeVitestInstallation as it}from"../../installations/node-vitest.js";import{collectNodeVitestEvidence as rt}from"./evidence.js";import{assertNodeVitestSpawnEnvironmentBudget as ot,NODE_VITEST_RUNTIME_INPUT_FILE_ENV as nt,NODE_VITEST_RUNTIME_INPUT_SHA256_ENV as at,selectNodeVitestForwardedEnvironment as st,verifyOwnedNodeVitestRuntimeInput as ct,writeOwnedNodeVitestRuntimeInput as ut}from"../runtime/runtime-input.js";import{writeNodeVitestWorkspace as dt}from"./workspace.js";import{engineEvidenceIdentity as lt}from"../../adapters/evidence-identity.js";const T={codePrefix:"NODE_VITEST",label:"Node/Vitest"};class A extends Error{constructor(e){const i=x(e);super(i.message,{cause:i}),this.name="NodeVitestConsumerPreparationError"}}export const nodeVitestExecutorDependencies={removeOwnedDirectory:t=>p.rm(t,{force:!0,recursive:!0}),writeRuntimeInput:ut,writeWorkspace:dt};export async function executeNodeVitestEngine(t,e,i,r=nodeVitestExecutorDependencies){const n=await pt(t,e,r,i);return Y(t,n.tasks,T,(o,a)=>ft(t,n,o,a),_t(t,n.installation))}async function pt(t,e,i,r){const n=t.profile.id,o=e.packages.vitest.cli,a=await Q(t.workspace.rootDirectory);M(t.workspace.temporaryDirectory,"temporary directory"),M(t.workspace.artifactDirectory,"artifact directory"),await p.mkdir(t.workspace.temporaryDirectory,{recursive:!0}),await p.mkdir(t.workspace.artifactDirectory,{recursive:!0});const f=await p.realpath(t.workspace.temporaryDirectory),d=await p.realpath(t.workspace.artifactDirectory),v=Object.freeze({...e.authoringEntrypoints,cli:e.packages.vitest.cli,packages:e.packages}),_=await R(`node-vitest-${L(t.invocationId)}-`,{parentDir:f});r.defer(()=>i.removeOwnedDirectory(_));const j=await tt(_),g=[];for(const[K,s]of t.tasks.entries()){const m=q(t,s),S=`${K.toString().padStart(3,"0")}-${L(s.id)}`,u=c.join(_,S),y=await R(`${S}-`,{parentDir:d}),O=c.join(u,"testkit-vitest-report.json"),U=c.join(y,"data","manifest.json"),B=c.join(u,"setup.mjs"),w=c.join(u,"input-bootstrap.mjs"),F=c.join(u,"resolution-bootstrap.cjs"),V=c.join(u,"vitest.config.mjs");try{await p.mkdir(u,{mode:448,recursive:!1});const D=await N(()=>P(a,s.file,"Node/Vitest task file")),k=await N(()=>vt(a,m)),$=await N(()=>It(t,n,s,m)),E=await i.writeRuntimeInput(u,$);await i.writeWorkspace({report:{identity:{engineId:"node-vitest",invocationId:t.invocationId,taskId:s.id,sourceFile:s.file},reportFile:O,workspaceDirectory:u},configFile:k.testkitConfig,entrypoints:v,inputBootstrapFile:w,resolutionBootstrapFile:F,rootDirectory:a,runtimeInput:{expectedFile:E.file,expectedInvocationId:t.invocationId,expectedProfileId:n,expectedSha256:E.sha256,expectedSizeBytes:E.sizeBytes,expectedTaskId:s.id},setupFile:B,sourceFile:D,timeoutMs:Math.max(1,s.deadlineMs-Date.now()),tsconfigFile:k.tsconfig,vitestConfigFile:V}),g.push({artifactDirectory:y,inputs:m,task:s,preparationFailure:null,prepared:{dataManifestFile:U,inputBootstrapFile:w,reportFile:O,resolutionBootstrapFile:F,runtimeInput:E,vitestConfigFile:V}})}catch(D){g.push({artifactDirectory:y,inputs:m,task:s,preparationFailure:gt(D),prepared:null})}}return Object.freeze({cliFile:o,installation:e,invocationEnvironment:j.environment,profileId:n,rootDirectory:a,tasks:Object.freeze(g)})}async function ft(t,e,i,r){const n=Math.max(0,i.task.deadlineMs-r);try{await ct(i.prepared.runtimeInput)}catch(d){return I(i.task.id,"failed",r,J(),{...l(d,"NODE_VITEST_INPUT_INVALID"),classification:"infra"})}const o=await et({args:[e.cliFile,"run","--config",i.prepared.vitestConfigFile,"--configLoader","runner"],cwd:e.rootDirectory,displayLabel:"Node/Vitest",env:Et(t,e,i),executable:process.execPath,signal:t.signal,timeoutMs:n});let a;try{a=await rt({identity:{engineId:"node-vitest",invocationId:t.invocationId,taskId:i.task.id,sourceFile:i.task.file},artifactDirectory:i.artifactDirectory,dataManifestFile:i.prepared.dataManifestFile,profileId:e.profileId,reportFile:i.prepared.reportFile,rootDirectory:e.rootDirectory,stderr:o.stderr,stdout:o.stdout})}catch(d){const v=W(o);return I(i.task.id,"failed",r,v,nodeVitestEvidenceCollectionFailure(o,d))}if(a.reportSummary===null&&o.exitCode!==0&&!t.signal.aborted&&!o.timedOut&&!o.outputLimitExceeded&&o.terminationError===null&&o.error===null)try{await it(e.installation,{signal:t.signal})}catch(d){if(!t.signal.aborted)return I(i.task.id,"failed",r,a.evidence,{...l(d,"NODE_VITEST_INSTALLATION_BROKEN"),classification:"infra"})}const f=classifyNodeVitestProcessResult(o,a,i.task.timeoutMs,Z(t.signal.reason));return I(i.task.id,f.status,r,a.evidence,f.failure)}export function classifyNodeVitestProcessResult(t,e,i,r){const n=z(t,i,r,T);return n!==null?n:e.reportSummary?.numTotalTests===0?{failure:{code:"NODE_VITEST_NO_TESTS",message:"Vitest did not discover any tests for the task file"},status:"failed"}:mt(e.reportSummary)?{failure:null,status:"skipped"}:t.exitCode!==0?H(t,T,e.reportFailureMessage??h(t.stderr)??h(t.stdout),"Vitest"):e.reportError!==null?{failure:{...l(e.reportError,"NODE_VITEST_REPORT_INVALID"),classification:"infra"},status:"failed"}:e.reportFailureMessage!==null?{failure:{code:"NODE_VITEST_REPORT_FAILED",message:e.reportFailureMessage},status:"failed"}:e.dataEvidenceError!==null?{failure:l(e.dataEvidenceError,"NODE_VITEST_DATA_EVIDENCE_INVALID"),status:"failed"}:e.reportSummary===null||!e.reportSummary.success||e.reportSummary.numFailedTests>0||e.reportSummary.numPassedTests===0?{failure:{code:"NODE_VITEST_REPORT_FAILED",message:"Vitest report did not contain a completed passing test"},status:"failed"}:{failure:null,status:"passed"}}export function nodeVitestEvidenceCollectionFailure(t,e){const i=x(e);return G(t,T,i)??{...l(i,"NODE_VITEST_EVIDENCE_FAILED"),classification:"infra"}}function mt(t){return t!==null&&t.success&&t.numTotalTests>0&&t.numPassedTests===0&&t.numFailedTests===0&&(t.numPendingTests>0||t.numTodoTests>0)}function Et(t,e,i){const r=i.task,n=b(t,r.serviceName),o={...e.invocationEnvironment};return Object.assign(o,{...n===null?{}:{BASE_URL:n},FORCE_COLOR:"0",NODE_OPTIONS:Tt(i.prepared.resolutionBootstrapFile,i.prepared.inputBootstrapFile),NO_COLOR:"1",TESTKIT_ACTIVE:"1",[nt]:i.prepared.runtimeInput.file,[at]:i.prepared.runtimeInput.sha256,TESTKIT_INTERNAL_FILE_DEADLINE_MS:String(r.deadlineMs),TESTKIT_INTERNAL_FILE_TIMEOUT_SECONDS:String(Math.max(1,Math.ceil(r.timeoutMs/1e3))),TESTKIT_INVOCATION_ID:t.invocationId,TESTKIT_LEASE_DIR:r.orchestration.lease.directory,TESTKIT_LEASE_ID:r.orchestration.lease.id,TESTKIT_MODE:r.orchestration.mode,...r.orchestration.runtimeId===void 0?{}:{TESTKIT_RUNTIME_ID:r.orchestration.runtimeId},TESTKIT_TEST_FILE:r.file,TESTKIT_TEST_ID:r.id,...e.profileId==="node-vitest-data"?{TESTKIT_DATA_ARTIFACT_DIR:c.dirname(i.prepared.dataManifestFile)}:{}}),ot(o),o}function It(t,e,i,r){const n=b(t,i.serviceName),o=st(r.environment);return{context:{baseUrl:n,engineId:"node-vitest",invocationId:t.invocationId,profileId:e,serviceName:i.serviceName,task:{file:i.file,id:i.id,suiteName:i.suiteName,testType:i.testType}},engineId:"node-vitest",environment:o,fixtures:r.fixtures,invocationId:t.invocationId,profileId:e,...r.setupData===void 0?{}:{setupData:r.setupData},taskId:i.id,version:1}}function Tt(t,e){return[`--require=${JSON.stringify(t)}`,`--import=${JSON.stringify(X(e).href)}`].join(" ")}function b(t,e){return Object.hasOwn(t.target.endpoints,e)?t.target.endpoints[e]:null}async function vt(t,e){return{testkitConfig:await C(t,e.supportModules,"testkitConfig"),tsconfig:await C(t,e.supportModules,"tsconfig")}}async function C(t,e,i){if(!Object.hasOwn(e,i))return null;const r=e[i];if(!c.isAbsolute(r))throw new Error(`supportModules.${i} must be an absolute path`);return P(t,r,`supportModules.${i}`)}function _t(t,e){return{...lt({engineId:"node-vitest",installationKind:"root-direct-packages",profile:t.profile}),directPackages:{esbuild:e.packages.esbuild.version,typescript:e.packages.typescript.version,vite:e.packages.vite.version,vitest:e.packages.vitest.version}}}function gt(t){return{...l(t,"NODE_VITEST_PREPARE_FAILED"),classification:t instanceof A?"product":"infra"}}async function N(t){try{return await t()}catch(e){throw new A(e)}}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{isPathWithin as y}from"../../../process/files/paths.js";import u from"node:fs";import{createRequire as h}from"node:module";import i from"node:path";import{fileURLToPath as l}from"node:url";import{NODE_VITEST_PACKAGE_REQUIREMENTS as N}from"../../installations/
|
|
1
|
+
import{isPathWithin as y}from"../../../process/files/paths.js";import u from"node:fs";import{createRequire as h}from"node:module";import i from"node:path";import{fileURLToPath as l}from"node:url";import{NODE_VITEST_PACKAGE_REQUIREMENTS as N}from"../../installations/package-requirements.js";export const managedTypeScriptFile=l(import.meta.resolve("typescript"));const k=/^@gfdelarue\/testkit(?:\/|$)/u;export function createConsumerTypeScriptPathsPlugin(t,e){const r=h(import.meta.url)(managedTypeScriptFile),n=S(t,r),o=Object.keys(n.paths??{});return o.length===0?null:{enforce:"pre",name:"testkit-node-vitest-consumer-typescript-paths",resolveId(a,s){if(s===void 0||isNodeVitestManagedDependencyImporter(s,e.dependencyRoots)||k.test(a)||!o.some(g=>w(a,g)))return null;const c=x(s);if(c===null)return null;const f=r.resolveModuleName(a,c,n,r.sys).resolvedModule?.resolvedFileName;return f===void 0?null:assertNodeVitestConsumerResolution(a,f,s,e)}}}export function assertNodeVitestConsumerResolution(t,e,r,n){if(isNodeVitestManagedDependencyImporter(r,n.dependencyRoots))return e;const o=d(e);if(o===null)return e;const a=v(o);if(a===null)return e;if(a.privatePackage)throw new Error(`Node/Vitest consumers may not resolve ${JSON.stringify(t)} into private Vitest package ${a.packageName}`);const s=n.aliases[t];if(Object.hasOwn(n.aliases,t)&&m(o,s)||Object.values(n.aliases).some(c=>m(o,c)))return e;throw new Error(`Node/Vitest consumers may not resolve ${JSON.stringify(t)} into managed direct package ${a.packageName}; import an exact public ${JSON.stringify(a.packageName)} export instead`)}function v(t){const e=[t];try{const r=u.realpathSync(t);r!==t&&e.push(r)}catch{}for(const r of e){let n=i.dirname(r);for(;;){const o=i.join(n,"package.json");if(u.existsSync(o)){const s=E(o);if(s!==null){if(s.startsWith("@vitest/"))return{packageName:s,privatePackage:!0};if(N.some(({packageName:c})=>c===s))return{packageName:s,privatePackage:!1}}}const a=i.dirname(n);if(a===n)break;n=a}}return null}function E(t){let e;try{e=JSON.parse(u.readFileSync(t,"utf8"))}catch(n){throw new Error(`Cannot inspect package boundary at ${t}`,{cause:n})}if(typeof e!="object"||e===null||Array.isArray(e))return null;const r=Reflect.get(e,"name");return typeof r=="string"?r:null}export function isNodeVitestManagedDependencyImporter(t,e){if(t===void 0)return!1;const r=d(t);return r===null?!1:e.some(n=>y(n,r))}function d(t){const e=t.split(/[?#]/u,1)[0];if(e.startsWith("\0"))return null;if(e.startsWith("file:"))return l(e);const r=e.startsWith("/@fs/")?e.slice(4):e;return i.isAbsolute(r)?i.resolve(r):null}function m(t,e){try{return u.realpathSync(t)===u.realpathSync(e)}catch{return i.resolve(t)===i.resolve(e)}}function S(t,e){const r=i.resolve(t).replaceAll(i.sep,"/"),n=i.posix.dirname(r),o=e.readConfigFile(r,c=>e.sys.readFile(c));if(o.error!==void 0)throw new Error(p(r,[o.error],e));const a=e.parseJsonConfigFileContent(o.config,e.sys,n,{},r),s=a.errors.filter(c=>c.category===e.DiagnosticCategory.Error&&c.code!==18003);if(s.length>0)throw new Error(p(r,s,e));return a.options}function p(t,e,r){const n=r.formatDiagnostics(e,{getCanonicalFileName:o=>o,getCurrentDirectory:()=>i.posix.dirname(t),getNewLine:()=>`
|
|
2
2
|
`});return`Cannot load explicit consumer TypeScript paths from ${t}:
|
|
3
3
|
${n}`.trim()}function w(t,e){const r=e.indexOf("*");if(r===-1)return t===e;const n=e.slice(0,r),o=e.slice(r+1);return t.startsWith(n)&&t.endsWith(o)}function x(t){const e=t.split(/[?#]/u,1)[0];return e.startsWith("file:")?l(e):i.isAbsolute(e)?e:null}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ManagedNodeModuleResolution } from "../../managed-module-resolution.js";
|
|
2
|
-
import {
|
|
2
|
+
import type { NodeVitestAuthoringEntrypoints, NodeVitestInstallation } from "../../installations/node-vitest.js";
|
|
3
3
|
export interface NodeVitestResolutionEntrypoints extends NodeVitestAuthoringEntrypoints {
|
|
4
4
|
readonly cli: string;
|
|
5
5
|
readonly packages: NodeVitestInstallation["packages"];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import c from"node:path";import{createManagedNodeModuleResolution as d}from"../../managed-module-resolution.js";import{NODE_VITEST_PACKAGE_REQUIREMENTS as i,NODE_VITEST_PRIVATE_PACKAGE_PREFIXES as g}from"../../installations/
|
|
1
|
+
import c from"node:path";import{createManagedNodeModuleResolution as d}from"../../managed-module-resolution.js";import{NODE_VITEST_PACKAGE_REQUIREMENTS as i,NODE_VITEST_PRIVATE_PACKAGE_PREFIXES as g}from"../../installations/package-requirements.js";export function resolveNodeVitestManagedRuntimeResolution(t,a){const o=i.filter(e=>e.authoringResolution==="exact-entrypoint"),n=Object.fromEntries(i.filter(e=>e.authoringResolution==="package-exports").map(e=>[e.packageName,t.packages[e.packageName].entrypoint])),r=o.map(e=>t.packages[e.packageName].root),s=d({consumerRoots:a.consumerRoots,direct:{"@gfdelarue/testkit":t.root,"@gfdelarue/testkit/config":t.config,"@gfdelarue/testkit/node-vitest/contract":t.contract,"@gfdelarue/testkit/node-vitest/data":t.data,"@gfdelarue/testkit/package.json":t.rootPackageJson,...Object.fromEntries(o.map(e=>[e.packageName,t.packages[e.packageName].entrypoint]))},directBridgeRoots:[c.dirname(t.root),...r],engineLabel:"Node/Vitest",forbiddenConsumerPackagePrefixes:g,packageAnchors:n,reservedPackageNames:["@gfdelarue/testkit",...o.map(e=>e.packageName)],stateSymbol:"@gfdelarue/testkit/node-vitest/resolution-hooks/v1",trustedImporterFiles:a.trustedImporterFiles,trustedInternalEntrypoints:a.trustedInternalEntrypoints,verifiedParentlessPackageFiles:{vitest:["dist/workers/forks.js","suppress-warnings.cjs"]},verifiedProcessEntrypoints:[t.cli]});return Object.freeze({...s,dependencyRoots:Object.freeze([...new Set([...s.dependencyRoots,c.dirname(t.root),...r])])})}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import o from"node:fs/promises";import i from"node:path";import{fileURLToPath as g,pathToFileURL as a}from"node:url";import{NODE_VITEST_RUNTIME_INPUT_SYMBOL as p}from"../runtime/runtime-input.js";import{NODE_VITEST_PACKAGE_REQUIREMENTS as
|
|
1
|
+
import o from"node:fs/promises";import i from"node:path";import{fileURLToPath as g,pathToFileURL as a}from"node:url";import{NODE_VITEST_RUNTIME_INPUT_SYMBOL as p}from"../runtime/runtime-input.js";import{NODE_VITEST_PACKAGE_REQUIREMENTS as R}from"../../installations/package-requirements.js";import{resolveNodeVitestManagedRuntimeResolution as y}from"./resolution.js";import{nativeReporterFile as S}from"../../adapters/native-report.js";const u="elench.testkit.node-vitest.config-runtime.v1",l=g(new URL("../../../../dist/engines/node-vitest/",import.meta.url)),f=i.join(l,"adapter/consumer-typescript-paths.js"),m=i.join(l,"runtime/data/runner.js"),d=i.join(l,"runtime/runtime-input.js");export async function writeNodeVitestWorkspace(e){await o.mkdir(i.dirname(e.setupFile),{recursive:!0});const t=y(e.entrypoints,{consumerRoots:[e.rootDirectory],trustedImporterFiles:[e.inputBootstrapFile,e.setupFile,e.vitestConfigFile],trustedInternalEntrypoints:[f,d]});await Promise.all([o.writeFile(e.setupFile,I(e.configFile,e.runtimeInput.expectedProfileId),"utf8"),o.writeFile(e.inputBootstrapFile,N(e.runtimeInput),"utf8"),o.writeFile(e.resolutionBootstrapFile,t.bootstrapSource,{encoding:"utf8",flag:"wx",mode:384}),o.writeFile(e.vitestConfigFile,O(e,t),"utf8")])}function N(e){const t=a(d).href,n=a(f).href;return`
|
|
2
2
|
const inputRuntime = await import(${JSON.stringify(t)});
|
|
3
|
-
const consumerResolutionRuntime = await import(${JSON.stringify(n)});
|
|
4
3
|
const { installNodeVitestRuntimeInput } = inputRuntime;
|
|
5
4
|
await installNodeVitestRuntimeInput(${JSON.stringify(e)});
|
|
6
5
|
const configRuntimeSymbol = Symbol.for(${JSON.stringify(u)});
|
|
7
6
|
if (Object.hasOwn(globalThis, configRuntimeSymbol))
|
|
8
7
|
throw new Error("Managed Node/Vitest config runtime is already installed");
|
|
8
|
+
// Only the Vitest parent requests config policy. Fork workers still validate
|
|
9
|
+
// their own input, but do not load the unused TypeScript alias-policy graph.
|
|
10
|
+
let loadedConfigRuntime;
|
|
11
|
+
const load = Object.freeze(() => {
|
|
12
|
+
loadedConfigRuntime ??= (async () => {
|
|
13
|
+
const consumerResolutionRuntime = await import(${JSON.stringify(n)});
|
|
9
14
|
const configRuntimeFunctions = [
|
|
10
15
|
consumerResolutionRuntime.assertNodeVitestConsumerResolution,
|
|
11
16
|
consumerResolutionRuntime.createConsumerTypeScriptPathsPlugin,
|
|
@@ -16,13 +21,17 @@ for (const value of configRuntimeFunctions) {
|
|
|
16
21
|
throw new Error("Managed Node/Vitest config runtime is invalid");
|
|
17
22
|
Object.freeze(value);
|
|
18
23
|
}
|
|
19
|
-
|
|
24
|
+
return Object.freeze({
|
|
20
25
|
assertNodeVitestConsumerResolution: configRuntimeFunctions[0],
|
|
21
26
|
createConsumerTypeScriptPathsPlugin: configRuntimeFunctions[1],
|
|
22
27
|
isNodeVitestManagedDependencyImporter: configRuntimeFunctions[2],
|
|
23
28
|
managedTypeScriptFile: consumerResolutionRuntime.managedTypeScriptFile,
|
|
24
29
|
version: 1,
|
|
25
30
|
});
|
|
31
|
+
})();
|
|
32
|
+
return loadedConfigRuntime;
|
|
33
|
+
});
|
|
34
|
+
const configRuntime = Object.freeze({ load, version: 1 });
|
|
26
35
|
Object.defineProperty(
|
|
27
36
|
globalThis,
|
|
28
37
|
configRuntimeSymbol,
|
|
@@ -62,9 +71,20 @@ __testkitRegisterNodeVitestRepositoryConfiguration(
|
|
|
62
71
|
if (installedRuntimeInput?.invocationId !== ${JSON.stringify(e.runtimeInput.expectedInvocationId)}
|
|
63
72
|
|| installedRuntimeInput.profileId !== ${JSON.stringify(e.runtimeInput.expectedProfileId)}
|
|
64
73
|
|| installedRuntimeInput.taskId !== ${JSON.stringify(e.runtimeInput.expectedTaskId)})
|
|
65
|
-
throw new Error("Managed Node/Vitest input bootstrap was not installed in the Vitest parent");`,s=`const
|
|
74
|
+
throw new Error("Managed Node/Vitest input bootstrap was not installed in the Vitest parent");`,s=`const configRuntimeSupplier = globalThis[
|
|
66
75
|
Symbol.for(${JSON.stringify(u)})
|
|
67
76
|
];
|
|
77
|
+
if (
|
|
78
|
+
typeof configRuntimeSupplier !== "object" ||
|
|
79
|
+
configRuntimeSupplier === null ||
|
|
80
|
+
!Object.isFrozen(configRuntimeSupplier) ||
|
|
81
|
+
Reflect.ownKeys(configRuntimeSupplier).length !== 2 ||
|
|
82
|
+
configRuntimeSupplier.version !== 1 ||
|
|
83
|
+
typeof configRuntimeSupplier.load !== "function" ||
|
|
84
|
+
!Object.isFrozen(configRuntimeSupplier.load)
|
|
85
|
+
)
|
|
86
|
+
throw new Error("Managed Node/Vitest config runtime supplier is invalid");
|
|
87
|
+
const configRuntime = await configRuntimeSupplier.load();
|
|
68
88
|
const configRuntimeKeys = [
|
|
69
89
|
"assertNodeVitestConsumerResolution",
|
|
70
90
|
"createConsumerTypeScriptPathsPlugin",
|
|
@@ -98,7 +118,7 @@ if (managedTypeScriptFile !== ${JSON.stringify(e.entrypoints.packages.typescript
|
|
|
98
118
|
${n}
|
|
99
119
|
${s}
|
|
100
120
|
const managedAliases = Object.freeze(${JSON.stringify(t.aliases)});
|
|
101
|
-
const managedPackageNames = Object.freeze(${JSON.stringify(
|
|
121
|
+
const managedPackageNames = Object.freeze(${JSON.stringify(R.map(({packageName:r})=>r))});
|
|
102
122
|
const managedDependencyRoots = Object.freeze(${JSON.stringify(t.dependencyRoots)});
|
|
103
123
|
const consumerResolutionPolicy = Object.freeze({
|
|
104
124
|
aliases: managedAliases,
|
|
@@ -115,6 +135,7 @@ export default {
|
|
|
115
135
|
enforce: "pre",
|
|
116
136
|
name: "testkit-node-vitest-managed-direct-packages",
|
|
117
137
|
async resolveId(specifier, importer, options) {
|
|
138
|
+
|
|
118
139
|
const managedImporter = isNodeVitestManagedDependencyImporter(
|
|
119
140
|
importer,
|
|
120
141
|
managedDependencyRoots,
|
|
@@ -146,6 +167,7 @@ export default {
|
|
|
146
167
|
${c}
|
|
147
168
|
],
|
|
148
169
|
root: ${JSON.stringify(e.rootDirectory)},
|
|
170
|
+
cacheDir: ${JSON.stringify(i.join(i.dirname(e.vitestConfigFile),"cache"))},
|
|
149
171
|
resolve: {
|
|
150
172
|
alias: Object.entries(managedAliases).map(([specifier, replacement]) => ({
|
|
151
173
|
find: exactSpecifierPattern(specifier),
|
|
@@ -153,9 +175,11 @@ export default {
|
|
|
153
175
|
})),
|
|
154
176
|
},
|
|
155
177
|
test: {
|
|
156
|
-
${e.runtimeInput.expectedProfileId==="node-vitest-data"?`runner: ${JSON.stringify(
|
|
157
|
-
|
|
158
|
-
|
|
178
|
+
${e.runtimeInput.expectedProfileId==="node-vitest-data"?`runner: ${JSON.stringify(m)},`:""}
|
|
179
|
+
// The installed SDK is compiled JavaScript. Use its native module instance
|
|
180
|
+
// for setup and consumer imports instead of transforming a second instance.
|
|
181
|
+
server: { deps: { external: ${JSON.stringify([e.entrypoints.config,...e.runtimeInput.expectedProfileId==="node-vitest-data"?[m,e.entrypoints.data]:[]].map(r=>r.replaceAll(i.sep,"/")))}.map(exactSpecifierPattern) } },
|
|
182
|
+
reporters: [[${JSON.stringify(S("node-vitest"))}, ${JSON.stringify(e.report)}]],
|
|
159
183
|
allowOnly: false,
|
|
160
184
|
disableConsoleIntercept: true,
|
|
161
185
|
environment: "node",
|
|
@@ -174,4 +198,4 @@ export default {
|
|
|
174
198
|
watch: false,
|
|
175
199
|
},
|
|
176
200
|
};
|
|
177
|
-
`}const
|
|
201
|
+
`}const v=new Set(["!","(",")","*","+","?","@","[","\\","]","{","}"]);export function toVitestIncludePattern(e){return Array.from(e.replaceAll(i.sep,"/"),t=>v.has(t)?`\\${t}`:t).join("")}
|
|
@@ -49,6 +49,7 @@ export interface InstallNodeVitestRuntimeInputRequest {
|
|
|
49
49
|
readonly expectedInvocationId: string;
|
|
50
50
|
readonly expectedProfileId: NodeVitestProfileId;
|
|
51
51
|
readonly expectedSha256: string;
|
|
52
|
+
readonly expectedSizeBytes: number;
|
|
52
53
|
readonly expectedTaskId: string;
|
|
53
54
|
}
|
|
54
55
|
export declare function writeOwnedNodeVitestRuntimeInput(taskDirectory: string, document: NodeVitestRuntimeInputDocument): Promise<OwnedNodeVitestRuntimeInput>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import m from"node:
|
|
1
|
+
import m from"node:path";import{readVerifiedOwnedFile as a,writeOwnedRuntimeInput as w,RUNTIME_INPUT_BYTE_MEASURE as S}from"../../runtime-input-file.js";import{requireBoundedJsonValue as O,RUNTIME_INPUT_JSON_BOUNDS as h}from"../../contracts/json-bounds.js";import{ENGINE_PROCESS_RESERVED_ENVIRONMENT_KEYS as V}from"../../../process/environment.js";import{installManagedProcessEnvironmentOverlay as R}from"../../../process/environment-overlay.js";export const NODE_VITEST_RUNTIME_INPUT_VERSION=1,NODE_VITEST_RUNTIME_INPUT_FILENAME="runtime-input.json",NODE_VITEST_RUNTIME_INPUT_FILE_ENV="TESTKIT_NODE_VITEST_INPUT_FILE",NODE_VITEST_RUNTIME_INPUT_SHA256_ENV="TESTKIT_NODE_VITEST_INPUT_SHA256",NODE_VITEST_RUNTIME_INPUT_SYMBOL="elench.testkit.node-vitest.runtime-input.v1",NODE_VITEST_DATA_BUDGET_FAILURE_DETAIL_KIND="data-flow-budget",NODE_VITEST_DATA_FAILURE_DETAILS_ARTIFACT_KIND="testkit.failure-details",NODE_VITEST_DATA_FAILURE_DETAILS_ARTIFACT_NAME="data-flow-failure-details";const d={filename:NODE_VITEST_RUNTIME_INPUT_FILENAME,label:"Node/Vitest runtime input",maxSizeBytes:8*1024*1024},f=512,p=4*1024*1024,l=new Set(["BASE_URL","FORCE_COLOR","NODE_OPTIONS","NO_COLOR","UPDATE_SNAPSHOT",...V].map(e=>e.toUpperCase()));export const MAX_NODE_VITEST_SPAWN_ENVIRONMENT_UNITS=24*1024;export async function writeOwnedNodeVitestRuntimeInput(e,t){return w(e,t,d)}export async function verifyOwnedNodeVitestRuntimeInput(e){await a(e,d)}export function selectNodeVitestForwardedEnvironment(e){const t={...e};return _(t),t}export function assertNodeVitestSpawnEnvironmentBudget(e){if(Object.entries(e).reduce((n,[i,r])=>n+i.length+r.length+2,1)>MAX_NODE_VITEST_SPAWN_ENVIRONMENT_UNITS)throw new Error(`Node/Vitest process environment exceeds the ${MAX_NODE_VITEST_SPAWN_ENVIRONMENT_UNITS.toString()} character environment-block budget`)}export async function installNodeVitestRuntimeInput(e){const t=process.env[NODE_VITEST_RUNTIME_INPUT_FILE_ENV],n=process.env[NODE_VITEST_RUNTIME_INPUT_SHA256_ENV];if(t!==e.expectedFile)throw new Error("Node/Vitest runtime input pointer does not match its engine-owned path");if(n!==e.expectedSha256)throw new Error("Node/Vitest runtime input digest does not match its prepared value");const i={directory:m.dirname(e.expectedFile),file:e.expectedFile,sha256:e.expectedSha256,sizeBytes:e.expectedSizeBytes},r=await a(i,d);let s;try{s=JSON.parse(r)}catch(u){throw new Error("Node/Vitest runtime input contains invalid JSON",{cause:u})}const o=E(I(s));if(o.invocationId!==e.expectedInvocationId||o.profileId!==e.expectedProfileId||o.taskId!==e.expectedTaskId)throw new Error("Node/Vitest runtime input identity does not match its task");R({environment:o.environment,label:"Node/Vitest"}),Object.defineProperty(globalThis,Symbol.for(NODE_VITEST_RUNTIME_INPUT_SYMBOL),{configurable:!0,enumerable:!1,value:o,writable:!1}),N(o)}export function applyNodeVitestEngineEnvironment(){const e=readNodeVitestRuntimeInput();if(e===null)throw new Error("Node/Vitest runtime input is not installed");N(e)}function N(e){const t=e.context.baseUrl;typeof t=="string"?process.env.BASE_URL=t:Reflect.deleteProperty(process.env,"BASE_URL")}export function readNodeVitestRuntimeInput(){const e=Reflect.get(globalThis,Symbol.for(NODE_VITEST_RUNTIME_INPUT_SYMBOL));return e===void 0?null:E(I(e,"Installed Node/Vitest runtime input"))}function I(e,t="Node/Vitest runtime input"){if(O(e,t,h,S),!c(e))throw new Error(`${t} must contain an object`);const n=new Set(["context","engineId","environment","fixtures","invocationId","profileId","setupData","taskId","version"]);for(const r of Object.keys(e))if(!n.has(r))throw new Error(`${t} contains unknown field ${r}`);if(e.version!==NODE_VITEST_RUNTIME_INPUT_VERSION)throw new Error(`${t}.version must be 1`);if(e.engineId!=="node-vitest")throw new Error(`${t}.engineId must be "node-vitest"`);if(!D(e.profileId))throw new Error(`${t}.profileId is invalid`);if(!c(e.context)||!c(e.fixtures))throw new Error(`${t} context and fixtures must be objects`);const i=x(e.environment,`${t}.environment`);return _(i),{context:e.context,engineId:"node-vitest",environment:i,fixtures:e.fixtures,invocationId:T(e.invocationId,`${t}.invocationId`),profileId:e.profileId,...Object.hasOwn(e,"setupData")?{setupData:e.setupData}:{},taskId:T(e.taskId,`${t}.taskId`),version:NODE_VITEST_RUNTIME_INPUT_VERSION}}function _(e){const t=Object.entries(e);if(t.length>f)throw new Error(`Node/Vitest forwarded environment exceeds the ${f.toString()} entry limit`);let n=0;const i=new Set;for(const[r,s]of t){y(r,s,"Node/Vitest forwarded environment");const o=r.toUpperCase();if(i.has(o))throw new Error(`Node/Vitest forwarded environment contains case-insensitive duplicate key ${r}`);if(i.add(o),o.startsWith("TESTKIT_")||o.startsWith("VITEST_")||l.has(o))throw new Error(`Node/Vitest forwarded environment contains reserved key ${r}`);n+=Buffer.byteLength(r)+Buffer.byteLength(s)+2}if(n>p)throw new Error(`Node/Vitest forwarded environment exceeds the ${p.toString()} byte limit`)}function x(e,t){if(!c(e))throw new Error(`${t} must be an object`);const n=Object.entries(e);if(!n.every(i=>typeof i[1]=="string"))throw new Error(`${t} values must all be strings`);return Object.fromEntries(n)}function y(e,t,n){if(e.length===0||e.includes("=")||e.includes("\0"))throw new Error(`${n} contains an invalid variable name`);if(t.includes("\0"))throw new Error(`${n}.${e} contains a NUL character`)}function E(e){if(e!==null&&typeof e=="object"&&!Object.isFrozen(e)){Object.freeze(e);for(const t of Object.values(e))E(t)}return e}function T(e,t){if(typeof e!="string"||e.length===0)throw new Error(`${t} must be a non-empty string`);return e}function D(e){return e==="node-vitest-contract"||e==="node-vitest-data"}function c(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}
|
|
@@ -6,9 +6,11 @@ export declare const RUNTIME_INPUT_BYTE_MEASURE: {
|
|
|
6
6
|
/**
|
|
7
7
|
* Single-writer protocol for the files a host prepares inside an owned task
|
|
8
8
|
* directory and later hands to a guest process by pointer + digest. Writes are
|
|
9
|
-
* exclusive-create (0o600),
|
|
9
|
+
* exclusive-create (0o600), closed, and atomically renamed into place; reads
|
|
10
10
|
* re-verify the file is still an owned regular file with the prepared size,
|
|
11
11
|
* owner-only permissions, identity realpath, and matching SHA-256 digest.
|
|
12
|
+
* These disposable handoffs require process-lifetime visibility, not persistence
|
|
13
|
+
* across machine power loss; durable evidence uses its separate synced writer.
|
|
12
14
|
*/
|
|
13
15
|
export interface OwnedFileProtocol {
|
|
14
16
|
readonly filename: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import u from"node:crypto";import a from"node:fs/promises";import o from"node:path";import{serializeBoundedJson as c,RUNTIME_INPUT_JSON_BOUNDS as h}from"./contracts/json-bounds.js";import{readStableEvidenceFile as y,evidenceFileAccess as b}from"./adapters/evidence-files.js";export const ownedRuntimeInputModuleUrl=new URL("../../dist/engines/runtime-input-file.js",import.meta.url).href,RUNTIME_INPUT_BYTE_MEASURE={unitLabel:"bytes",unitsOf:t=>Buffer.byteLength(t,"utf8")};export async function writeOwnedRuntimeInput(t,e,i){const
|
|
1
|
+
import u from"node:crypto";import a from"node:fs/promises";import o from"node:path";import{serializeBoundedJson as c,RUNTIME_INPUT_JSON_BOUNDS as h}from"./contracts/json-bounds.js";import{readStableEvidenceFile as y,evidenceFileAccess as b}from"./adapters/evidence-files.js";export const ownedRuntimeInputModuleUrl=new URL("../../dist/engines/runtime-input-file.js",import.meta.url).href,RUNTIME_INPUT_BYTE_MEASURE={unitLabel:"bytes",unitsOf:t=>Buffer.byteLength(t,"utf8")};export async function writeOwnedRuntimeInput(t,e,i){const r=c(e,i.label,h,RUNTIME_INPUT_BYTE_MEASURE),s=await m(t,i.label),w=Buffer.byteLength(r);if(w>i.maxSizeBytes)throw new Error(`${i.label} exceeds the ${i.maxSizeBytes.toString()} byte limit`);const f=o.join(s,i.filename),l=o.join(s,`.${i.filename}.${process.pid.toString()}.${u.randomUUID()}.tmp`);let n=null;try{n=await a.open(l,"wx",384),await n.writeFile(r,"utf8"),await n.close(),n=null,await a.rename(l,f),await a.chmod(f,384)}catch(d){throw await n?.close().catch(()=>{}),await a.rm(l,{force:!0}).catch(()=>{}),d}return{directory:s,file:f,sha256:u.createHash("sha256").update(r,"utf8").digest("hex"),sizeBytes:w}}export async function readVerifiedOwnedFile(t,e){const i=await m(t.directory,e.label);if(o.dirname(t.file)!==i)throw new Error(`${e.label} escaped its owned task directory`);const r=await a.lstat(t.file,{bigint:!0});if(!r.isFile()||r.isSymbolicLink())throw new Error(`${e.label} must be an owned regular file`);if(r.size!==BigInt(t.sizeBytes)||r.size>BigInt(e.maxSizeBytes))throw new Error(`${e.label} size changed after preparation`);if(process.platform!=="win32"&&(r.mode&63n)!==0n)throw new Error(`${e.label} permissions must be owner-only`);const s=await a.realpath(t.file);if(s!==t.file||o.dirname(s)!==i)throw new Error(`${e.label} resolved outside its owned task directory`);const{contents:w,sha256:f}=await y(s,e.maxSizeBytes,e.label,b,r),l=Buffer.from(t.sha256),n=Buffer.from(f);if(l.length!==n.length||!u.timingSafeEqual(n,l))throw new Error(`${e.label} integrity check failed`);return w.toString("utf8")}async function m(t,e){if(!o.isAbsolute(t))throw new Error(`${e} directory must be absolute`);const i=await a.realpath(t);if(!(await a.stat(i)).isDirectory())throw new Error(`${e} directory must be a directory`);return i}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SupervisedProcessHandle, SupervisedProcessReady } from "../process/index.js";
|
|
2
|
+
export { isRunManifestActive } from "../process/runs/inspection.js";
|
|
2
3
|
import type { RuntimeInstanceContext } from "./runtime-contexts.js";
|
|
3
4
|
import type { LocalServiceLifecycle } from "./services.js";
|
|
4
5
|
import { type ProductLifecycleFence } from "../process/lifecycle-fence.js";
|
|
@@ -66,6 +67,4 @@ export declare function createUnpublishedRunLifecycle(productDir: string, option
|
|
|
66
67
|
export declare function cleanupOwnedRunLifecycleWithinLifecycleFence(fence: Readonly<ProductLifecycleFence>, lifecycle: Readonly<RunLifecycle>): boolean;
|
|
67
68
|
export declare function readOwnedRunLifecycleWithinLifecycleFence(fence: Readonly<ProductLifecycleFence>, lifecycle: Readonly<RunLifecycle>): OwnedRunLifecycleState;
|
|
68
69
|
export declare function findPortOwner(productDir: string, { host, port }: Readonly<RunPortQuery>): RunPortOwner | null;
|
|
69
|
-
export declare function isRunManifestActive(manifest: Readonly<RunManifest>): boolean;
|
|
70
70
|
export declare function disposeFailedRunLifecycle(lifecycle: RunLifecycle): void;
|
|
71
|
-
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import H from"crypto";import{isTerminalRunStatus as x}from"../process/runs/schema.js";import{observeProcessCreationIdentity as U}from"../process/process-identity.js";import{isRunManifestActive as B}from"../process/runs/inspection.js";export{isRunManifestActive}from"../process/runs/inspection.js";import{normalizeOperationReason as _}from"../domain/operation-reason.js";import{finalizeLiveRunArtifact as Y}from"../evidence/index.js";import{captureProcessCreationIdentity as J}from"../process/service-processes.js";import{requireProductLifecycleFenceProductDir as K,withProductLifecycleFence as Q}from"../process/lifecycle-fence.js";import{createRunManifest as X,listRunManifests as $,readRunMaintenanceStopRequest as Z,removeRunManifestFile as G,requireSafeRunId as ee,RUN_MANIFEST_SCHEMA_VERSION as te,runManifestPath as ne,writeRunManifest as re}from"../process/runs/manifest.js";const ie=1e3,oe=String,T=new WeakMap;export function createUnpublishedRunLifecycle(n,r={}){const i=r.runId===void 0?ce():ee(r.runId,"Testkit run id"),a=ne(n,i),s=new AbortController,m=new AbortController,u=r.pid===void 0?process.pid:r.pid;if(!Number.isSafeInteger(u)||u<0)throw new Error("Testkit run pid must be a non-negative safe integer");const b=u>0?J(u):null;if(u>0&&b===null)throw new Error(`Could not capture the Testkit run process identity for pid ${u.toString()}`);let f={incarnationId:H.randomUUID(),schemaVersion:te,runId:i,pid:u,processIdentity:typeof b=="string"?b:null,controller:ae(r.controller),status:"starting",startedAt:new Date().toISOString(),heartbeatAt:r.heartbeat===!1?null:new Date().toISOString(),interruptReason:null,services:[],runtimeDirs:[]};const O=[];let w,D=null;const y=new Set,S=new Set,L=r.keepAlive===!0?setInterval(()=>{},ie):null;let R=null,g=null,v=null,P=!1;function j(e){const t=T.get(I);if(!t)throw new Error("Testkit run lifecycle ownership is unavailable");if(!t.published){X(n,e),t.published=!0;return}re(n,e)}function A(){const e=Z(n,i);return e===null||e.targetIncarnationId!==f.incarnationId||e.targetPid!==u||e.targetProcessIdentity!==f.processIdentity||e.coordinatorPid===D?.coordinatorPid&&e.coordinatorProcessIdentity===D.coordinatorProcessIdentity?null:U(e.coordinatorPid,e.coordinatorProcessIdentity)==="different"?(D=e,null):e}function h(e){k("mutate run state");const t=se(f);e(t),t.heartbeatAt!==null&&(t.heartbeatAt=new Date().toISOString()),j(t),f=t}function k(e){if(g!==null)throw new Error(`Cannot ${e} after Testkit run ${i} finalization is committed`)}function W(){R||(R=setInterval(()=>{},1e3))}function q(){R&&(clearInterval(R),R=null)}function N(){for(const e of S)clearInterval(e);S.clear()}function z(e="controller process exited before Testkit run finalized"){if(P||x(f.status))return!1;const t=_(e,"controller process exited before Testkit run finalized"),o=new Date,l=f.status==="interrupting"||s.signal.aborted?"interrupted":"failed",c=l==="interrupted"?f.interruptReason??v??t:t;return h(p=>{p.status=l,p.finishedAt=o.toISOString(),p.interruptReason=c,p.heartbeatAt!==null&&(p.heartbeatAt=o.toISOString())}),Y(n,{runId:i,reason:c,status:l,finishedAt:o}),!0}const F=()=>{z()};process.on("exit",F);const I={runId:i,manifestPath:a,maintenanceStopSignal:m.signal,signal:s.signal,commitFinalization(e="finished"){if(g!==null)return g;const t=A(),o=s.signal.aborted||t!==null||f.status==="interrupting",l=o?f.interruptReason??v??t?.reason??"run interrupted":null,c=Object.freeze({interruptReason:l,interrupted:o});return h(p=>{p.status=o?"interrupted":e,p.finishedAt=new Date().toISOString(),p.interruptReason=l}),g=c,N(),c},getInterruptReason(){return f.interruptReason??v},isMaintenanceStopCoordinated(){return A()!==null},isStopRequested(){return s.signal.aborted||g?.interrupted===!0},markRunning(){h(e=>{e.status="running"})},markFinished(e="finished"){h(t=>{t.status=e,t.finishedAt=new Date().toISOString()})},heartbeat(e={}){h(t=>{t.heartbeatAt=new Date().toISOString(),e&&typeof e=="object"&&!Array.isArray(e)&&(t.session={...t.session||{},...e})})},startHeartbeat(e=5e3,t=null){k("start a run heartbeat");const o=Number.isFinite(Number(e))&&Number(e)>0?Math.floor(Number(e)):5e3,l=setInterval(()=>{let c;try{c=typeof t=="function"?t():{}}catch{c={}}I.heartbeat(c)},o);return l.unref(),S.add(l),()=>{clearInterval(l),S.delete(l)}},requestStop(e="interrupted"){if(g!==null||x(f.status))return;const t=A(),o=t?t.reason:_(e,"interrupted"),l=!s.signal.aborted;l&&(v=o),W();let c=null;if(l)try{h(d=>{d.status="interrupting",d.interruptReason=o})}catch(d){c=d instanceof Error?d:new Error("Failed to persist the Testkit stop request",{cause:d})}t!==null&&!m.signal.aborted&&m.abort(new Error(`Testkit run ${i} is being stopped by external maintenance`));const p=[...y].map(({supervisor:d})=>d.stop(t===null?void 0:Date.parse(t.requestedAt)+d.shutdownTimeoutMs));if(Promise.allSettled(p).then(q),s.signal.aborted||s.abort(new Error(`testkit run interrupted (${o})`)),!!l&&c!==null)throw c},trackGraphContext(e){h(t=>{le(t.runtimeDirs,e.runtimeDir)})},registerService(e,t,o,l){if(k("register application service ownership"),o.pid!==t.pid||(process.platform==="win32"?o.recovery.processGroupId!==null:o.recovery.processGroupId!==o.pid))throw new Error("Application service supervisor returned an invalid ownership handoff");const c={supervisor:t,unsubscribe:()=>{}};y.add(c);const p=ue(e);h(d=>{d.services=d.services.filter(M=>M.pid!==o.pid),d.services.push({serviceName:e.name,runtimeLabel:e.runtimeLabel,command:e.testkit.local.start||null,cwd:l,pid:o.pid,processGroupId:o.recovery.processGroupId,processIdentity:o.recovery.creationIdentity,ports:p,startedAt:new Date().toISOString(),shutdownTimeoutMs:t.shutdownTimeoutMs,shutdownDeadlineAt:t.shutdownDeadlineAt})}),c.unsubscribe=t.onShutdownStarted(d=>{h(M=>{const E=M.services.find(V=>V.pid===o.pid);E&&(E.shutdownDeadlineAt=E.shutdownDeadlineAt==null?d:Math.min(E.shutdownDeadlineAt,d))})})},unregisterService(e){k("unregister application service ownership");for(const t of y)t.supervisor.pid===e&&(t.unsubscribe(),y.delete(t));h(t=>{t.services=t.services.filter(o=>o.pid!==e)})},installSignalHandlers(){if(!(O.length>0)&&!(u!==process.pid||U(u,f.processIdentity)!=="matches")){for(const e of["SIGINT","SIGTERM","SIGHUP"]){const t=I.requestStop.bind(I,e);process.on(e,t),O.push([e,t])}process.platform==="win32"&&(w=setInterval(()=>{const e=A();e!==null&&(clearInterval(w),I.requestStop(e.reason))},100),w.unref(),S.add(w))}},removeSignalHandlers(){w&&(clearInterval(w),S.delete(w),w=void 0);for(const[e,t]of O.splice(0))process.off(e,t)},async removeManifest(){await Q(n,e=>cleanupOwnedRunLifecycleWithinLifecycleFence(e,I))},finalizeForControllerExit:z,dispose(){P=!0;for(const e of y)e.unsubscribe();process.off("exit",F),I.removeSignalHandlers(),L&&clearInterval(L),q(),N()}};return T.set(I,{incarnationId:f.incarnationId,pid:u,processIdentity:f.processIdentity,productDir:n,published:!1,runId:i}),I}function se(n){const r={...n,controller:n.controller===null?null:{...n.controller},runtimeDirs:[...n.runtimeDirs],services:n.services.map(i=>({...i,ports:i.ports.map(a=>({...a}))}))};return n.session!==void 0&&(r.session=n.session===null?null:{...n.session}),r}function ae(n){return!n||typeof n!="object"||Array.isArray(n)?null:Object.fromEntries(Object.entries(n).filter(([,r])=>r!==void 0).map(([r,i])=>[r,i===null?null:oe(i)]))}export function cleanupOwnedRunLifecycleWithinLifecycleFence(n,r){const{manifest:i,ownership:a,productDir:s}=readOwnedRunLifecycleWithinLifecycleFence(n,r);if(!i)return!1;if(i.services.length>0)throw new Error(`Cannot clean Testkit run ${r.runId} service ownership while holding the lifecycle fence`);return G(s,r.runId),a.published=!1,!0}export function readOwnedRunLifecycleWithinLifecycleFence(n,r){const i=K(n),a=T.get(r);if(!a||a.productDir!==i)throw new Error("Cannot clean a Testkit run without matching lifecycle ownership");if(a.runId!==r.runId)throw new Error("Testkit run lifecycle identity has been modified");const s=$(i).find(m=>m.runId===r.runId);if(s!==void 0&&(s.incarnationId!==a.incarnationId||s.pid!==a.pid||s.processIdentity!==a.processIdentity))throw new Error(`Testkit run ${r.runId} ownership no longer matches its manifest`);return{manifest:s??null,ownership:a,productDir:i}}export function findPortOwner(n,{host:r,port:i}){const a=$(n);for(const s of a)for(const m of s.services)for(const u of m.ports)if(C(u.host)===C(r)&&u.port===Number(i))return{manifest:s,service:m,active:B(s)};return null}function ue(n){const r=new Set,i=[];for(const a of[n.testkit.local.baseUrl,n.testkit.local.readyUrl])if(a)try{const s=new URL(a),m=C(s.hostname),u=Number(s.port||(s.protocol==="https:"?443:80)),b=`${m}:${u.toString()}`;if(r.has(b))continue;r.add(b),i.push({host:m,port:u})}catch{}return i}function le(n,r){r&&(n.includes(r)||n.push(r))}function ce(){return`${Date.now().toString()}-${process.pid.toString()}-${H.randomBytes(4).toString("hex")}`}function C(n){return n?n==="::1"?"127.0.0.1":n:"127.0.0.1"}export function disposeFailedRunLifecycle(n){const r=T.get(n);n.dispose(),n.removeSignalHandlers(),r?.published===!0&&G(r.productDir,n.runId)}
|