@gfdelarue/testkit 0.3.4 → 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.
Files changed (121) hide show
  1. package/README.md +4 -0
  2. package/dist/app/doctor/index.js +1 -1
  3. package/dist/app/inspect/artifacts.d.ts +1 -0
  4. package/dist/app/inspect/artifacts.js +1 -1
  5. package/dist/app/inspect/index.js +1 -1
  6. package/dist/app/lint/index.js +1 -1
  7. package/dist/app/lint/native.js +1 -1
  8. package/dist/app/lint/policy.js +1 -1
  9. package/dist/app/lint/rules/data.js +1 -1
  10. package/dist/app/lint/rules/engine-imports.js +1 -1
  11. package/dist/app/lint/rules/syntax.js +1 -1
  12. package/dist/app/lint/rules/unit.js +1 -1
  13. package/dist/app/lint/shared.d.ts +1 -1
  14. package/dist/app/lint/shared.js +1 -1
  15. package/dist/app/run/orchestrator.js +1 -1
  16. package/dist/app/run/request.js +1 -1
  17. package/dist/app/setup/playwright.js +1 -1
  18. package/dist/app/status/index.d.ts +2 -0
  19. package/dist/app/status/index.js +1 -1
  20. package/dist/app/status/runs.d.ts +2 -2
  21. package/dist/app/status/runs.js +1 -1
  22. package/dist/app/stop/index.js +1 -1
  23. package/dist/app/typecheck/index.d.ts +2 -0
  24. package/dist/app/typecheck/index.js +4 -4
  25. package/dist/cli/commands/artifacts.js +2 -2
  26. package/dist/cli/commands/db/schema/refresh.js +1 -1
  27. package/dist/cli/commands/db/schema/verify.js +1 -1
  28. package/dist/cli/commands/doctor.js +1 -1
  29. package/dist/cli/operations/db/schema/refresh/operation.d.ts +1 -0
  30. package/dist/cli/operations/db/schema/refresh/operation.js +1 -1
  31. package/dist/cli/operations/db/schema/verify/operation.d.ts +1 -0
  32. package/dist/cli/operations/db/schema/verify/operation.js +1 -1
  33. package/dist/cli/operations/run/operation.js +1 -1
  34. package/dist/config/loader/discover-tests.d.ts +5 -0
  35. package/dist/config/loader/discover-tests.js +1 -1
  36. package/dist/config/loader/discovery.js +2 -2
  37. package/dist/config/loader/file-metadata.js +1 -1
  38. package/dist/config/loader/index.d.ts +2 -1
  39. package/dist/config/loader/index.js +1 -1
  40. package/dist/config/loader/load.d.ts +2 -0
  41. package/dist/config/loader/load.js +1 -1
  42. package/dist/config/loader/managed-configs.d.ts +2 -0
  43. package/dist/config/loader/managed-configs.js +1 -1
  44. package/dist/config/loader/paths.d.ts +1 -1
  45. package/dist/config/loader/paths.js +1 -1
  46. package/dist/config/loader/skip-config.d.ts +1 -1
  47. package/dist/config/loader/test-selection.d.ts +15 -0
  48. package/dist/config/loader/test-selection.js +1 -0
  49. package/dist/database/index.d.ts +1 -3
  50. package/dist/database/index.js +1 -1
  51. package/dist/database/lifecycle-cleanup.d.ts +7 -0
  52. package/dist/database/lifecycle-cleanup.js +1 -0
  53. package/dist/database/local-postgres.js +1 -1
  54. package/dist/database/schema-source.js +3 -3
  55. package/dist/engines/adapters/evidence-files.js +1 -1
  56. package/dist/engines/catalog.d.ts +3 -8
  57. package/dist/engines/catalog.js +1 -1
  58. package/dist/engines/definitions.d.ts +8 -1
  59. package/dist/engines/definitions.js +1 -1
  60. package/dist/engines/generated-performance.d.ts +1 -0
  61. package/dist/engines/generated-performance.js +12 -0
  62. package/dist/engines/installations/node-vitest.d.ts +8 -22
  63. package/dist/engines/installations/node-vitest.js +1 -1
  64. package/dist/engines/installations/package-requirements.d.ts +28 -0
  65. package/dist/engines/installations/package-requirements.js +1 -0
  66. package/dist/engines/installations/playwright.d.ts +1 -11
  67. package/dist/engines/installations/playwright.js +1 -1
  68. package/dist/engines/k6/adapter/repository-configuration.d.ts +1 -1
  69. package/dist/engines/k6/adapter/repository-configuration.js +1 -1
  70. package/dist/engines/managed-module-resolution.js +111 -21
  71. package/dist/engines/manager.d.ts +1 -0
  72. package/dist/engines/manager.js +1 -1
  73. package/dist/engines/node-vitest/adapter/adapter.d.ts +1 -1
  74. package/dist/engines/node-vitest/adapter/adapter.js +1 -1
  75. package/dist/engines/node-vitest/adapter/consumer-typescript-paths.js +1 -1
  76. package/dist/engines/node-vitest/adapter/resolution.d.ts +1 -1
  77. package/dist/engines/node-vitest/adapter/resolution.js +1 -1
  78. package/dist/engines/node-vitest/adapter/workspace.js +33 -9
  79. package/dist/engines/node-vitest/runtime/runtime-input.d.ts +1 -0
  80. package/dist/engines/node-vitest/runtime/runtime-input.js +1 -1
  81. package/dist/engines/runtime-input-file.d.ts +3 -1
  82. package/dist/engines/runtime-input-file.js +1 -1
  83. package/dist/environment/lifecycle-controller.d.ts +1 -2
  84. package/dist/environment/lifecycle-controller.js +1 -1
  85. package/dist/environment/lifecycle.js +1 -1
  86. package/dist/environment/maintenance.d.ts +1 -1
  87. package/dist/environment/maintenance.js +1 -1
  88. package/dist/environment/ready-environment.d.ts +1 -1
  89. package/dist/environment/ready-environment.js +1 -1
  90. package/dist/environment/runtime-contexts.d.ts +1 -1
  91. package/dist/environment/runtime-contexts.js +1 -1
  92. package/dist/environment/runtime-preparation.js +1 -1
  93. package/dist/environment/services.js +1 -1
  94. package/dist/environment/template.d.ts +1 -1
  95. package/dist/environment/template.js +1 -1
  96. package/dist/evidence/bundles.js +1 -1
  97. package/dist/evidence/read-run.js +1 -1
  98. package/dist/evidence/reading.d.ts +1 -1
  99. package/dist/evidence/reading.js +1 -1
  100. package/dist/evidence/storage.js +2 -2
  101. package/dist/kernel/error-serialization.js +1 -1
  102. package/dist/kernel/work-metrics.d.ts +6 -0
  103. package/dist/kernel/work-metrics.js +1 -0
  104. package/dist/process/capture-output.js +1 -1
  105. package/dist/process/execa-bundle.json +1 -1
  106. package/dist/process/files/product-dir.d.ts +1 -0
  107. package/dist/process/files/product-dir.js +1 -0
  108. package/dist/process/locks.js +4 -4
  109. package/dist/process/readiness.js +1 -1
  110. package/dist/process/runs/inspection.d.ts +5 -0
  111. package/dist/process/runs/inspection.js +1 -0
  112. package/dist/process/runs/manifest.d.ts +1 -0
  113. package/dist/process/runs/manifest.js +1 -1
  114. package/dist/process/service-processes.d.ts +1 -1
  115. package/dist/process/service-processes.js +1 -1
  116. package/dist/toolchains/index.js +2 -2
  117. package/docs/engine-architecture.md +4 -1
  118. package/docs/test-types.md +9 -0
  119. package/package.json +2 -1
  120. package/dist/cli/renderers/artifact-preview.d.ts +0 -4
  121. package/dist/cli/renderers/artifact-preview.js +0 -1
@@ -1 +1 @@
1
- import{makeTempDirAsync as R}from"../../../process/files/temp-dir.js";import{classifyManagedProcessResult as W,terminalOutcome as y,processOutputEvidence as z,emptyEvidence as J,failureFromError as f,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 A}from"../../adapters/workspace.js";import d 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{collectNodeVitestEvidence as it}from"./evidence.js";import{assertNodeVitestSpawnEnvironmentBudget as rt,NODE_VITEST_RUNTIME_INPUT_FILE_ENV as ot,NODE_VITEST_RUNTIME_INPUT_SHA256_ENV as nt,selectNodeVitestForwardedEnvironment as st,verifyOwnedNodeVitestRuntimeInput as at,writeOwnedNodeVitestRuntimeInput as ct}from"../runtime/runtime-input.js";import{writeNodeVitestWorkspace as ut}from"./workspace.js";import{engineEvidenceIdentity as dt}from"../../adapters/evidence-identity.js";const E={codePrefix:"NODE_VITEST",label:"Node/Vitest"};class L extends Error{constructor(e){const i=x(e);super(i.message,{cause:i}),this.name="NodeVitestConsumerPreparationError"}}export const nodeVitestExecutorDependencies={removeOwnedDirectory:t=>d.rm(t,{force:!0,recursive:!0}),writeRuntimeInput:ct,writeWorkspace:ut};export async function executeNodeVitestEngine(t,e,i,r=nodeVitestExecutorDependencies){const o=await pt(t,e,r,i);return Y(t,o.tasks,E,(n,s)=>ft(t,o,n,s),vt(t,o.installation))}async function pt(t,e,i,r){const o=t.profile.id,n=e.packages.vitest.cli,s=await Q(t.workspace.rootDirectory);M(t.workspace.temporaryDirectory,"temporary directory"),M(t.workspace.artifactDirectory,"artifact directory"),await d.mkdir(t.workspace.temporaryDirectory,{recursive:!0}),await d.mkdir(t.workspace.artifactDirectory,{recursive:!0});const l=await d.realpath(t.workspace.temporaryDirectory),p=await d.realpath(t.workspace.artifactDirectory),I=Object.freeze({...e.authoringEntrypoints,cli:e.packages.vitest.cli,packages:e.packages}),T=await R(`node-vitest-${A(t.invocationId)}-`,{parentDir:l});r.defer(()=>i.removeOwnedDirectory(T));const j=await tt(T),v=[];for(const[U,a]of t.tasks.entries()){const m=q(t,a),S=`${U.toString().padStart(3,"0")}-${A(a.id)}`,u=c.join(T,S),_=await R(`${S}-`,{parentDir:p}),w=c.join(u,"testkit-vitest-report.json"),K=c.join(_,"data","manifest.json"),B=c.join(u,"setup.mjs"),F=c.join(u,"input-bootstrap.mjs"),O=c.join(u,"resolution-bootstrap.cjs"),V=c.join(u,"vitest.config.mjs");try{await d.mkdir(u,{mode:448,recursive:!1});const D=await N(()=>P(s,a.file,"Node/Vitest task file")),k=await N(()=>Tt(s,m)),$=await N(()=>Et(t,o,a,m)),g=await i.writeRuntimeInput(u,$);await i.writeWorkspace({report:{identity:{engineId:"node-vitest",invocationId:t.invocationId,taskId:a.id,sourceFile:a.file},reportFile:w,workspaceDirectory:u},configFile:k.testkitConfig,entrypoints:I,inputBootstrapFile:F,resolutionBootstrapFile:O,rootDirectory:s,runtimeInput:{expectedFile:g.file,expectedInvocationId:t.invocationId,expectedProfileId:o,expectedSha256:g.sha256,expectedTaskId:a.id},setupFile:B,sourceFile:D,timeoutMs:Math.max(1,a.deadlineMs-Date.now()),tsconfigFile:k.tsconfig,vitestConfigFile:V}),v.push({artifactDirectory:_,inputs:m,task:a,preparationFailure:null,prepared:{dataManifestFile:K,inputBootstrapFile:F,reportFile:w,resolutionBootstrapFile:O,runtimeInput:g,vitestConfigFile:V}})}catch(D){v.push({artifactDirectory:_,inputs:m,task:a,preparationFailure:_t(D),prepared:null})}}return Object.freeze({cliFile:n,installation:e,invocationEnvironment:j.environment,profileId:o,rootDirectory:s,tasks:Object.freeze(v)})}async function ft(t,e,i,r){const o=Math.max(0,i.task.deadlineMs-r);try{await at(i.prepared.runtimeInput)}catch(p){return y(i.task.id,"failed",r,J(),{...f(p,"NODE_VITEST_INPUT_INVALID"),classification:"infra"})}const n=await et({args:[e.cliFile,"run","--config",i.prepared.vitestConfigFile,"--configLoader","runner"],cwd:e.rootDirectory,displayLabel:"Node/Vitest",env:mt(t,e,i),executable:process.execPath,signal:t.signal,timeoutMs:o});let s;try{s=await it({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:n.stderr,stdout:n.stdout})}catch(p){const I=z(n);return y(i.task.id,"failed",r,I,nodeVitestEvidenceCollectionFailure(n,p))}const l=classifyNodeVitestProcessResult(n,s,i.task.timeoutMs,Z(t.signal.reason));return y(i.task.id,l.status,r,s.evidence,l.failure)}export function classifyNodeVitestProcessResult(t,e,i,r){const o=W(t,i,r,E);return o!==null?o:e.reportSummary?.numTotalTests===0?{failure:{code:"NODE_VITEST_NO_TESTS",message:"Vitest did not discover any tests for the task file"},status:"failed"}:lt(e.reportSummary)?{failure:null,status:"skipped"}:t.exitCode!==0?H(t,E,e.reportFailureMessage??h(t.stderr)??h(t.stdout),"Vitest"):e.reportError!==null?{failure:{...f(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:f(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,E,i)??{...f(i,"NODE_VITEST_EVIDENCE_FAILED"),classification:"infra"}}function lt(t){return t!==null&&t.success&&t.numTotalTests>0&&t.numPassedTests===0&&t.numFailedTests===0&&(t.numPendingTests>0||t.numTodoTests>0)}function mt(t,e,i){const r=i.task,o=C(t,r.serviceName),n={...e.invocationEnvironment};return Object.assign(n,{...o===null?{}:{BASE_URL:o},FORCE_COLOR:"0",NODE_OPTIONS:It(i.prepared.resolutionBootstrapFile,i.prepared.inputBootstrapFile),NO_COLOR:"1",TESTKIT_ACTIVE:"1",[ot]:i.prepared.runtimeInput.file,[nt]: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)}:{}}),rt(n),n}function Et(t,e,i,r){const o=C(t,i.serviceName),n=st(r.environment);return{context:{baseUrl:o,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:n,fixtures:r.fixtures,invocationId:t.invocationId,profileId:e,...r.setupData===void 0?{}:{setupData:r.setupData},taskId:i.id,version:1}}function It(t,e){return[`--require=${JSON.stringify(t)}`,`--import=${JSON.stringify(X(e).href)}`].join(" ")}function C(t,e){return Object.hasOwn(t.target.endpoints,e)?t.target.endpoints[e]:null}async function Tt(t,e){return{testkitConfig:await b(t,e.supportModules,"testkitConfig"),tsconfig:await b(t,e.supportModules,"tsconfig")}}async function b(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 vt(t,e){return{...dt({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 _t(t){return{...f(t,"NODE_VITEST_PREPARE_FAILED"),classification:t instanceof L?"product":"infra"}}async function N(t){try{return await t()}catch(e){throw new L(e)}}
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/node-vitest.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:()=>`
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 { type NodeVitestAuthoringEntrypoints, type NodeVitestInstallation } from "../../installations/node-vitest.js";
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/node-vitest.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
+ 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 y}from"../../installations/node-vitest.js";import{resolveNodeVitestManagedRuntimeResolution as R}from"./resolution.js";import{nativeReporterFile as N}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)),m=i.join(l,"adapter/consumer-typescript-paths.js"),f=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=R(e.entrypoints,{consumerRoots:[e.rootDirectory],trustedImporterFiles:[e.inputBootstrapFile,e.setupFile,e.vitestConfigFile],trustedInternalEntrypoints:[m,d]});await Promise.all([o.writeFile(e.setupFile,I(e.configFile,e.runtimeInput.expectedProfileId),"utf8"),o.writeFile(e.inputBootstrapFile,S(e.runtimeInput),"utf8"),o.writeFile(e.resolutionBootstrapFile,t.bootstrapSource,{encoding:"utf8",flag:"wx",mode:384}),o.writeFile(e.vitestConfigFile,O(e,t),"utf8")])}function S(e){const t=a(d).href,n=a(m).href;return`
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
- const configRuntime = Object.freeze({
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 configRuntime = globalThis[
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(y.map(({packageName:r})=>r))});
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(f)},
157
- server: { deps: { external: ${JSON.stringify([f,e.entrypoints.data].map(r=>r.replaceAll(i.sep,"/")))}.map(exactSpecifierPattern) } },`:""}
158
- reporters: [[${JSON.stringify(N("node-vitest"))}, ${JSON.stringify(e.report)}]],
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 T=new Set(["!","(",")","*","+","?","@","[","\\","]","{","}"]);export function toVitestIncludePattern(e){return Array.from(e.replaceAll(i.sep,"/"),t=>T.has(t)?`\\${t}`:t).join("")}
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:fs/promises";import w from"node:path";import{readVerifiedOwnedFile as f,writeOwnedRuntimeInput as S,RUNTIME_INPUT_BYTE_MEASURE as O}from"../../runtime-input-file.js";import{requireBoundedJsonValue as h,RUNTIME_INPUT_JSON_BOUNDS as V}from"../../contracts/json-bounds.js";import{ENGINE_PROCESS_RESERVED_ENVIRONMENT_KEYS as l}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 E={filename:NODE_VITEST_RUNTIME_INPUT_FILENAME,label:"Node/Vitest runtime input",maxSizeBytes:8*1024*1024},N=512,p=4*1024*1024,x=new Set(["BASE_URL","FORCE_COLOR","NODE_OPTIONS","NO_COLOR","UPDATE_SNAPSHOT",...l].map(t=>t.toUpperCase()));export const MAX_NODE_VITEST_SPAWN_ENVIRONMENT_UNITS=24*1024;export async function writeOwnedNodeVitestRuntimeInput(t,e){return S(t,e,E)}export async function verifyOwnedNodeVitestRuntimeInput(t){await f(t,E)}export function selectNodeVitestForwardedEnvironment(t){const e={...t};return _(e),e}export function assertNodeVitestSpawnEnvironmentBudget(t){if(Object.entries(t).reduce((n,[o,r])=>n+o.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(t){const e=process.env[NODE_VITEST_RUNTIME_INPUT_FILE_ENV],n=process.env[NODE_VITEST_RUNTIME_INPUT_SHA256_ENV];if(e!==t.expectedFile)throw new Error("Node/Vitest runtime input pointer does not match its engine-owned path");if(n!==t.expectedSha256)throw new Error("Node/Vitest runtime input digest does not match its prepared value");const o=await m.lstat(t.expectedFile),r={directory:w.dirname(t.expectedFile),file:t.expectedFile,sha256:t.expectedSha256,sizeBytes:o.size},c=await f(r,E);let i;try{i=JSON.parse(c)}catch(u){throw new Error("Node/Vitest runtime input contains invalid JSON",{cause:u})}const s=a(I(i));if(s.invocationId!==t.expectedInvocationId||s.profileId!==t.expectedProfileId||s.taskId!==t.expectedTaskId)throw new Error("Node/Vitest runtime input identity does not match its task");R({environment:s.environment,label:"Node/Vitest"}),Object.defineProperty(globalThis,Symbol.for(NODE_VITEST_RUNTIME_INPUT_SYMBOL),{configurable:!0,enumerable:!1,value:s,writable:!1}),applyNodeVitestEngineEnvironment()}export function applyNodeVitestEngineEnvironment(){const t=readNodeVitestRuntimeInput();if(t===null)throw new Error("Node/Vitest runtime input is not installed");const e=t.context.baseUrl;typeof e=="string"?process.env.BASE_URL=e:Reflect.deleteProperty(process.env,"BASE_URL")}export function readNodeVitestRuntimeInput(){const t=Reflect.get(globalThis,Symbol.for(NODE_VITEST_RUNTIME_INPUT_SYMBOL));return t===void 0?null:a(I(t,"Installed Node/Vitest runtime input"))}function I(t,e="Node/Vitest runtime input"){if(h(t,e,V,O),!d(t))throw new Error(`${e} must contain an object`);const n=new Set(["context","engineId","environment","fixtures","invocationId","profileId","setupData","taskId","version"]);for(const r of Object.keys(t))if(!n.has(r))throw new Error(`${e} contains unknown field ${r}`);if(t.version!==NODE_VITEST_RUNTIME_INPUT_VERSION)throw new Error(`${e}.version must be 1`);if(t.engineId!=="node-vitest")throw new Error(`${e}.engineId must be "node-vitest"`);if(!A(t.profileId))throw new Error(`${e}.profileId is invalid`);if(!d(t.context)||!d(t.fixtures))throw new Error(`${e} context and fixtures must be objects`);const o=y(t.environment,`${e}.environment`);return _(o),{context:t.context,engineId:"node-vitest",environment:o,fixtures:t.fixtures,invocationId:T(t.invocationId,`${e}.invocationId`),profileId:t.profileId,...Object.hasOwn(t,"setupData")?{setupData:t.setupData}:{},taskId:T(t.taskId,`${e}.taskId`),version:NODE_VITEST_RUNTIME_INPUT_VERSION}}function _(t){const e=Object.entries(t);if(e.length>N)throw new Error(`Node/Vitest forwarded environment exceeds the ${N.toString()} entry limit`);let n=0;const o=new Set;for(const[r,c]of e){D(r,c,"Node/Vitest forwarded environment");const i=r.toUpperCase();if(o.has(i))throw new Error(`Node/Vitest forwarded environment contains case-insensitive duplicate key ${r}`);if(o.add(i),i.startsWith("TESTKIT_")||i.startsWith("VITEST_")||x.has(i))throw new Error(`Node/Vitest forwarded environment contains reserved key ${r}`);n+=Buffer.byteLength(r)+Buffer.byteLength(c)+2}if(n>p)throw new Error(`Node/Vitest forwarded environment exceeds the ${p.toString()} byte limit`)}function y(t,e){if(!d(t))throw new Error(`${e} must be an object`);const n=Object.entries(t);if(!n.every(o=>typeof o[1]=="string"))throw new Error(`${e} values must all be strings`);return Object.fromEntries(n)}function D(t,e,n){if(t.length===0||t.includes("=")||t.includes("\0"))throw new Error(`${n} contains an invalid variable name`);if(e.includes("\0"))throw new Error(`${n}.${t} contains a NUL character`)}function a(t){if(t!==null&&typeof t=="object"&&!Object.isFrozen(t)){Object.freeze(t);for(const e of Object.values(t))a(e)}return t}function T(t,e){if(typeof t!="string"||t.length===0)throw new Error(`${e} must be a non-empty string`);return t}function A(t){return t==="node-vitest-contract"||t==="node-vitest-data"}function d(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}
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), fsynced, and atomically renamed into place; reads
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 n=c(e,i.label,h,RUNTIME_INPUT_BYTE_MEASURE),s=await m(t,i.label),w=Buffer.byteLength(n);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 r=null;try{r=await a.open(l,"wx",384),await r.writeFile(n,"utf8"),await r.sync(),await r.close(),r=null,await a.rename(l,f),await a.chmod(f,384)}catch(d){throw await r?.close().catch(()=>{}),await a.rm(l,{force:!0}).catch(()=>{}),d}return{directory:s,file:f,sha256:u.createHash("sha256").update(n,"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 n=await a.lstat(t.file,{bigint:!0});if(!n.isFile()||n.isSymbolicLink())throw new Error(`${e.label} must be an owned regular file`);if(n.size!==BigInt(t.sizeBytes)||n.size>BigInt(e.maxSizeBytes))throw new Error(`${e.label} size changed after preparation`);if(process.platform!=="win32"&&(n.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,n),l=Buffer.from(t.sha256),r=Buffer.from(f);if(l.length!==r.length||!u.timingSafeEqual(r,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
+ 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 x from"crypto";import{isTerminalRunStatus as U}from"../process/runs/schema.js";import{observeProcessCreationIdentity as M}from"../process/index.js";import{normalizeOperationReason as _}from"../domain/operation-reason.js";import{finalizeLiveRunArtifact as B}from"../evidence/index.js";import{captureProcessCreationIdentity as Y}from"../process/service-processes.js";import{requireProductLifecycleFenceProductDir as J,withProductLifecycleFence as K}from"../process/lifecycle-fence.js";import{createRunManifest as Q,listRunManifests as $,readRunMaintenanceStopRequest as X,removeRunManifestFile as G,requireSafeRunId as Z,RUN_MANIFEST_SCHEMA_VERSION as ee,runManifestPath as te,writeRunManifest as ne}from"../process/runs/manifest.js";const re=1e3,ie=String,T=new WeakMap;export function createUnpublishedRunLifecycle(n,r={}){const i=r.runId===void 0?le():Z(r.runId,"Testkit run id"),a=te(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?Y(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:x.randomUUID(),schemaVersion:ee,runId:i,pid:u,processIdentity:typeof b=="string"?b:null,controller:se(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,P=r.keepAlive===!0?setInterval(()=>{},re):null;let R=null,g=null,v=null,q=!1;function j(e){const t=T.get(I);if(!t)throw new Error("Testkit run lifecycle ownership is unavailable");if(!t.published){Q(n,e),t.published=!0;return}ne(n,e)}function A(){const e=X(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:M(e.coordinatorPid,e.coordinatorProcessIdentity)==="different"?(D=e,null):e}function h(e){k("mutate run state");const t=oe(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 N(){R&&(clearInterval(R),R=null)}function z(){for(const e of S)clearInterval(e);S.clear()}function F(e="controller process exited before Testkit run finalized"){if(q||U(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())}),B(n,{runId:i,reason:c,status:l,finishedAt:o}),!0}const H=()=>{F()};process.on("exit",H);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,z(),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||U(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(N),s.signal.aborted||s.abort(new Error(`testkit run interrupted (${o})`)),!!l&&c!==null)throw c},trackGraphContext(e){h(t=>{ue(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=ae(e);h(d=>{d.services=d.services.filter(C=>C.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(C=>{const E=C.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||M(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 K(n,e=>cleanupOwnedRunLifecycleWithinLifecycleFence(e,I))},finalizeForControllerExit:F,dispose(){q=!0;for(const e of y)e.unsubscribe();process.off("exit",H),I.removeSignalHandlers(),P&&clearInterval(P),N(),z()}};return T.set(I,{incarnationId:f.incarnationId,pid:u,processIdentity:f.processIdentity,productDir:n,published:!1,runId:i}),I}function oe(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 se(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:ie(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=J(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(L(u.host)===L(r)&&u.port===Number(i))return{manifest:s,service:m,active:isRunManifestActive(s)};return null}export function isRunManifestActive(n){return n.pid>0&&M(n.pid,n.processIdentity)!=="different"}function ae(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=L(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 ue(n,r){r&&(n.includes(r)||n.push(r))}function le(){return`${Date.now().toString()}-${process.pid.toString()}-${x.randomBytes(4).toString("hex")}`}function L(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)}
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)}
@@ -1 +1 @@
1
- import{formatRunSummary as w}from"../process/runs/manifest.js";import{cleanupOrphanedLocalInfrastructure as R,cleanupTerminalRunLocalInfrastructure as m}from"../database/index.js";import{resetResultArtifacts as v}from"../evidence/index.js";import{requireProductLifecycleFenceProductDir as d,withProductLifecycleFence as l}from"../process/lifecycle-fence.js";import{listRunManifests as u,removeRunManifestFile as y,requireSafeRunId as I}from"../process/runs/manifest.js";import{stopRunManifest as A}from"./run-stopping.js";import{createUnpublishedRunLifecycle as L,disposeFailedRunLifecycle as k,isRunManifestActive as a,cleanupOwnedRunLifecycleWithinLifecycleFence as S,readOwnedRunLifecycleWithinLifecycleFence as p}from"./lifecycle-controller.js";import{reconcileInactiveRunArtifact as g,reconcileOrphanedLiveRunArtifact as x}from"./lifecycle-artifacts.js";class f extends Error{constructor(n){super(`Stale Testkit run ${n} still owns application services and requires phased cleanup`),this.name="StaleRunServiceCleanupRequiredError"}}export async function startRunLifecycle(e,n={}){for(;;){await cleanupStaleRuns(e);try{return await l(e,t=>startRunLifecycleWithinLifecycleFence(t,n))}catch(t){if(!(t instanceof f))throw t}}}export async function startRunLifecycleWithinLifecycleFence(e,n={}){const t=d(e);h(t),await cleanupStaleRunsWithinLifecycleFence(e);const c=u(t).find(i=>!a(i)&&i.services.length>0);if(c!==void 0)throw new f(c.runId);h(t);const r=L(t,n);try{return r.installSignalHandlers(),n.resetResultsBeforePublish===!0&&v(t),r.markRunning(),r}catch(i){throw k(r),i}}function h(e){const n=u(e).find(a);if(n)throw new Error(`Active Testkit run ${w(n)} already owns this product`)}export async function cleanupRuns(e,{includeActive:n=!1}={}){const t=[],c=[];for(;;){const i=await l(e,()=>u(e).flatMap(s=>{const o=a(s);return n&&o||!o&&s.services.length>0?[{active:o,manifest:s}]:[]}));if(i.length===0)break;for(const s of i)await A(e,s.manifest,{reason:s.active?"cleanup":"stale run process not active"}),s.active?t.push(s.manifest):c.push(s.manifest)}const r=await l(e,i=>cleanupRunsWithinLifecycleFence(i));return{...r,cleaned:[...t,...c,...r.cleaned],stoppedActive:t}}export async function cleanupRunsWithinLifecycleFence(e,{protectedLiveRunIds:n=new Set}={}){const t=d(e),c=u(t),r={cleaned:[],skippedActive:[],stoppedActive:[]};for(const i of c){if(a(i)||i.services.length>0){r.skippedActive.push(i);continue}g(t,i),y(t,O(i)),r.cleaned.push(i)}return x(t,{activeRunIds:new Set([...r.skippedActive.map(i=>i.runId).filter(i=>typeof i=="string"),...n])}),r.resourceCleanup=await R(t),{cleaned:r.cleaned,resourceCleanup:r.resourceCleanup,skippedActive:r.skippedActive,stoppedActive:r.stoppedActive}}export async function cleanupStaleRuns(e){const n=[];for(;;){const t=await cleanupRuns(e);if(n.push(...t.cleaned),!t.skippedActive.some(r=>!a(r)&&r.services.length>0))return n}}export async function cleanupStaleRunsWithinLifecycleFence(e){return(await cleanupRunsWithinLifecycleFence(e)).cleaned}export async function cleanupOwnedRunLifecycleResourcesWithinLifecycleFence(e,n){const t=p(e,n);if(t.manifest===null)throw new Error(`Cannot clean terminal Testkit run ${n.runId} resources without its owned manifest`);if(t.manifest.services.length>0)throw new Error(`Cannot clean terminal Testkit run ${n.runId} resources while service ownership remains published`);const c=C(t.manifest);await m(e,c);const r=p(e,n);if(r.manifest===null||!F(r.manifest,c))throw new Error(`Cannot remove terminal Testkit run ${n.runId} because its manifest changed during resource cleanup`);return S(e,n)}function C(e){if(e.status!=="failed"&&e.status!=="finished"&&e.status!=="interrupted")throw new Error(`Cannot clean Testkit run ${e.runId} database resources before terminal finalization`);if(typeof e.finishedAt!="string"||e.finishedAt.length===0)throw new Error(`Cannot clean terminal Testkit run ${e.runId} database resources without a completion time`);return{finishedAt:e.finishedAt,incarnationId:e.incarnationId,pid:e.pid,processIdentity:e.processIdentity,runId:e.runId,status:e.status}}function F(e,n){return e.runId===n.runId&&e.incarnationId===n.incarnationId&&e.pid===n.pid&&e.processIdentity===n.processIdentity&&e.finishedAt===n.finishedAt&&e.status===n.status&&e.services.length===0}function O(e){return I(e.runId,"Testkit run manifest runId")}export{listRunManifests,RunManifestInventoryError}from"../process/runs/manifest.js";export{stopRunById,stopRunManifest}from"./run-stopping.js";export{cleanupOwnedRunLifecycleWithinLifecycleFence,findPortOwner,isRunManifestActive}from"./lifecycle-controller.js";export{isPidRunning}from"../process/runs/stop.js";export{formatRunSummary}from"../process/runs/manifest.js";
1
+ import{formatRunSummary as w}from"../process/runs/manifest.js";import{cleanupOrphanedLocalInfrastructure as R,cleanupTerminalRunLocalInfrastructure as m}from"../database/lifecycle-cleanup.js";import{resetResultArtifacts as v}from"../evidence/index.js";import{requireProductLifecycleFenceProductDir as d,withProductLifecycleFence as l}from"../process/lifecycle-fence.js";import{listRunManifests as u,removeRunManifestFile as y,requireSafeRunId as I}from"../process/runs/manifest.js";import{stopRunManifest as A}from"./run-stopping.js";import{createUnpublishedRunLifecycle as L,disposeFailedRunLifecycle as k,isRunManifestActive as a,cleanupOwnedRunLifecycleWithinLifecycleFence as S,readOwnedRunLifecycleWithinLifecycleFence as p}from"./lifecycle-controller.js";import{reconcileInactiveRunArtifact as g,reconcileOrphanedLiveRunArtifact as x}from"./lifecycle-artifacts.js";class f extends Error{constructor(n){super(`Stale Testkit run ${n} still owns application services and requires phased cleanup`),this.name="StaleRunServiceCleanupRequiredError"}}export async function startRunLifecycle(e,n={}){for(;;){await cleanupStaleRuns(e);try{return await l(e,t=>startRunLifecycleWithinLifecycleFence(t,n))}catch(t){if(!(t instanceof f))throw t}}}export async function startRunLifecycleWithinLifecycleFence(e,n={}){const t=d(e);h(t),await cleanupStaleRunsWithinLifecycleFence(e);const c=u(t).find(i=>!a(i)&&i.services.length>0);if(c!==void 0)throw new f(c.runId);h(t);const r=L(t,n);try{return r.installSignalHandlers(),n.resetResultsBeforePublish===!0&&v(t),r.markRunning(),r}catch(i){throw k(r),i}}function h(e){const n=u(e).find(a);if(n)throw new Error(`Active Testkit run ${w(n)} already owns this product`)}export async function cleanupRuns(e,{includeActive:n=!1}={}){const t=[],c=[];for(;;){const i=await l(e,()=>u(e).flatMap(s=>{const o=a(s);return n&&o||!o&&s.services.length>0?[{active:o,manifest:s}]:[]}));if(i.length===0)break;for(const s of i)await A(e,s.manifest,{reason:s.active?"cleanup":"stale run process not active"}),s.active?t.push(s.manifest):c.push(s.manifest)}const r=await l(e,i=>cleanupRunsWithinLifecycleFence(i));return{...r,cleaned:[...t,...c,...r.cleaned],stoppedActive:t}}export async function cleanupRunsWithinLifecycleFence(e,{protectedLiveRunIds:n=new Set}={}){const t=d(e),c=u(t),r={cleaned:[],skippedActive:[],stoppedActive:[]};for(const i of c){if(a(i)||i.services.length>0){r.skippedActive.push(i);continue}g(t,i),y(t,O(i)),r.cleaned.push(i)}return x(t,{activeRunIds:new Set([...r.skippedActive.map(i=>i.runId).filter(i=>typeof i=="string"),...n])}),r.resourceCleanup=await R(t),{cleaned:r.cleaned,resourceCleanup:r.resourceCleanup,skippedActive:r.skippedActive,stoppedActive:r.stoppedActive}}export async function cleanupStaleRuns(e){const n=[];for(;;){const t=await cleanupRuns(e);if(n.push(...t.cleaned),!t.skippedActive.some(r=>!a(r)&&r.services.length>0))return n}}export async function cleanupStaleRunsWithinLifecycleFence(e){return(await cleanupRunsWithinLifecycleFence(e)).cleaned}export async function cleanupOwnedRunLifecycleResourcesWithinLifecycleFence(e,n){const t=p(e,n);if(t.manifest===null)throw new Error(`Cannot clean terminal Testkit run ${n.runId} resources without its owned manifest`);if(t.manifest.services.length>0)throw new Error(`Cannot clean terminal Testkit run ${n.runId} resources while service ownership remains published`);const c=C(t.manifest);await m(e,c);const r=p(e,n);if(r.manifest===null||!F(r.manifest,c))throw new Error(`Cannot remove terminal Testkit run ${n.runId} because its manifest changed during resource cleanup`);return S(e,n)}function C(e){if(e.status!=="failed"&&e.status!=="finished"&&e.status!=="interrupted")throw new Error(`Cannot clean Testkit run ${e.runId} database resources before terminal finalization`);if(typeof e.finishedAt!="string"||e.finishedAt.length===0)throw new Error(`Cannot clean terminal Testkit run ${e.runId} database resources without a completion time`);return{finishedAt:e.finishedAt,incarnationId:e.incarnationId,pid:e.pid,processIdentity:e.processIdentity,runId:e.runId,status:e.status}}function F(e,n){return e.runId===n.runId&&e.incarnationId===n.incarnationId&&e.pid===n.pid&&e.processIdentity===n.processIdentity&&e.finishedAt===n.finishedAt&&e.status===n.status&&e.services.length===0}function O(e){return I(e.runId,"Testkit run manifest runId")}export{listRunManifests,RunManifestInventoryError}from"../process/runs/manifest.js";export{stopRunById,stopRunManifest}from"./run-stopping.js";export{cleanupOwnedRunLifecycleWithinLifecycleFence,findPortOwner,isRunManifestActive}from"./lifecycle-controller.js";export{isPidRunning}from"../process/runs/stop.js";export{formatRunSummary}from"../process/runs/manifest.js";
@@ -55,7 +55,7 @@ export interface CacheMaintenance<Result> {
55
55
  }
56
56
  export interface EnvironmentMaintenance {
57
57
  inspect(desiredGraphs: readonly DesiredRuntimeGraph[], serviceName?: string | null): EnvironmentInventory;
58
- inspectRuns(): Array<RunManifest & {
58
+ inspectRuns(runId?: string): Array<RunManifest & {
59
59
  active: boolean;
60
60
  }>;
61
61
  stop(selection: RunSelection, reason: string): Promise<StoppedRun[]>;
@@ -1 +1 @@
1
- import o from"fs";import p from"path";import{cleanupOwnedLocalDatabaseResources as G,cleanupOrphanedLocalInfrastructure as L,destroyOwnedLocalDatabaseResources as x,destroyRuntimeDatabase as C,destroyServiceDatabaseCache as I,isDatabaseStateDir as k}from"../database/index.js";import{cleanupRuns as A,cleanupRunsWithinLifecycleFence as g,isRunManifestActive as m,listRunManifests as d,stopRunManifest as F}from"./lifecycle.js";import{findGraphDirsForService as N,findRuntimeStateDirs as E}from"./state.js";import{collectRuntimeGraphStatus as M,collectRunStatus as T}from"./status-runtime.js";import{withProductLifecycleFence as w}from"../process/lifecycle-fence.js";import{pruneEmptyDir as O}from"../process/files/directory-status.js";export function createEnvironmentMaintenance(e){async function n(t,a){if(t.kind==="destroy")return $(e,t.configs,t);if(a===void 0)throw new Error("Cleanup requires a cache maintenance plan");if(t.dryRun)return P(e,t,a);const s=await A(e,{includeActive:!0});return w(e,i=>P(e,t,a,i,s))}return{inspect(t,a=null){return{runtimeGraphs:M(e,{desiredGraphs:t,serviceName:a}),runs:T(e)}},inspectRuns:()=>d(e).map(t=>({...t,active:m(t)})),async stop(t,a){const s=await w(e,()=>{const r=d(e);return t.kind==="run"?r.filter(u=>u.runId===t.runId):t.kind==="all"?r:r.filter(m).slice(-1)}),i=[];for(const r of s)i.push(await F(e,r,{reason:a}));return i},maintain:n}}async function $(e,n,t={}){j(e,n),await A(e,{includeActive:!0}),await w(e,async a=>{const s=await g(a);b(s,"destroy");const i=new Set(n.flatMap(r=>[r.stateDir,...N(e,r.name)]));for(const r of i){if(!o.existsSync(r))continue;const u=E(r,k);for(const l of u)await C({productDir:e,stateDir:l});o.rmSync(r,{recursive:!0,force:!0})}for(const r of n)await I(e,r.name);t.destroyOwnedResources&&await x(a),await L(e)})}async function P(e,n,t,a=null,s=null){const{dryRun:i}=n,r=n.serviceName||null,u=n.resources;let l,h=[];if(i)l=U(e);else{if(a===null)throw new Error("Destructive cleanup requires the product lifecycle fence");const c=await g(a);b(c,"cleanup"),l={...c,cleaned:[...s?.cleaned??[],...c.cleaned],stoppedActive:[...s?.stoppedActive??[]]},h=[...s===null?[]:[s.resourceCleanup],c.resourceCleanup]}const y=M(e,{desiredGraphs:n.desiredGraphs,serviceName:r}).flatMap(c=>c.staleRuntimeDirs.filter(f=>!f.active).map(f=>({graph:c.name,runtimeId:f.id,path:f.path}))),v=t(),R=[];let S={targets:[],removed:[],kept:[],skipped:[],stopped:[]};if(!i){for(const c of y)await B(e,c.path),R.push(c);await v.prune(),O(p.join(e,".testkit","_graphs"))}return u&&(S=await G(e,{dryRun:i})),{environment:{...l,dryRun:i,targets:y,runtimeCleaned:R,resourceCleanup:S,automaticResourceCleanup:h},otherCaches:v.result}}function b(e,n){if(e.skippedActive.length!==0)throw new Error(`A Testkit run started while ${n} was acquiring exclusive product ownership; retry after the run finishes`)}function j(e,n){const t=o.realpathSync.native(p.resolve(e));for(const a of n)if(o.realpathSync.native(p.resolve(a.productDir))!==t)throw new Error(`Cannot destroy service ${a.name} from a different Testkit product`)}async function B(e,n){if(!n||!o.existsSync(n))return;const t=E(n,k);for(const a of t)await C({productDir:e,stateDir:a});o.rmSync(n,{recursive:!0,force:!0})}function U(e){const n={cleaned:[],skippedActive:[],stoppedActive:[]};for(const t of d(e))m(t)?n.skippedActive.push(t):n.cleaned.push(t);return n}
1
+ import{inspectRunManifests as G}from"../process/runs/inspection.js";import o from"fs";import m from"path";import{cleanupOwnedLocalDatabaseResources as L,cleanupOrphanedLocalInfrastructure as x,destroyOwnedLocalDatabaseResources as I,destroyRuntimeDatabase as R,destroyServiceDatabaseCache as F,isDatabaseStateDir as S}from"../database/index.js";import{cleanupRuns as C,cleanupRunsWithinLifecycleFence as k,isRunManifestActive as A,listRunManifests as g,stopRunManifest as N}from"./lifecycle.js";import{findGraphDirsForService as T,findRuntimeStateDirs as M}from"./state.js";import{collectRuntimeGraphStatus as E,collectRunStatus as O}from"./status-runtime.js";import{withProductLifecycleFence as p}from"../process/lifecycle-fence.js";import{pruneEmptyDir as $}from"../process/files/directory-status.js";export function createEnvironmentMaintenance(e){async function n(t,a){if(t.kind==="destroy")return j(e,t.configs,t);if(a===void 0)throw new Error("Cleanup requires a cache maintenance plan");if(t.dryRun)return P(e,t,a);const s=await C(e,{includeActive:!0});return p(e,i=>P(e,t,a,i,s))}return{inspect(t,a=null){return{runtimeGraphs:E(e,{desiredGraphs:t,serviceName:a}),runs:O(e)}},inspectRuns:t=>G(e,t),async stop(t,a){const s=await p(e,()=>{const r=g(e);return t.kind==="run"?r.filter(u=>u.runId===t.runId):t.kind==="all"?r:r.filter(A).slice(-1)}),i=[];for(const r of s)i.push(await N(e,r,{reason:a}));return i},maintain:n}}async function j(e,n,t={}){B(e,n),await C(e,{includeActive:!0}),await p(e,async a=>{const s=await k(a);b(s,"destroy");const i=new Set(n.flatMap(r=>[r.stateDir,...T(e,r.name)]));for(const r of i){if(!o.existsSync(r))continue;const u=M(r,S);for(const f of u)await R({productDir:e,stateDir:f});o.rmSync(r,{recursive:!0,force:!0})}for(const r of n)await F(e,r.name);t.destroyOwnedResources&&await I(a),await x(e)})}async function P(e,n,t,a=null,s=null){const{dryRun:i}=n,r=n.serviceName||null,u=n.resources;let f,d=[];if(i)f=W(e);else{if(a===null)throw new Error("Destructive cleanup requires the product lifecycle fence");const c=await k(a);b(c,"cleanup"),f={...c,cleaned:[...s?.cleaned??[],...c.cleaned],stoppedActive:[...s?.stoppedActive??[]]},d=[...s===null?[]:[s.resourceCleanup],c.resourceCleanup]}const w=E(e,{desiredGraphs:n.desiredGraphs,serviceName:r}).flatMap(c=>c.staleRuntimeDirs.filter(l=>!l.active).map(l=>({graph:c.name,runtimeId:l.id,path:l.path}))),h=t(),y=[];let v={targets:[],removed:[],kept:[],skipped:[],stopped:[]};if(!i){for(const c of w)await U(e,c.path),y.push(c);await h.prune(),$(m.join(e,".testkit","_graphs"))}return u&&(v=await L(e,{dryRun:i})),{environment:{...f,dryRun:i,targets:w,runtimeCleaned:y,resourceCleanup:v,automaticResourceCleanup:d},otherCaches:h.result}}function b(e,n){if(e.skippedActive.length!==0)throw new Error(`A Testkit run started while ${n} was acquiring exclusive product ownership; retry after the run finishes`)}function B(e,n){const t=o.realpathSync.native(m.resolve(e));for(const a of n)if(o.realpathSync.native(m.resolve(a.productDir))!==t)throw new Error(`Cannot destroy service ${a.name} from a different Testkit product`)}async function U(e,n){if(!n||!o.existsSync(n))return;const t=M(n,S);for(const a of t)await R({productDir:e,stateDir:a});o.rmSync(n,{recursive:!0,force:!0})}function W(e){const n={cleaned:[],skippedActive:[],stoppedActive:[]};for(const t of g(e))A(t)?n.skippedActive.push(t):n.cleaned.push(t);return n}
@@ -1,4 +1,4 @@
1
- import { type DatabaseInfo } from "../database/index.js";
1
+ import { type DatabaseInfo } from "../database/state.js";
2
2
  import type { RuntimeInstanceContext } from "./runtime-contexts.js";
3
3
  import type { ReadyServiceConfig } from "./runtime-service.js";
4
4
  export interface ReadyService {
@@ -1 +1 @@
1
- import{readDatabaseInfo as s}from"../database/index.js";export function createReadyEnvironment(t,n){const a=new Map(t.runtimeConfigs.map(e=>[e.name,s(e.stateDir)]));return{...n,graphKey:t.graphKey,runtimeId:t.runtimeId,targetNames:[...t.targetNames],services:new Map(t.runtimeConfigs.map(e=>{const r={...e.testkit.templateContext};return delete r.databaseBindingsReader,[e.name,{baseUrl:e.testkit.local?.baseUrl||null,database:a.get(e.name)??null,config:{...e,testkit:{...e.testkit,templateContext:{...r,databaseBindingsByService:a}}}}]}))}}
1
+ import{readDatabaseInfo as s}from"../database/state.js";export function createReadyEnvironment(t,n){const a=new Map(t.runtimeConfigs.map(e=>[e.name,s(e.stateDir)]));return{...n,graphKey:t.graphKey,runtimeId:t.runtimeId,targetNames:[...t.targetNames],services:new Map(t.runtimeConfigs.map(e=>{const r={...e.testkit.templateContext};return delete r.databaseBindingsReader,[e.name,{baseUrl:e.testkit.local?.baseUrl||null,database:a.get(e.name)??null,config:{...e,testkit:{...e.testkit,templateContext:{...r,databaseBindingsByService:a}}}}]}))}}
@@ -1,4 +1,4 @@
1
- import { type ProvisionDatabaseOptions } from "../database/index.js";
1
+ import type { ProvisionDatabaseOptions } from "../database/index.js";
2
2
  import type { RuntimeServiceSpec, RuntimeGraph } from "./planning-types.js";
3
3
  import { type RuntimePreparationConfig, type RuntimePreparationOptions } from "./runtime-preparation.js";
4
4
  import { type LocalServiceLifecycle, type StartedLocalService, type StartLocalServicesOptions } from "./services.js";
@@ -1 +1 @@
1
- import p from"fs";import t from"path";import{provisionDatabase as m}from"../database/index.js";import{prepareRuntimeServices as o}from"./runtime-preparation.js";import{writeGraphMetadata as u}from"./state.js";import{startLocalServices as f,stopLocalServices as l}from"./services.js";import{resolveRuntimeInstanceConfigs as d}from"./template.js";export function createRuntimeInstanceContext(r,e,a){const i=t.join(a,".testkit","_graphs",e.dirName),s=t.join(i,"runtimes",r);p.mkdirSync(s,{recursive:!0}),u(i,e);const n=d(e.runtimeConfigs,r,s,{graphDirName:e.dirName,portNamespaceIndex:e.portNamespaceIndex,portNamespaceStride:e.portNamespaceStride});return{graphKey:e.key,targetNames:[...e.targetNames],graphDir:i,runtimeDir:s,runtimeId:r,runtimeConfigs:n,prepared:!1,preparationPromise:null,started:!1,startupPromise:null,startedServices:[]}}export async function ensureRuntimeInstanceReady(r,e,a,i={}){return r.prepared||(r.preparationPromise||(r.preparationPromise=(async()=>{await prepareDatabases(r.runtimeConfigs,{...i,signal:a.signal}),await o(r.runtimeConfigs,{...i,signal:a.signal}),r.prepared=!0})().finally(()=>{r.preparationPromise=null})),await r.preparationPromise),e&&!r.started&&(r.startupPromise||(r.startupPromise=(async()=>{r.startedServices=await f(r.runtimeConfigs,a,i),r.started=!0})().finally(()=>{r.startupPromise=null})),await r.startupPromise),r}async function c(r,e){r?.started&&(await l(r.startedServices,e),r.started=!1,r.startedServices=[])}export async function cleanupRuntimeInstanceContext(r,e){r&&await c(r,e)}export async function prepareDatabases(r,e={}){for(const a of r)await m(a,{progress:{reporter:e.reporter,setupRegistry:e.setupRegistry},signal:e.signal,skipSchemaSourceVerify:e.skipSchemaSourceVerify})}
1
+ import p from"fs";import t from"path";import{prepareRuntimeServices as m}from"./runtime-preparation.js";import{writeGraphMetadata as o}from"./state.js";import{startLocalServices as u,stopLocalServices as f}from"./services.js";import{resolveRuntimeInstanceConfigs as d}from"./template.js";export function createRuntimeInstanceContext(r,e,i){const a=t.join(i,".testkit","_graphs",e.dirName),s=t.join(a,"runtimes",r);p.mkdirSync(s,{recursive:!0}),o(a,e);const n=d(e.runtimeConfigs,r,s,{graphDirName:e.dirName,portNamespaceIndex:e.portNamespaceIndex,portNamespaceStride:e.portNamespaceStride});return{graphKey:e.key,targetNames:[...e.targetNames],graphDir:a,runtimeDir:s,runtimeId:r,runtimeConfigs:n,prepared:!1,preparationPromise:null,started:!1,startupPromise:null,startedServices:[]}}export async function ensureRuntimeInstanceReady(r,e,i,a={}){return r.prepared||(r.preparationPromise||(r.preparationPromise=(async()=>{await prepareDatabases(r.runtimeConfigs,{...a,signal:i.signal}),await m(r.runtimeConfigs,{...a,signal:i.signal}),r.prepared=!0})().finally(()=>{r.preparationPromise=null})),await r.preparationPromise),e&&!r.started&&(r.startupPromise||(r.startupPromise=(async()=>{r.startedServices=await u(r.runtimeConfigs,i,a),r.started=!0})().finally(()=>{r.startupPromise=null})),await r.startupPromise),r}async function l(r,e){r?.started&&(await f(r.startedServices,e),r.started=!1,r.startedServices=[])}export async function cleanupRuntimeInstanceContext(r,e){r&&await l(r,e)}export async function prepareDatabases(r,e={}){if(!r.some(a=>a.testkit.database))return;const{provisionDatabase:i}=await import("../database/index.js");for(const a of r)await i(a,{progress:{reporter:e.reporter,setupRegistry:e.setupRegistry},signal:e.signal,skipSchemaSourceVerify:e.skipSchemaSourceVerify})}
@@ -1 +1 @@
1
- import S from"crypto";import p from"fs";import k from"path";import{resolveServiceCwd as D}from"../config/loader/index.js";import{appendFileToHash as y}from"../repo/fingerprint-policy.js";import{announceResolvedToolchain as b,resolveConfiguredToolchain as v}from"../toolchains/index.js";import{readDatabaseUrl as m}from"../database/index.js";import{buildExecutionEnv as w,collectConfiguredInputs as x}from"../steps/index.js";import{runConfiguredSteps as R}from"../database/configured-steps.js";import"../evidence/setup-progress.js";const I="prepare-manifest.json";export async function prepareRuntimeServices(e,r={}){for(const t of e)await prepareRuntimeService(t,r)}export async function prepareRuntimeService(e,r={}){const t=e.testkit.runtime.prepare;if(!t||t.steps.length===0||!N(e))return;const n=e.testkit.prepareDir,s=await computeRuntimePrepareFingerprint(e,{signal:r.signal}),i=k.join(n,I);if(g(i)?.fingerprint===s){r.setupRegistry?.recordCached({config:e,stage:"runtime:prepare",kind:"runtime-prepare",summary:"runtime prepare cache hit"});return}p.rmSync(n,{recursive:!0,force:!0}),p.mkdirSync(n,{recursive:!0});const o={...w(e,{...e.testkit.local?.env||{},...r.extraEnv||{}},process.env)},d=m(e.stateDir);d&&(o.DATABASE_URL=d);const u=r.setupRegistry?.start({config:e,stage:"runtime:prepare",kind:"runtime-prepare",summary:"runtime prepare",recordLog:!1}),c=r.setupRegistry;try{await b(e,await v(e,{signal:r.signal}),r.reporter),await R({config:e,steps:t.steps,env:o,labelPrefix:"runtime:prepare",reporter:r.reporter,setupRegistry:c||null,parentOperation:u,signal:r.signal});const a=u?c?.finish(u,{status:"passed",summary:"runtime prepare"}):null;a&&r.reporter?.setupOperationFinished?.(a),A(i,{fingerprint:s,preparedAt:new Date().toISOString(),runtimeId:e.runtimeId||null,serviceName:e.name})}catch(a){const f=u?c?.finish(u,{status:"failed",summary:"runtime prepare",error:U(a,"message")||a}):null;throw f&&r.reporter?.setupOperationFinished?.(f),p.rmSync(n,{recursive:!0,force:!0}),a}}function N(e){return e.testkit.runtime.prepare!=null}export async function computeRuntimePrepareFingerprint(e,r={}){const t=await v(e,{signal:r.signal}),n=S.createHash("sha256");n.update(JSON.stringify({prepare:e.testkit.runtime.prepare,toolchain:t?{kind:"node",install:t.install,nodeVersion:t.nodeVersion,npmVersion:t.npmVersion,nodeSource:t.nodeSource,npmSource:t.npmSource,fingerprint:t.fingerprint}:null,serviceEnv:e.testkit.serviceEnv||{},local:e.testkit.local?{baseUrl:e.testkit.local.baseUrl,cwd:e.testkit.local.cwd,env:e.testkit.local.env||{},readyUrl:e.testkit.local.readyUrl,start:e.testkit.local.start}:null})),n.update(JSON.stringify(O(e)));const s=e.testkit.fingerprints||{exclude:[],include:[]};for(const i of e.testkit.envFiles||[])y(n,e.productDir,D(e.productDir,i),s);for(const i of x(e.productDir,e.testkit.runtime.prepare))y(n,e.productDir,i,s);return n.digest("hex")}function O(e){const r=[],t=m(e.stateDir);t&&r.push({service:e.name,url:t});const n=new Set;for(const i of Object.values(e.testkit.serviceEnv||{}))h(i,n,e.name);for(const i of Object.values(e.testkit.local?.env||{}))h(i,n,e.name);const s=e.testkit.templateContext?.stateDirByService;for(const i of[...n].sort()){const l=s?.get(i),o=l?m(l):null;r.push({service:i,url:o||null})}return r}function h(e,r,t){if(typeof e!="string")return;const n=/\{db(?:Url|Host|Port|Name|User|Password|Ssl)(?::([a-zA-Z0-9_-]+))?\}/g;let s=n.exec(e);for(;s;)r.add(s[1]||t),s=n.exec(e)}function g(e){if(!p.existsSync(e))return null;try{const r=JSON.parse(p.readFileSync(e,"utf8"));return F(r)?{fingerprint:r.fingerprint,...typeof r.preparedAt=="string"?{preparedAt:r.preparedAt}:{},...typeof r.runtimeId=="string"||typeof r.runtimeId=="number"||r.runtimeId===null?{runtimeId:r.runtimeId}:{},...typeof r.serviceName=="string"?{serviceName:r.serviceName}:{}}:null}catch{return null}}function A(e,r){p.writeFileSync(e,JSON.stringify(r,null,2))}function F(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function U(e,r){if(!((typeof e!="object"||e===null)&&typeof e!="function"))return Reflect.get(e,r)}
1
+ import S from"crypto";import p from"fs";import k from"path";import{resolveServiceCwd as D}from"../config/loader/index.js";import{appendFileToHash as y}from"../repo/fingerprint-policy.js";import{announceResolvedToolchain as b,resolveConfiguredToolchain as v}from"../toolchains/index.js";import{readDatabaseUrl as m}from"../database/state.js";import{buildExecutionEnv as w,collectConfiguredInputs as x}from"../steps/index.js";import{runConfiguredSteps as R}from"../database/configured-steps.js";import"../evidence/setup-progress.js";const I="prepare-manifest.json";export async function prepareRuntimeServices(e,r={}){for(const t of e)await prepareRuntimeService(t,r)}export async function prepareRuntimeService(e,r={}){const t=e.testkit.runtime.prepare;if(!t||t.steps.length===0||!N(e))return;const n=e.testkit.prepareDir,s=await computeRuntimePrepareFingerprint(e,{signal:r.signal}),i=k.join(n,I);if(g(i)?.fingerprint===s){r.setupRegistry?.recordCached({config:e,stage:"runtime:prepare",kind:"runtime-prepare",summary:"runtime prepare cache hit"});return}p.rmSync(n,{recursive:!0,force:!0}),p.mkdirSync(n,{recursive:!0});const o={...w(e,{...e.testkit.local?.env||{},...r.extraEnv||{}},process.env)},d=m(e.stateDir);d&&(o.DATABASE_URL=d);const u=r.setupRegistry?.start({config:e,stage:"runtime:prepare",kind:"runtime-prepare",summary:"runtime prepare",recordLog:!1}),c=r.setupRegistry;try{await b(e,await v(e,{signal:r.signal}),r.reporter),await R({config:e,steps:t.steps,env:o,labelPrefix:"runtime:prepare",reporter:r.reporter,setupRegistry:c||null,parentOperation:u,signal:r.signal});const a=u?c?.finish(u,{status:"passed",summary:"runtime prepare"}):null;a&&r.reporter?.setupOperationFinished?.(a),A(i,{fingerprint:s,preparedAt:new Date().toISOString(),runtimeId:e.runtimeId||null,serviceName:e.name})}catch(a){const f=u?c?.finish(u,{status:"failed",summary:"runtime prepare",error:U(a,"message")||a}):null;throw f&&r.reporter?.setupOperationFinished?.(f),p.rmSync(n,{recursive:!0,force:!0}),a}}function N(e){return e.testkit.runtime.prepare!=null}export async function computeRuntimePrepareFingerprint(e,r={}){const t=await v(e,{signal:r.signal}),n=S.createHash("sha256");n.update(JSON.stringify({prepare:e.testkit.runtime.prepare,toolchain:t?{kind:"node",install:t.install,nodeVersion:t.nodeVersion,npmVersion:t.npmVersion,nodeSource:t.nodeSource,npmSource:t.npmSource,fingerprint:t.fingerprint}:null,serviceEnv:e.testkit.serviceEnv||{},local:e.testkit.local?{baseUrl:e.testkit.local.baseUrl,cwd:e.testkit.local.cwd,env:e.testkit.local.env||{},readyUrl:e.testkit.local.readyUrl,start:e.testkit.local.start}:null})),n.update(JSON.stringify(O(e)));const s=e.testkit.fingerprints||{exclude:[],include:[]};for(const i of e.testkit.envFiles||[])y(n,e.productDir,D(e.productDir,i),s);for(const i of x(e.productDir,e.testkit.runtime.prepare))y(n,e.productDir,i,s);return n.digest("hex")}function O(e){const r=[],t=m(e.stateDir);t&&r.push({service:e.name,url:t});const n=new Set;for(const i of Object.values(e.testkit.serviceEnv||{}))h(i,n,e.name);for(const i of Object.values(e.testkit.local?.env||{}))h(i,n,e.name);const s=e.testkit.templateContext?.stateDirByService;for(const i of[...n].sort()){const l=s?.get(i),o=l?m(l):null;r.push({service:i,url:o||null})}return r}function h(e,r,t){if(typeof e!="string")return;const n=/\{db(?:Url|Host|Port|Name|User|Password|Ssl)(?::([a-zA-Z0-9_-]+))?\}/g;let s=n.exec(e);for(;s;)r.add(s[1]||t),s=n.exec(e)}function g(e){if(!p.existsSync(e))return null;try{const r=JSON.parse(p.readFileSync(e,"utf8"));return F(r)?{fingerprint:r.fingerprint,...typeof r.preparedAt=="string"?{preparedAt:r.preparedAt}:{},...typeof r.runtimeId=="string"||typeof r.runtimeId=="number"||r.runtimeId===null?{runtimeId:r.runtimeId}:{},...typeof r.serviceName=="string"?{serviceName:r.serviceName}:{}}:null}catch{return null}}function A(e,r){p.writeFileSync(e,JSON.stringify(r,null,2))}function F(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function U(e,r){if(!((typeof e!="object"||e===null)&&typeof e!="function"))return Reflect.get(e,r)}
@@ -1 +1 @@
1
- import{DEFAULT_READY_TIMEOUT_MS as x,waitForReady as M,serviceCompletionError as y}from"../process/readiness.js";import{resolveServiceCwd as A}from"../config/loader/index.js";import{startSupervisedProcess as f,ProcessExitedBeforeReadyError as k,DEFAULT_SHUTDOWN_TIMEOUT_MS as R}from"../process/index.js";import{announceResolvedToolchain as U,applyToolchainEnv as g,resolveConfiguredToolchain as D}from"../toolchains/index.js";import{buildExecutionEnv as O}from"../steps/index.js";import{numericPortFromUrl as P}from"./template.js";import{assertLocalServicePortsAvailable as _}from"./readiness.js";import{captureOutput as T}from"../process/index.js";import{prepareServiceStartInvocation as C}from"../process/service-processes.js";import{readDatabaseUrl as F}from"../database/index.js";export async function startLocalServices(e,r,t={}){const s=[];try{for(const a of e){if(!B(a))continue;const n=await startLocalService(a,r,t);s.push(n)}}catch(a){try{await stopLocalServices(s,r)}catch(n){throw new AggregateError([a,n],"Local service startup failed and its supervised cleanup also failed",{cause:n})}throw a}return s}export async function startLocalService(e,r,t={}){const s=A(e.productDir,e.testkit.local.cwd),a=await D(e,{signal:r.signal});await U(e,a,t.reporter);const n=g(O(e,{...e.testkit.local.env||{},...t.extraEnv||{}},process.env),a),l=e.testkit.local.port||P(e.testkit.local.baseUrl);l&&(n.PORT=String(l));const b=F(e.stateDir);b&&(n.DATABASE_URL=b),await _(e),t.reporter?.localServiceStarting?.(e,e.testkit.local.start);const v=C(e.testkit.local.start,n),d=t.logRegistry?.ensureServiceLogRecord(e),L=t.reporter?.outputMode==="debug"?o=>t.reporter?.writeDebugLine?.(o):null,i=f({args:v.args,windowsVerbatimArguments:v.windowsVerbatimArguments,cwd:s,displayLabel:`Testkit local service ${e.runtimeLabel}:${e.name}`,env:I(n),executable:v.executable,outputMode:"caller-drained",signal:r.signal,shutdownTimeoutMs:e.testkit.local.shutdownTimeoutMs??R,timeoutMs:null}),S=[T(i.stdout,{livePrefix:`[${e.runtimeLabel}:${e.name}]`,liveWriter:L,onLine(o){d&&t.logRegistry?.append(d,"stdout",o)}}),T(i.stderr,{livePrefix:`[${e.runtimeLabel}:${e.name}]`,liveWriter:L,onLine(o){d&&t.logRegistry?.append(d,"stderr",o)}})];let h,p=!1,w=!1;try{h=await i.ready,p=!0,r.registerService(e,i,h,s);const o=e.testkit.local.readyTimeoutMs||x;e.testkit.local.readyUrl!==!1&&e.testkit.local.readyUrl!=null&&await M({name:`${e.runtimeLabel}:${e.name}`,url:e.testkit.local.readyUrl,timeoutMs:o,process:i,signal:r.signal})}catch(o){const u=o instanceof k?y(`${e.runtimeLabel}:${e.name}`,o):o;w=!0;const c=[];try{c.push(...await $(i,S,!1))}catch(E){c.push(m(E))}if(p)try{r.unregisterService(i.pid)}catch(E){c.push(m(E))}throw c.length>0?new AggregateError([u,...c],`Local service ${e.runtimeLabel}:${e.name} startup and cleanup failed`,{cause:o}):u}return{completionMonitor:i.completion.then(o=>{if(w)return null;if(r.signal.aborted){if(p){p=!1;try{r.unregisterService(i.pid)}catch{}}return null}let u=y(`${e.runtimeLabel}:${e.name}`,o,"unexpectedly");if(p){p=!1;try{r.unregisterService(i.pid)}catch(c){u=new AggregateError([u,c],`Local service ${e.runtimeLabel}:${e.name} exited and its manifest entry could not be removed`)}}try{r.requestStop(u.message)}catch(c){u=new AggregateError([u,c],`Local service ${e.runtimeLabel}:${e.name} exited and the active run could not be interrupted`)}return u}),expectCompletion(){w=!0},name:e.name,outputDrains:S,ownership:h,supervisor:i}}export async function stopLocalServices(e,r){const t=[];for(const s of[...e].reverse()){s.expectCompletion();try{t.push(...await $(s.supervisor,s.outputDrains)),await s.completionMonitor}catch(a){t.push(m(a))}finally{try{r?.unregisterService(s.ownership.pid)}catch(a){t.push(m(a))}}}if(t.length>0)throw new AggregateError(t,"Could not stop all local services")}async function $(e,r,t=!0){const a=await e.stop().then(l=>({error:null,result:l}),l=>({error:m(l),result:null})),n=await Promise.allSettled(r);return[...a.result?j(a.result,t):[a.error],...n.flatMap(l=>l.status==="rejected"?[m(l.reason)]:[])]}function j(e,r=!0){return[...r?[e.error]:[],e.terminationError].filter(t=>t!==null)}function I(e){return Object.fromEntries(Object.entries(e).filter(r=>r[1]!==void 0))}function m(e){return e instanceof Error?e:new Error(String(e))}function B(e){return e.testkit.local!=null}
1
+ import{DEFAULT_READY_TIMEOUT_MS as x,waitForReady as M,serviceCompletionError as y}from"../process/readiness.js";import{resolveServiceCwd as A}from"../config/loader/index.js";import{startSupervisedProcess as f,ProcessExitedBeforeReadyError as k,DEFAULT_SHUTDOWN_TIMEOUT_MS as R}from"../process/index.js";import{announceResolvedToolchain as U,applyToolchainEnv as g,resolveConfiguredToolchain as D}from"../toolchains/index.js";import{buildExecutionEnv as O}from"../steps/index.js";import{numericPortFromUrl as P}from"./template.js";import{assertLocalServicePortsAvailable as _}from"./readiness.js";import{captureOutput as T}from"../process/index.js";import{prepareServiceStartInvocation as C}from"../process/service-processes.js";import{readDatabaseUrl as F}from"../database/state.js";export async function startLocalServices(e,r,t={}){const s=[];try{for(const a of e){if(!B(a))continue;const n=await startLocalService(a,r,t);s.push(n)}}catch(a){try{await stopLocalServices(s,r)}catch(n){throw new AggregateError([a,n],"Local service startup failed and its supervised cleanup also failed",{cause:n})}throw a}return s}export async function startLocalService(e,r,t={}){const s=A(e.productDir,e.testkit.local.cwd),a=await D(e,{signal:r.signal});await U(e,a,t.reporter);const n=g(O(e,{...e.testkit.local.env||{},...t.extraEnv||{}},process.env),a),l=e.testkit.local.port||P(e.testkit.local.baseUrl);l&&(n.PORT=String(l));const b=F(e.stateDir);b&&(n.DATABASE_URL=b),await _(e),t.reporter?.localServiceStarting?.(e,e.testkit.local.start);const v=C(e.testkit.local.start,n),d=t.logRegistry?.ensureServiceLogRecord(e),L=t.reporter?.outputMode==="debug"?o=>t.reporter?.writeDebugLine?.(o):null,i=f({args:v.args,windowsVerbatimArguments:v.windowsVerbatimArguments,cwd:s,displayLabel:`Testkit local service ${e.runtimeLabel}:${e.name}`,env:I(n),executable:v.executable,outputMode:"caller-drained",signal:r.signal,shutdownTimeoutMs:e.testkit.local.shutdownTimeoutMs??R,timeoutMs:null}),S=[T(i.stdout,{livePrefix:`[${e.runtimeLabel}:${e.name}]`,liveWriter:L,onLine(o){d&&t.logRegistry?.append(d,"stdout",o)}}),T(i.stderr,{livePrefix:`[${e.runtimeLabel}:${e.name}]`,liveWriter:L,onLine(o){d&&t.logRegistry?.append(d,"stderr",o)}})];let h,p=!1,w=!1;try{h=await i.ready,p=!0,r.registerService(e,i,h,s);const o=e.testkit.local.readyTimeoutMs||x;e.testkit.local.readyUrl!==!1&&e.testkit.local.readyUrl!=null&&await M({name:`${e.runtimeLabel}:${e.name}`,url:e.testkit.local.readyUrl,timeoutMs:o,process:i,signal:r.signal})}catch(o){const u=o instanceof k?y(`${e.runtimeLabel}:${e.name}`,o):o;w=!0;const c=[];try{c.push(...await $(i,S,!1))}catch(E){c.push(m(E))}if(p)try{r.unregisterService(i.pid)}catch(E){c.push(m(E))}throw c.length>0?new AggregateError([u,...c],`Local service ${e.runtimeLabel}:${e.name} startup and cleanup failed`,{cause:o}):u}return{completionMonitor:i.completion.then(o=>{if(w)return null;if(r.signal.aborted){if(p){p=!1;try{r.unregisterService(i.pid)}catch{}}return null}let u=y(`${e.runtimeLabel}:${e.name}`,o,"unexpectedly");if(p){p=!1;try{r.unregisterService(i.pid)}catch(c){u=new AggregateError([u,c],`Local service ${e.runtimeLabel}:${e.name} exited and its manifest entry could not be removed`)}}try{r.requestStop(u.message)}catch(c){u=new AggregateError([u,c],`Local service ${e.runtimeLabel}:${e.name} exited and the active run could not be interrupted`)}return u}),expectCompletion(){w=!0},name:e.name,outputDrains:S,ownership:h,supervisor:i}}export async function stopLocalServices(e,r){const t=[];for(const s of[...e].reverse()){s.expectCompletion();try{t.push(...await $(s.supervisor,s.outputDrains)),await s.completionMonitor}catch(a){t.push(m(a))}finally{try{r?.unregisterService(s.ownership.pid)}catch(a){t.push(m(a))}}}if(t.length>0)throw new AggregateError(t,"Could not stop all local services")}async function $(e,r,t=!0){const a=await e.stop().then(l=>({error:null,result:l}),l=>({error:m(l),result:null})),n=await Promise.allSettled(r);return[...a.result?j(a.result,t):[a.error],...n.flatMap(l=>l.status==="rejected"?[m(l.reason)]:[])]}function j(e,r=!0){return[...r?[e.error]:[],e.terminationError].filter(t=>t!==null)}function I(e){return Object.fromEntries(Object.entries(e).filter(r=>r[1]!==void 0))}function m(e){return e instanceof Error?e:new Error(String(e))}function B(e){return e.testkit.local!=null}
@@ -1,7 +1,7 @@
1
1
  import type { RuntimeSocket } from "../process/runtime-socket.js";
2
2
  import { type NormalizedDatabaseConfig, type NormalizedRuntimeConfig } from "../config/loader/index.js";
3
3
  import { type ExecutionEnvironmentConfig, type ExecutionEnvironmentLocalConfig, type ExecutionEnvironmentTestkitConfig, type ExecutionTemplateContextValues, type RuntimeUrlMapping } from "../steps/index.js";
4
- import { readDatabaseInfo } from "../database/index.js";
4
+ import { readDatabaseInfo } from "../database/state.js";
5
5
  type RuntimeIdentifier = number | string;
6
6
  type StringMap = ReadonlyMap<string, string>;
7
7
  interface RuntimeLocalConfig extends ExecutionEnvironmentLocalConfig {
@@ -1 +1 @@
1
- import{finalizeRuntimeDatabaseConfig as R}from"../database/runtime-config.js";import w from"path";import{finalizeConfiguredInputs as N,finalizeConfiguredSteps as y}from"../steps/configured-steps.js";import"../config/loader/index.js";import{finalizeString as h,resolveTemplateString as E}from"../steps/index.js";import{readDatabaseInfo as M}from"../database/index.js";const O=100;export function resolveRuntimeInstanceConfigs(t,e,r,s={}){const o=B(s.graphDirName,e),c=buildPortMap(t,e,{index:s.portNamespaceIndex||0,stride:s.portNamespaceStride||1,portOffset:s.portOffset||0}),i=new Map,p=new Map,l=new Map,u=new Map,d=new Map,m=new Map,S=x(s.externalEndpoints),v=x(s.publicEndpoints),k=x(s.privateEndpoints),f=x(s.serviceContextEndpoints);for(const n of t)m.set(n.name,resolveServiceStateDir(r,n));for(const n of t)n.testkit.local&&(i.set(n.name,resolveRuntimeUrl(n.testkit.local.baseUrl,n.name,e,{runtimeId:e,runtimeLabel:o,runtimeDir:r,portMap:c,baseUrlByService:i,readyUrlByService:p,stateDirByService:m})),n.testkit.local.readyUrl!==!1&&n.testkit.local.readyUrl!=null&&p.set(n.name,resolveRuntimeUrl(n.testkit.local.readyUrl,n.name,e,{runtimeId:e,runtimeLabel:o,runtimeDir:r,portMap:c,baseUrlByService:i,readyUrlByService:p,stateDirByService:m})));for(const[n,a]of Object.entries(S))i.has(n)||i.set(n,a),p.has(n)||p.set(n,a);for(const[n,a]of Object.entries(v))l.set(n,a);for(const[n,a]of Object.entries(k))u.set(n,a);for(const[n,a]of Object.entries(f))d.set(n,a);for(const[n,a]of i.entries())l.has(n)||l.set(n,g(a,s.publicHost||null)),d.has(n)||d.set(n,a),u.has(n)||u.set(n,a);const b=[];for(const n of t){if(!n.testkit.local)continue;const a=i.get(n.name),U=p.get(n.name);a&&b.push([n.testkit.local.baseUrl,a]),U&&b.push([n.testkit.local.readyUrl,U])}return t.map(n=>resolveRuntimeConfig(n,e,o,r,c,i,p,l,u,d,s.publicHost||null,m,b))}function x(t){return Object.fromEntries(Object.entries(t??{}).map(([e,r])=>[e,(r??"").trim()]).filter(e=>e[1].length>0))}export function buildPortMap(t,e,r={}){const s=new Map,o=new Map,c=j(e,r)+Number(r.portOffset||0);for(const i of t){if(!i.testkit.local)continue;const p=i.testkit.local.port||numericPortFromUrl(i.testkit.local.baseUrl);if(!p)continue;const l=p+c;if(l>65535)throw new Error(`Runtime port resolution exceeded 65535 for service "${i.name}" (${String(l)}). Reduce runtime instances or choose lower local.port/baseUrl ports in testkit.config.ts.`);const u=o.get(l);if(u)throw new Error(`Runtime port collision: services "${u}" and "${i.name}" both resolve to ${String(l)}. Assign distinct local.port/baseUrl ports in testkit.config.ts.`);o.set(l,i.name),s.set(i.name,l)}return s}export function resolveRuntimeConfig(t,e,r,s,o,c,i,p,l,u,d,m,S){const v=resolveServiceStateDir(s,t),k=resolveServicePrepareDir(s,t),f={runtimeId:e,runtimeLabel:r,runtimeDir:s,productDir:t.productDir,serviceName:t.name,serviceCwd:t.testkit.local?.cwd||".",serviceStateDir:v,prepareDir:k,portMap:o,baseUrlByService:c,readyUrlByService:i,publicBaseUrlByService:p,privateBaseUrlByService:l,serviceContextUrlByService:u,publicHost:d,stateDirByService:m,urlMappings:S,leaseId:null,leaseDir:null,databaseBindingsReader:M},b=R(t.testkit.database,f),n={...t.testkit.runtime,prepare:P(t.testkit.runtime.prepare,f)},a=t.testkit.local?{...t.testkit.local,start:h(t.testkit.local.start,f),cwd:t.testkit.local.cwd!==void 0?h(t.testkit.local.cwd,f):t.testkit.local.cwd,port:o.get(t.name)||t.testkit.local.port,baseUrl:c.get(t.name),readyUrl:i.get(t.name),env:{...t.testkit.local.env||{}}}:void 0;return{...t,stateDir:v,runtimeId:e,runtimeLabel:r,testkit:{...t.testkit,database:b,prepareDir:k,templateContext:f,runtime:n,local:a}}}export function resolveServiceStateDir(t,e){return w.join(t,"services",e.name)}export function resolveServicePrepareDir(t,e){return w.join(resolveServiceStateDir(t,e),"prepared")}export function resolveRuntimeUrl(t,e,r,s){const o=E(t,{...s,runtimeId:r,serviceName:e}),c=s.portMap.get(e);return c?rewriteUrlPort(o,c):o}function g(t,e){if(!e)return t;try{const r=new URL(t);r.hostname=e;let s=r.toString();return!t.endsWith("/")&&r.pathname==="/"&&s.endsWith("/")&&(s=s.slice(0,-1)),s}catch{return t}}function P(t,e){return t?{inputs:N(t.inputs,r=>h(r,e)),steps:y(t.steps,r=>h(r,e))}:{inputs:[],steps:[]}}export function rewriteUrlPort(t,e){try{const r=new URL(t);if(!r.port)return t;const s=new URL(t);s.port=String(e);let o=s.toString();return!t.endsWith("/")&&r.pathname==="/"&&o.endsWith("/")&&(o=o.slice(0,-1)),o}catch{return t}}export function numericPortFromUrl(t){try{const e=new URL(t),r=Number(e.port);return Number.isInteger(r)&&r>0?r:null}catch{return null}}export function socketFromUrl(t){try{const e=new URL(t),r=Number(e.port);if(!Number.isInteger(r)||r<=0)return null;const s=normalizeSocketHost(e.hostname);return s?{host:s,port:r}:null}catch{return null}}export function normalizeSocketHost(t){return!t||t==="localhost"?"127.0.0.1":t==="[::1]"?"::1":t}function j(t,e={}){const r=String(t).match(/(\d+)$/),s=r?Number.parseInt(r[1],10)-1:0,o=Math.max(0,Number(e.index||0)),c=Math.max(1,Number(e.stride||1));return O*(o*c+s)}function B(t,e){return t?`${t}/${e}`:e}
1
+ import{finalizeRuntimeDatabaseConfig as R}from"../database/runtime-config.js";import w from"path";import{finalizeConfiguredInputs as N,finalizeConfiguredSteps as y}from"../steps/configured-steps.js";import"../config/loader/index.js";import{finalizeString as h,resolveTemplateString as E}from"../steps/index.js";import{readDatabaseInfo as M}from"../database/state.js";const O=100;export function resolveRuntimeInstanceConfigs(t,e,r,s={}){const o=B(s.graphDirName,e),c=buildPortMap(t,e,{index:s.portNamespaceIndex||0,stride:s.portNamespaceStride||1,portOffset:s.portOffset||0}),i=new Map,p=new Map,l=new Map,u=new Map,d=new Map,m=new Map,S=x(s.externalEndpoints),v=x(s.publicEndpoints),k=x(s.privateEndpoints),f=x(s.serviceContextEndpoints);for(const n of t)m.set(n.name,resolveServiceStateDir(r,n));for(const n of t)n.testkit.local&&(i.set(n.name,resolveRuntimeUrl(n.testkit.local.baseUrl,n.name,e,{runtimeId:e,runtimeLabel:o,runtimeDir:r,portMap:c,baseUrlByService:i,readyUrlByService:p,stateDirByService:m})),n.testkit.local.readyUrl!==!1&&n.testkit.local.readyUrl!=null&&p.set(n.name,resolveRuntimeUrl(n.testkit.local.readyUrl,n.name,e,{runtimeId:e,runtimeLabel:o,runtimeDir:r,portMap:c,baseUrlByService:i,readyUrlByService:p,stateDirByService:m})));for(const[n,a]of Object.entries(S))i.has(n)||i.set(n,a),p.has(n)||p.set(n,a);for(const[n,a]of Object.entries(v))l.set(n,a);for(const[n,a]of Object.entries(k))u.set(n,a);for(const[n,a]of Object.entries(f))d.set(n,a);for(const[n,a]of i.entries())l.has(n)||l.set(n,g(a,s.publicHost||null)),d.has(n)||d.set(n,a),u.has(n)||u.set(n,a);const b=[];for(const n of t){if(!n.testkit.local)continue;const a=i.get(n.name),U=p.get(n.name);a&&b.push([n.testkit.local.baseUrl,a]),U&&b.push([n.testkit.local.readyUrl,U])}return t.map(n=>resolveRuntimeConfig(n,e,o,r,c,i,p,l,u,d,s.publicHost||null,m,b))}function x(t){return Object.fromEntries(Object.entries(t??{}).map(([e,r])=>[e,(r??"").trim()]).filter(e=>e[1].length>0))}export function buildPortMap(t,e,r={}){const s=new Map,o=new Map,c=j(e,r)+Number(r.portOffset||0);for(const i of t){if(!i.testkit.local)continue;const p=i.testkit.local.port||numericPortFromUrl(i.testkit.local.baseUrl);if(!p)continue;const l=p+c;if(l>65535)throw new Error(`Runtime port resolution exceeded 65535 for service "${i.name}" (${String(l)}). Reduce runtime instances or choose lower local.port/baseUrl ports in testkit.config.ts.`);const u=o.get(l);if(u)throw new Error(`Runtime port collision: services "${u}" and "${i.name}" both resolve to ${String(l)}. Assign distinct local.port/baseUrl ports in testkit.config.ts.`);o.set(l,i.name),s.set(i.name,l)}return s}export function resolveRuntimeConfig(t,e,r,s,o,c,i,p,l,u,d,m,S){const v=resolveServiceStateDir(s,t),k=resolveServicePrepareDir(s,t),f={runtimeId:e,runtimeLabel:r,runtimeDir:s,productDir:t.productDir,serviceName:t.name,serviceCwd:t.testkit.local?.cwd||".",serviceStateDir:v,prepareDir:k,portMap:o,baseUrlByService:c,readyUrlByService:i,publicBaseUrlByService:p,privateBaseUrlByService:l,serviceContextUrlByService:u,publicHost:d,stateDirByService:m,urlMappings:S,leaseId:null,leaseDir:null,databaseBindingsReader:M},b=R(t.testkit.database,f),n={...t.testkit.runtime,prepare:P(t.testkit.runtime.prepare,f)},a=t.testkit.local?{...t.testkit.local,start:h(t.testkit.local.start,f),cwd:t.testkit.local.cwd!==void 0?h(t.testkit.local.cwd,f):t.testkit.local.cwd,port:o.get(t.name)||t.testkit.local.port,baseUrl:c.get(t.name),readyUrl:i.get(t.name),env:{...t.testkit.local.env||{}}}:void 0;return{...t,stateDir:v,runtimeId:e,runtimeLabel:r,testkit:{...t.testkit,database:b,prepareDir:k,templateContext:f,runtime:n,local:a}}}export function resolveServiceStateDir(t,e){return w.join(t,"services",e.name)}export function resolveServicePrepareDir(t,e){return w.join(resolveServiceStateDir(t,e),"prepared")}export function resolveRuntimeUrl(t,e,r,s){const o=E(t,{...s,runtimeId:r,serviceName:e}),c=s.portMap.get(e);return c?rewriteUrlPort(o,c):o}function g(t,e){if(!e)return t;try{const r=new URL(t);r.hostname=e;let s=r.toString();return!t.endsWith("/")&&r.pathname==="/"&&s.endsWith("/")&&(s=s.slice(0,-1)),s}catch{return t}}function P(t,e){return t?{inputs:N(t.inputs,r=>h(r,e)),steps:y(t.steps,r=>h(r,e))}:{inputs:[],steps:[]}}export function rewriteUrlPort(t,e){try{const r=new URL(t);if(!r.port)return t;const s=new URL(t);s.port=String(e);let o=s.toString();return!t.endsWith("/")&&r.pathname==="/"&&o.endsWith("/")&&(o=o.slice(0,-1)),o}catch{return t}}export function numericPortFromUrl(t){try{const e=new URL(t),r=Number(e.port);return Number.isInteger(r)&&r>0?r:null}catch{return null}}export function socketFromUrl(t){try{const e=new URL(t),r=Number(e.port);if(!Number.isInteger(r)||r<=0)return null;const s=normalizeSocketHost(e.hostname);return s?{host:s,port:r}:null}catch{return null}}export function normalizeSocketHost(t){return!t||t==="localhost"?"127.0.0.1":t==="[::1]"?"::1":t}function j(t,e={}){const r=String(t).match(/(\d+)$/),s=r?Number.parseInt(r[1],10)-1:0,o=Math.max(0,Number(e.index||0)),c=Math.max(1,Number(e.stride||1));return O*(o*c+s)}function B(t,e){return t?`${t}/${e}`:e}