@gfdelarue/testkit 0.6.0 → 0.6.2

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 (156) hide show
  1. package/README.md +455 -56
  2. package/bin/testkit-windows-owner-arm64.exe +0 -0
  3. package/bin/testkit-windows-owner-x64.exe +0 -0
  4. package/dist/app/bugs/batch.js +1 -0
  5. package/dist/app/bugs/ci-report.js +2 -2
  6. package/dist/app/bugs/ci-summary.js +3 -3
  7. package/dist/app/bugs/draft.js +2 -2
  8. package/dist/app/bugs/file.js +1 -1
  9. package/dist/app/bugs/guide.js +1 -1
  10. package/dist/app/bugs/record.js +1 -1
  11. package/dist/app/bugs/run-reading.js +1 -1
  12. package/dist/app/bugs/store.js +1 -1
  13. package/dist/app/campaigns/summary.js +1 -1
  14. package/dist/app/cleanup/index.js +1 -1
  15. package/dist/app/db-schema/prepare.js +1 -1
  16. package/dist/app/db-schema/refresh.js +1 -1
  17. package/dist/app/db-schema/verify.js +1 -1
  18. package/dist/app/destroy/index.js +1 -1
  19. package/dist/app/environments/config-driven.js +1 -1
  20. package/dist/app/environments/file-setup.js +1 -1
  21. package/dist/app/environments/routing.js +1 -1
  22. package/dist/app/environments/static-values.js +1 -1
  23. package/dist/app/environments/supplied.js +1 -1
  24. package/dist/app/lint/shared.js +1 -1
  25. package/dist/app/run/live-run.js +1 -1
  26. package/dist/app/run/orchestrator.js +1 -1
  27. package/dist/app/run/planning.js +1 -1
  28. package/dist/app/run/results.js +1 -1
  29. package/dist/app/run/run-finalization.js +1 -1
  30. package/dist/app/run/run-finisher.js +1 -0
  31. package/dist/app/run/setup/runtime-node-setup-protocol.js +1 -1
  32. package/dist/app/run/setup/runtime-node-setup-worker.js +1 -1
  33. package/dist/app/run/setup/runtime-node-setup.js +2 -2
  34. package/dist/app/status/runs.js +1 -1
  35. package/dist/app/stop/index.js +1 -1
  36. package/dist/app/tracker/index.js +1 -1
  37. package/dist/app/tracker/token.js +2 -2
  38. package/dist/app/typecheck/index.js +4 -4
  39. package/dist/cli/command-catalog.js +1 -1
  40. package/dist/cli/command-flags.js +1 -1
  41. package/dist/cli/commands/bug/file.js +3 -2
  42. package/dist/cli/framework/help.js +5 -5
  43. package/dist/cli/framework/parser.js +1 -1
  44. package/dist/cli/json-errors.js +1 -1
  45. package/dist/cli/renderers/bug/text.js +1 -1
  46. package/dist/cli/renderers/run/formatting.js +1 -1
  47. package/dist/config/loader/config-loader.js +1 -1
  48. package/dist/config/loader/discover-tests.js +1 -1
  49. package/dist/config/loader/discovery-result.js +1 -0
  50. package/dist/config/loader/index.js +1 -1
  51. package/dist/config/loader/runtime-isolation.js +1 -1
  52. package/dist/config/loader/skip-config.js +1 -1
  53. package/dist/config/loader/test-selection.js +1 -1
  54. package/dist/database/admin.js +5 -2
  55. package/dist/database/call-caps.js +1 -0
  56. package/dist/database/cleanup.js +1 -1
  57. package/dist/database/configured-steps.js +1 -1
  58. package/dist/database/constants.js +1 -1
  59. package/dist/database/docker-endpoint.js +1 -0
  60. package/dist/database/docker-metadata.js +2 -2
  61. package/dist/database/docker.js +5 -5
  62. package/dist/database/index.js +2 -1
  63. package/dist/database/lifecycle-cleanup.js +1 -1
  64. package/dist/database/local-postgres.js +4 -1
  65. package/dist/database/naming.js +1 -1
  66. package/dist/database/operation-deadline.js +1 -0
  67. package/dist/database/postgres/connection-check.js +1 -1
  68. package/dist/database/postgres/index.js +2 -2
  69. package/dist/database/postgres/owned-exec.js +27 -0
  70. package/dist/database/postgres/protocol.js +2 -2
  71. package/dist/database/provider.js +1 -0
  72. package/dist/database/roles.js +2 -2
  73. package/dist/database/schema-operations.js +5 -1
  74. package/dist/database/schema-source.js +3 -3
  75. package/dist/database/steps.js +1 -1
  76. package/dist/database/template-steps.js +4 -4
  77. package/dist/domain/suite-selection.js +1 -1
  78. package/dist/engines/adapters/evidence-files.js +1 -1
  79. package/dist/engines/adapters/execution.js +1 -1
  80. package/dist/engines/node-vitest/adapter/adapter.js +1 -1
  81. package/dist/engines/node-vitest/adapter/driver.js +2 -2
  82. package/dist/engines/node-vitest/runtime/runtime-input.js +1 -1
  83. package/dist/engines/playwright/adapter/adapter.js +1 -1
  84. package/dist/engines/playwright/adapter/browser-network-evidence.js +4 -0
  85. package/dist/engines/playwright/adapter/config.js +16 -6
  86. package/dist/engines/playwright/adapter/evidence.js +2 -1
  87. package/dist/engines/playwright/adapter/failure-details.js +2 -2
  88. package/dist/engines/playwright/adapter/reporter.js +1 -1
  89. package/dist/engines/playwright/runtime/browser-network.js +1 -0
  90. package/dist/engines/playwright/runtime/browser-traffic.js +1 -0
  91. package/dist/engines/playwright/runtime/chromium-environment.js +1 -0
  92. package/dist/engines/playwright/runtime/emulation.js +1 -0
  93. package/dist/engines/playwright/runtime/index.js +1 -1
  94. package/dist/engines/playwright/runtime/managed-test.js +1 -1
  95. package/dist/engines/playwright/runtime/runtime-input.js +2 -2
  96. package/dist/environment/lifecycle-controller.js +1 -1
  97. package/dist/environment/lifecycle.js +1 -1
  98. package/dist/environment/maintenance.js +1 -1
  99. package/dist/environment/run-environment.js +1 -1
  100. package/dist/environment/run-stopping.js +1 -1
  101. package/dist/environment/runtime-manager.js +1 -1
  102. package/dist/evidence/artifacts.js +5 -2
  103. package/dist/evidence/bundles.js +2 -1
  104. package/dist/evidence/compressed-artifacts.js +1 -0
  105. package/dist/evidence/environment-record.js +1 -1
  106. package/dist/evidence/index.js +1 -1
  107. package/dist/evidence/live-run-summary.js +1 -0
  108. package/dist/evidence/local-paths.js +1 -0
  109. package/dist/evidence/maintenance.js +1 -1
  110. package/dist/evidence/operation-timings.js +1 -1
  111. package/dist/evidence/redaction.js +4 -4
  112. package/dist/evidence/run-evidence.js +1 -0
  113. package/dist/evidence/setup-progress.js +1 -1
  114. package/dist/evidence/storage-inventory.js +1 -1
  115. package/dist/evidence/storage.js +2 -2
  116. package/dist/kernel/redaction-targets.js +1 -1
  117. package/dist/kernel/redaction.js +7 -4
  118. package/dist/process/engine-environment-keys.js +1 -0
  119. package/dist/process/environment-overlay.js +1 -1
  120. package/dist/process/environment.js +1 -1
  121. package/dist/process/execa-bundle.json +3 -3
  122. package/dist/process/files/evidence-writes.js +1 -0
  123. package/dist/process/files/product-dir.js +1 -1
  124. package/dist/process/host-process.js +1 -1
  125. package/dist/process/index.js +1 -1
  126. package/dist/process/lifecycle-fence.js +1 -1
  127. package/dist/process/lock-progress.js +2 -0
  128. package/dist/process/locks.js +4 -4
  129. package/dist/process/process-identity.js +1 -1
  130. package/dist/process/process.js +35 -35
  131. package/dist/process/recovery.js +1 -1
  132. package/dist/process/runs/manifest.js +1 -1
  133. package/dist/process/runs/ownership.js +1 -1
  134. package/dist/process/runs/stop.js +1 -1
  135. package/dist/process/shutdown.js +1 -1
  136. package/dist/process/windows-native.js +1 -1
  137. package/dist/repo/history.js +7 -4
  138. package/dist/repo/managed-git.js +1 -1
  139. package/dist/repo/state.js +4 -4
  140. package/dist/steps/environment.js +1 -1
  141. package/dist/toolchains/node-provisioning.js +2 -2
  142. package/dist/tracker/gitea.js +1 -1
  143. package/dist/tracker/github.js +1 -1
  144. package/dist/tracker/http.js +1 -1
  145. package/dist/tracker/ownership.js +1 -0
  146. package/dist/tracker/redaction.js +1 -1
  147. package/dist/tracker/responses.js +1 -1
  148. package/dist/types/playwright.d.ts +38 -2
  149. package/dist/types/workspace.d.ts +4 -4
  150. package/docs/bug-records.md +16 -10
  151. package/docs/engines.md +52 -4
  152. package/docs/release-notes.md +749 -0
  153. package/package.json +14 -2
  154. package/testkit-config-schema.md +36 -42
  155. package/dist/config/loader/task-policy.js +0 -1
  156. package/dist/environment/lifecycle-artifacts.js +0 -1
@@ -1 +1 @@
1
- import{resolveCommittedRunFinalization as z}from"../../evidence/finalization.js";import{collectTestkitStorageInventory as T,writeRunArtifact as k}from"../../evidence/index.js";import{buildRunArtifact as w}from"../../evidence/reporting.js";import{isUnknownRecord as x}from"../../kernel/unknown-values.js";import{formatError as C}from"../../kernel/error-serialization.js";export async function finalizeRunArtifacts({productDir:t,runId:r,results:n,startedAt:a,finishedAt:u,execution:c,workerCount:e,selection:i,archiveConfig:d=null,declaredEnvironmentKeys:m=[],metadata:h,environmentRecord:f,operationTimingRegistry:F=null,reporter:b,runInterruptReason:g=null,runStatus:o=null,publishRunFinalization:y}){const l=w({productDir:t,runId:r,results:n,startedAt:a,finishedAt:u,execution:c,workerCount:e,typeValues:i.typeValues,suiteSelectors:i.suiteSelectors,fileNames:i.fileNames,groupFilter:i.groupFilter,planning:i.planning??null,archiveConfig:d,declaredEnvironmentKeys:m,metadata:h,environment:f.snapshot(),operationTimings:F?.listOperations()??[],runInterruptReason:g,runStatus:o}),S=O(l.storage),v=V(t);l.storage={...S??{},testkit:v};const A=o==="failed"||n.some(s=>s.failed||s.infraFailed)?"failed":"passed",R=s=>{N(l,s);const p=I({archiveConfig:d,declaredEnvironmentKeys:m,productDir:t,redactKeys:f.redactKeys,redactionTargets:f.redactionTargets(),runArtifact:l});return b?.runSummary?.(n,u-a,p.runArtifact.run.status==="interrupted"?"interrupted":p.runArtifact.run.status==="failed"?"failed":"passed"),p};return y?await y(A,R):R(z(A,{interrupted:o==="interrupted",interruptReason:o==="interrupted"?g:null}))}function I({archiveConfig:t,declaredEnvironmentKeys:r,productDir:n,redactKeys:a,redactionTargets:u,runArtifact:c}){const e=k(n,c,{archiveConfig:t,declaredEnvironmentKeys:r,redactKeys:a,redactionTargets:u});return{runArtifact:e,shouldFailArchiveCleanup:e.archive.cleanup?.status==="failed",shouldFailArchivePublication:e.archive.publication.status==="failed"&&e.archive.publication.required===!0}}function N(t,r){if(t.run.status=r.artifactStatus,r.artifactStatus!=="interrupted"){delete t.run.interruptReason;return}t.run.interruptReason=r.interruptReason??"run interrupted"}function O(t){return x(t)?t:null}function V(t){try{return T(t)}catch(r){return{status:"failed",error:C(r)}}}
1
+ import{resolveCommittedRunFinalization as k}from"../../evidence/finalization.js";import{writeRunArtifact as z}from"../../evidence/artifacts.js";import{collectTestkitStorageInventory as T}from"../../evidence/index.js";import{buildRunArtifact as w}from"../../evidence/reporting.js";import{isUnknownRecord as x}from"../../kernel/unknown-values.js";import{formatError as C}from"../../kernel/error-serialization.js";export async function finalizeRunArtifacts({productDir:t,runId:r,results:i,startedAt:a,finishedAt:u,execution:c,workerCount:e,selection:n,archiveConfig:d=null,declaredEnvironmentKeys:m=[],metadata:h,environmentRecord:f,operationTimingRegistry:F=null,reporter:b,runInterruptReason:g=null,runStatus:o=null,publishRunFinalization:y}){const l=w({productDir:t,runId:r,results:i,startedAt:a,finishedAt:u,execution:c,workerCount:e,typeValues:n.typeValues,suiteSelectors:n.suiteSelectors,fileNames:n.fileNames,groupFilter:n.groupFilter,planning:n.planning??null,archiveConfig:d,declaredEnvironmentKeys:m,metadata:h,environment:f.snapshot(),operationTimings:F?.listOperations()??[],runInterruptReason:g,runStatus:o}),v=O(l.storage),S=P(t);l.storage={...v??{},testkit:S};const A=o==="failed"||i.some(s=>s.failed||s.infraFailed)?"failed":"passed",R=s=>{N(l,s);const p=I({archiveConfig:d,declaredEnvironmentKeys:m,productDir:t,redactKeys:f.redactKeys,redactionTargets:f.redactionTargets(),runArtifact:l});return b?.runSummary?.(i,u-a,p.runArtifact.run.status==="interrupted"?"interrupted":p.runArtifact.run.status==="failed"?"failed":"passed"),p};return y?await y(A,R):R(k(A,{interrupted:o==="interrupted",interruptReason:o==="interrupted"?g:null}))}function I({archiveConfig:t,declaredEnvironmentKeys:r,productDir:i,redactKeys:a,redactionTargets:u,runArtifact:c}){const e=z(i,c,{archiveConfig:t,declaredEnvironmentKeys:r,redactKeys:a,redactionTargets:u});return{runArtifact:e,shouldFailArchiveCleanup:e.archive.cleanup?.status==="failed",shouldFailArchivePublication:e.archive.publication.status==="failed"&&e.archive.publication.required===!0}}function N(t,r){if(t.run.status=r.artifactStatus,r.artifactStatus!=="interrupted"){delete t.run.interruptReason;return}t.run.interruptReason=r.interruptReason??"run interrupted"}function O(t){return x(t)?t:null}function P(t){try{return T(t,{rootRelativePath:".testkit/results"})}catch(r){return{status:"failed",error:C(r)}}}
@@ -0,0 +1 @@
1
+ import{resolveCommittedRunFinalization as l}from"../../evidence/finalization.js";export function createRunFinisher(e){let i=null;return{get committedFinalization(){return i},finish(n,a,o,r){return e.finalize(o,async t=>{i??=l(n,t.commit(n==="failed"?"failed":"finished"));const m=a(i);return{maintenance:r(await t.maintain()),sealed:m}})}}}
@@ -1 +1 @@
1
- import{isUnknownRecord as u}from"../../../kernel/unknown-values.js";export const MAX_RUNTIME_NODE_SETUP_DATA_BYTES=1024*1024,MAX_RUNTIME_NODE_SETUP_SIDECARS=16;export function isRuntimeNodeSetupWorkerRequest(e){return u(e)&&typeof e.sessionId=="string"&&e.sessionId.length>0&&typeof e.modulePath=="string"}export function isRuntimeNodeSetupParentMessage(e){return _(e)&&(e.type==="cleanup"||e.type==="abort"&&u(e.error)&&S(e.kind))}export function readRuntimeNodeSetupWorkerMessage(e){if(!_(e))return null;switch(e.type){case"abort-acknowledged":break;case"cleanup-started":if(!Array.isArray(e.owners))return null;break;case"cleanup-progress":if(e.owner!=="consumer"&&e.owner!=="testkit"||!Number.isSafeInteger(e.remainingCallbacks))return null;break;case"ready":if(!("setupData"in e)||!Array.isArray(e.sidecars))return null;break;case"sidecar-started":if(!u(e.sidecar))return null;break;case"setup-error":if(!u(e.error)||!S(e.kind)||!Array.isArray(e.cleanupFailures))return null;break;case"aborted":case"cleanup-complete":if(!Array.isArray(e.cleanupFailures))return null;break;default:return null}return e}function S(e){return e==="cancelled"||e==="deadline"||e==="infra"||e==="product"}function _(e){return u(e)&&typeof e.type=="string"&&typeof e.sessionId=="string"&&e.sessionId.length>0}export function serializeRuntimeNodeSetupError(e){const r=g(e,"message"),n=g(e,"name"),i=g(e,"stack");return{message:typeof r=="string"?r:e===null||typeof e!="object"&&typeof e!="function"?String(e):"Runtime node setup failed",name:typeof n=="string"&&n?n:"Error",...typeof i=="string"?{stack:i}:{}}}export function serializeRuntimeNodeSetupCleanupFailures(e){return e.map(({error:r,owner:n})=>({error:serializeRuntimeNodeSetupError(r),owner:n}))}function g(e,r){const n=new Set;try{for(;u(e)&&!n.has(e);){n.add(e);const i=Object.getOwnPropertyDescriptor(e,r);if(i)return"value"in i?i.value:void 0;e=Object.getPrototypeOf(e)}}catch{}}export function normalizeRuntimeNodeSetupData(e){const r={},n=new Set,i=[{value:e===void 0?{}:e,path:"$",target:r,key:"data"}];let h=0;const a=(p,t)=>{throw new TypeError(`Runtime node setup data at ${p} must be plain JSON: ${t}`)},y=(p,t)=>{h+=p,h>MAX_RUNTIME_NODE_SETUP_DATA_BYTES&&a(t,`must encode to no more than ${MAX_RUNTIME_NODE_SETUP_DATA_BYTES.toString()} bytes`)};for(;i.length;){const p=i.pop();if(!p)break;if("leave"in p){n.delete(p.leave);continue}const{value:t,path:s,target:b,key:k}=p;if(t===null||typeof t=="string"||typeof t=="boolean"||typeof t=="number"&&Number.isFinite(t)){y(Buffer.byteLength(JSON.stringify(t)),s),Object.defineProperty(b,k,{value:t,enumerable:!0,writable:!0,configurable:!0});continue}typeof t!="object"&&a(s,`unsupported ${typeof t} value`),n.has(t)&&a(s,"circular reference");const c=Array.isArray(t),w=Object.getPrototypeOf(t);!c&&w!==Object.prototype&&w!==null&&a(s,"expected a plain object");const l=Reflect.ownKeys(t).filter(o=>!c||o!=="length");c&&l.length!==t.length&&a(s,"sparse array or extra array properties");const R=c?[]:{};Object.defineProperty(b,k,{value:R,enumerable:!0,writable:!0,configurable:!0}),y(2+Math.max(0,l.length-1),s),n.add(t),i.push({leave:t});const m=[];for(const o of l){typeof o!="string"&&a(s,"symbol key");const f=c?`${s}[${o}]`:/^[A-Za-z_$][\w$]*$/u.test(o)?`${s}.${o}`:`${s}[${JSON.stringify(o)}]`;c&&(!/^(?:0|[1-9]\d*)$/u.test(o)||Number(o)>=t.length)&&a(f,"extra array property");const d=Object.getOwnPropertyDescriptor(t,o);(!d||!("value"in d)||!d.enumerable)&&a(f,"expected an enumerable data property"),c||y(Buffer.byteLength(JSON.stringify(o))+1,f),m.push({value:d.value,path:f,target:R,key:o})}for(let o=m.length-1;o>=0;o-=1)i.push(m[o])}return r.data}export function normalizeRuntimeNodeSetupSidecarId(e){const r=typeof e=="string"&&e.trim()?e.trim():"http-capture";if(!/^[A-Za-z0-9._-]+$/u.test(r))throw new TypeError("Runtime node setup sidecar id must contain only letters, numbers, dot, underscore, or hyphen");return r}export const HTTP_CAPTURE_OPERATION_PREFIX="/__testkit__/";export function normalizeRuntimeNodeSetupSidecarPath(e){const r=typeof e=="string"&&e.trim()?e.trim():"/capture";if(!r.startsWith("/")||r.startsWith("//")||/[\\?#]/u.test(r))throw new TypeError("Runtime node setup sidecar path must be an absolute URL path");if(r.startsWith(HTTP_CAPTURE_OPERATION_PREFIX))throw new TypeError(`Runtime node setup sidecar path may not start with ${HTTP_CAPTURE_OPERATION_PREFIX}`);return r}export function httpCaptureRedactionTargets({requestsUrl:e,resetUrl:r}){return[e,r].flatMap(n=>typeof n=="string"?[/\/__testkit__\/([A-Za-z0-9_-]+)\/requests$/u.exec(n)?.[1]??n]:[])}
1
+ import{isUnknownRecord as c}from"../../../kernel/unknown-values.js";export const MAX_RUNTIME_NODE_SETUP_DATA_BYTES=1024*1024,MAX_RUNTIME_NODE_SETUP_SIDECARS=16;export function isRuntimeNodeSetupWorkerRequest(e){return c(e)&&typeof e.sessionId=="string"&&e.sessionId.length>0&&typeof e.modulePath=="string"}export function isRuntimeNodeSetupParentMessage(e){return R(e)&&(e.type==="cleanup"||e.type==="abort"&&c(e.error)&&S(e.kind))}export function readRuntimeNodeSetupWorkerMessage(e){if(!R(e))return null;switch(e.type){case"abort-acknowledged":break;case"cleanup-started":if(!Array.isArray(e.owners))return null;break;case"cleanup-progress":if(e.owner!=="consumer"&&e.owner!=="testkit"||!Number.isSafeInteger(e.remainingCallbacks))return null;break;case"ready":if(!("setupData"in e)||!Array.isArray(e.sidecars))return null;break;case"sidecar-started":if(!c(e.sidecar))return null;break;case"setup-error":if(!c(e.error)||!S(e.kind)||!Array.isArray(e.cleanupFailures))return null;break;case"aborted":case"cleanup-complete":if(!Array.isArray(e.cleanupFailures))return null;break;default:return null}return e}function S(e){return e==="cancelled"||e==="deadline"||e==="infra"||e==="product"}function R(e){return c(e)&&typeof e.type=="string"&&typeof e.sessionId=="string"&&e.sessionId.length>0}export function serializeRuntimeNodeSetupError(e){const t=g(e,"message"),n=g(e,"name"),i=g(e,"stack");return{message:typeof t=="string"?t:e===null||typeof e!="object"&&typeof e!="function"?String(e):"Runtime node setup failed",name:typeof n=="string"&&n?n:"Error",...typeof i=="string"?{stack:i}:{}}}export function serializeRuntimeNodeSetupCleanupFailures(e){return e.map(({error:t,owner:n})=>({error:serializeRuntimeNodeSetupError(t),owner:n}))}function g(e,t){const n=new Set;try{for(;c(e)&&!n.has(e);){n.add(e);const i=Object.getOwnPropertyDescriptor(e,t);if(i)return"value"in i?i.value:void 0;e=Object.getPrototypeOf(e)}}catch{}}export function normalizeRuntimeNodeSetupData(e){const t={},n=new Set,i=[{value:e===void 0?{}:e,path:"$",target:t,key:"data"}];let h=0;const a=(p,r)=>{throw new TypeError(`Runtime node setup data at ${p} must be plain JSON: ${r}`)},y=(p,r)=>{h+=p,h>MAX_RUNTIME_NODE_SETUP_DATA_BYTES&&a(r,`must encode to no more than ${MAX_RUNTIME_NODE_SETUP_DATA_BYTES.toString()} bytes`)};for(;i.length;){const p=i.pop();if(!p)break;if("leave"in p){n.delete(p.leave);continue}const{value:r,path:s,target:b,key:_}=p;if(r===null||typeof r=="string"||typeof r=="boolean"||typeof r=="number"&&Number.isFinite(r)){y(Buffer.byteLength(JSON.stringify(r)),s),Object.defineProperty(b,_,{value:r,enumerable:!0,writable:!0,configurable:!0});continue}typeof r!="object"&&a(s,`unsupported ${typeof r} value`),n.has(r)&&a(s,"circular reference");const u=Array.isArray(r),w=Object.getPrototypeOf(r);!u&&w!==Object.prototype&&w!==null&&a(s,"expected a plain object");const l=Reflect.ownKeys(r).filter(o=>!u||o!=="length");u&&l.length!==r.length&&a(s,"sparse array or extra array properties");const k=u?[]:{};Object.defineProperty(b,_,{value:k,enumerable:!0,writable:!0,configurable:!0}),y(2+Math.max(0,l.length-1),s),n.add(r),i.push({leave:r});const m=[];for(const o of l){typeof o!="string"&&a(s,"symbol key");const f=u?`${s}[${o}]`:/^[A-Za-z_$][\w$]*$/u.test(o)?`${s}.${o}`:`${s}[${JSON.stringify(o)}]`;u&&(!/^(?:0|[1-9]\d*)$/u.test(o)||Number(o)>=r.length)&&a(f,"extra array property");const d=Object.getOwnPropertyDescriptor(r,o);(!d||!("value"in d)||!d.enumerable)&&a(f,"expected an enumerable data property"),u||y(Buffer.byteLength(JSON.stringify(o))+1,f),m.push({value:d.value,path:f,target:k,key:o})}for(let o=m.length-1;o>=0;o-=1)i.push(m[o])}return t.data}export function normalizeRuntimeNodeSetupSidecarId(e){const t=typeof e=="string"&&e.trim()?e.trim():"http-capture";if(!/^[A-Za-z0-9._-]+$/u.test(t))throw new TypeError("Runtime node setup sidecar id must contain only letters, numbers, dot, underscore, or hyphen");return t}export const HTTP_CAPTURE_OPERATION_PREFIX="/__testkit__/";export function normalizeRuntimeNodeSetupSidecarPath(e){const t=typeof e=="string"&&e.trim()?e.trim():"/capture";if(!t.startsWith("/")||t.startsWith("//")||/[\\?#]/u.test(t)||Array.from(t).some(n=>n<" "))throw new TypeError("Runtime node setup sidecar path must be an absolute URL path");if(t.startsWith(HTTP_CAPTURE_OPERATION_PREFIX))throw new TypeError(`Runtime node setup sidecar path may not start with ${HTTP_CAPTURE_OPERATION_PREFIX}`);if(t.split("/").some(n=>/^(?:\.|%2e){1,2}$/iu.test(n)))throw new TypeError("Runtime node setup sidecar path may not contain . or .. segments");return t}export function httpCaptureRedactionTargets({requestsUrl:e,resetUrl:t,url:n}){return[...[e,t].flatMap(i=>typeof i=="string"?[/\/__testkit__\/([A-Za-z0-9_-]+)\/requests$/u.exec(i)?.[1]??i]:[]),...typeof n=="string"?A(n):[]]}function A(e){const t=/^https?:\/\/[^/?#]*\/__testkit__\/([A-Za-z0-9_-]+)\//iu.exec(e)?.[1];return[...new Set([t,x(e)])].filter(n=>n!==void 0)}function x(e){try{return/^\/__testkit__\/([A-Za-z0-9_-]+)\//u.exec(new URL(e).pathname)?.[1]}catch{return}}
@@ -1 +1 @@
1
- import{classifyRuntimeNodeSetupFailure as j,createSetupFailure as E}from"./runtime-node-setup-errors.js";import{randomBytes as Y}from"node:crypto";import G from"node:http";import{pathToFileURL as W}from"node:url";import{parentPort as M,workerData as J}from"node:worker_threads";import{HTTP_CAPTURE_OPERATION_PREFIX as $,isRuntimeNodeSetupParentMessage as Q,isRuntimeNodeSetupWorkerRequest as K,MAX_RUNTIME_NODE_SETUP_SIDECARS as F,normalizeRuntimeNodeSetupData as V,normalizeRuntimeNodeSetupSidecarId as Z,normalizeRuntimeNodeSetupSidecarPath as ee,serializeRuntimeNodeSetupError as q,serializeRuntimeNodeSetupCleanupFailures as te}from"./runtime-node-setup-protocol.js";import{isUnknownRecord as B}from"../../../kernel/unknown-values.js";import{toError as re,errorMessage as ne}from"../../../kernel/error-serialization.js";const oe=1024*1024,se=100,ae=8*1024*1024,ie=8*1024*1024,ue=1e3,de=256,s=Re(J),H=be(),p=new AbortController,k=[],C=new Set,A=[];let P=null,D=0,u="starting",g=null;H.on("message",e=>{le(e)}),L(),ce();async function ce(){try{const t=await import(`${W(s.modulePath).href}?testkit=${encodeURIComponent(s.sessionId)}`);I();const r=Te(t);if(!r)throw new TypeError(`Runtime node setup "${s.moduleRelativePath}" must export a setup function`);const n=await r(pe()),o=he(n);o&&k.push({callback:o,owner:"consumer"}),I();const c=V(we(n));S(),u="ready",d({sessionId:s.sessionId,setupData:c,sidecars:A.map(f=>({...f})),type:"ready"})}catch(e){const t=j(e),r=await _();u="cleaned",S(),d({cleanupFailures:r,error:q(e),kind:t,sessionId:s.sessionId,type:"setup-error"})}}async function le(e){if(!Q(e)||e.sessionId!==s.sessionId){await O(E("infra","Runtime node setup worker received an invalid parent message"));return}if(e.type==="abort"){if(p.abort(E(e.kind,e.error.message)),d({sessionId:s.sessionId,type:"abort-acknowledged"}),u==="ready"){u="cleaning";const r=await _();u="cleaned",S(),d({cleanupFailures:r,sessionId:s.sessionId,type:"aborted"})}return}if(u==="cleaned"){d({cleanupFailures:[],sessionId:s.sessionId,type:"cleanup-complete"});return}if(u!=="ready"){await O(E("infra",`Runtime node setup cleanup is invalid during phase "${u}"`));return}u="cleaning";const t=await _();u="cleaned",S(),d({cleanupFailures:t,sessionId:s.sessionId,type:"cleanup-complete"})}async function O(e){p.abort(e);const t=await _();u="cleaned",S(),d({cleanupFailures:t,error:q(e),kind:"infra",sessionId:s.sessionId,type:"setup-error"})}function pe(){return{deadlineMs:s.deadlineMs,env:{...s.runtimeEnv},leaseDir:s.leaseDir,productDir:s.productDir,serviceName:s.serviceName,sidecars:{httpCapture(e={}){if(I(),A.length+D>=F)return Promise.reject(new RangeError(`Runtime node setup may create at most ${F.toString()} sidecars`));D+=1;const t=fe(e).then(r=>(k.push({callback:r.cleanup,owner:"testkit"}),A.push(r.handle),d({sessionId:s.sessionId,sidecar:{...r.handle},type:"sidecar-started"}),I(),r.handle)).finally(()=>{D-=1,C.delete(t)});return C.add(t),t}},signal:p.signal,testFile:s.testFile}}async function fe(e){const t=[],r=new Set;let n=0;const o=Z(e.id),c=ee(e.path),f=z(e.maxRequests,se,ue,"maxRequests"),m=z(e.maxBodyBytes,oe,ae,"maxBodyBytes"),b=Se(e.status,204),l=`${$}${Y(24).toString("base64url")}/requests`,a=G.createServer((i,h)=>{if(i.url===l){i.method==="GET"?R(h,200,{requests:t}):i.method==="DELETE"?(t.splice(0),n=0,R(h,200,{ok:!0})):R(h,405,{error:"HTTP capture requests allow only GET and DELETE"});return}if(i.url?.startsWith($)===!0){R(h,404,{error:"Unknown HTTP capture operation"});return}ye(i,m).then(w=>{const U={bodyBytes:w.bodyBytes,bodyText:w.retainedBody.toString("utf8"),bodyTruncated:w.bodyBytes>w.retainedBody.length,headers:i.headers,method:i.method??null,path:i.url??null,receivedAt:new Date().toISOString()};for(n+=Buffer.byteLength(U.bodyText),t.push(U);t.length>f||n>ie;){const N=t.shift();if(!N)break;n-=Buffer.byteLength(N.bodyText)}R(h,b,{ok:!0})}).catch(w=>{R(h,500,{error:ne(w)})})});a.maxConnections=de,a.requestTimeout=3e4,a.headersTimeout=3e4,a.keepAliveTimeout=1e3,a.on("connection",i=>{r.add(i),i.once("close",()=>r.delete(i))}),await me(a);const y=a.address();if(y===null||typeof y=="string")throw await x(a,r),new Error("Runtime node HTTP capture did not bind a TCP port");const T=`http://127.0.0.1:${y.port.toString()}`,X={id:o,requestsUrl:`${T}${l}`,resetUrl:`${T}${l}`,type:"http-capture",url:`${T}${c}`};return{cleanup:()=>x(a,r),handle:X}}function me(e){return new Promise((t,r)=>{const n=o=>{r(o)};e.once("error",n),e.listen(0,"127.0.0.1",()=>{e.off("error",n),t()})})}function x(e,t){if(!e.listening){for(const r of t)r.destroy();return Promise.resolve()}return new Promise((r,n)=>{e.close(o=>{o?n(o):r()}),e.closeIdleConnections(),e.closeAllConnections();for(const o of t)o.destroy()})}function ye(e,t){return new Promise((r,n)=>{const o=[];let c=0,f=0,m=!1;const b=l=>{m||(m=!0,n(re(l)))};e.on("data",l=>{if(m)return;if(typeof l!="string"&&!(l instanceof Uint8Array)){b(new TypeError("HTTP capture received an unsupported body chunk"));return}const a=Buffer.from(l);c=Math.min(Number.MAX_SAFE_INTEGER,c+a.length);const y=t-f;if(y>0){const T=a.subarray(0,y);o.push(T),f+=T.length}}),e.once("aborted",()=>{b(new Error("HTTP request was aborted"))}),e.once("error",b),e.once("end",()=>{m||(m=!0,r({bodyBytes:c,retainedBody:Buffer.concat(o,f)}))})})}function R(e,t,r){e.destroyed||e.writableEnded||(e.writeHead(t,{"content-type":"application/json"}),e.end(JSON.stringify(r)))}function Te(e){if(!B(e))return null;const t=e.setup??e.default;return typeof t=="function"?t:null}function he(e){if(!B(e))return null;const t=v(e,"cleanup");return typeof t=="function"?t:null}function we(e){return B(e)&&("data"in e||"cleanup"in e)?Object.hasOwn(e,"data")?v(e,"data"):{}:e??{}}function v(e,t){let r=e;for(;r!==null;){const n=Object.getOwnPropertyDescriptor(r,t);if(n){if(!("value"in n))throw new TypeError(`Runtime node setup data at $.${t} must be an enumerable data property`);return n.value}r=Object.getPrototypeOf(r)}}function _(){return P!==null||(P=(async()=>{await Promise.allSettled([...C]);const e=[],t=k.splice(0).reverse();d({owners:t.map(({owner:r})=>r),sessionId:s.sessionId,type:"cleanup-started"});for(const[r,{callback:n,owner:o}]of t.entries()){d({owner:o,remainingCallbacks:t.length-r,sessionId:s.sessionId,type:"cleanup-progress"});try{await n()}catch(c){e.push({error:c,owner:o})}}return te(e)})()),P}function L(){const e=s.deadlineMs-Date.now();if(e<=0){p.abort(Ee());return}g=setTimeout(L,Math.min(e,2147483647))}function S(){g!==null&&clearTimeout(g),g=null}function Ee(){return E("deadline",`Runtime node setup exceeded its deadline ${s.deadlineMs.toString()}`)}function I(){if(p.signal.aborted)throw p.signal.reason instanceof Error?p.signal.reason:new Error(String(p.signal.reason))}function d(e){H.postMessage(e)}function Re(e){if(!K(e))throw E("infra","Runtime node setup worker received invalid worker data");return e}function be(){if(M===null)throw E("infra","Runtime node setup worker requires a parent port");return M}function z(e,t,r,n){if(e===void 0)return t;const o=Number(e);if(!Number.isInteger(o)||o<=0||o>r)throw new RangeError(`Runtime node HTTP capture ${n} must be an integer between 1 and ${r.toString()}`);return o}function Se(e,t){if(e===void 0)return t;const r=Number(e);return Number.isInteger(r)&&r>=100&&r<=599?r:t}
1
+ import{classifyRuntimeNodeSetupFailure as J,createSetupFailure as R}from"./runtime-node-setup-errors.js";import{randomBytes as H}from"node:crypto";import Q from"node:http";import{pathToFileURL as K}from"node:url";import{parentPort as O,workerData as V}from"node:worker_threads";import{HTTP_CAPTURE_OPERATION_PREFIX as A,isRuntimeNodeSetupParentMessage as Z,isRuntimeNodeSetupWorkerRequest as ee,MAX_RUNTIME_NODE_SETUP_SIDECARS as x,normalizeRuntimeNodeSetupData as te,normalizeRuntimeNodeSetupSidecarId as re,normalizeRuntimeNodeSetupSidecarPath as ne,serializeRuntimeNodeSetupError as v,serializeRuntimeNodeSetupCleanupFailures as oe}from"./runtime-node-setup-protocol.js";import{isUnknownRecord as $}from"../../../kernel/unknown-values.js";import{toError as se,errorMessage as ae}from"../../../kernel/error-serialization.js";const ie=1024*1024,ue=100,ce=8*1024*1024,de=8*1024*1024,le=1e3,pe=256,s=Pe(V),L=ge(),p=new AbortController,D=[],U=new Set,N=[];let g=null,q=0,u="starting",_=null;L.on("message",e=>{me(e)}),Y(),fe();async function fe(){try{const t=await import(`${K(s.modulePath).href}?testkit=${encodeURIComponent(s.sessionId)}`);B();const r=Ee(t);if(!r)throw new TypeError(`Runtime node setup "${s.moduleRelativePath}" must export a setup function`);const n=await r(ye()),o=Re(n);o&&D.push({callback:o,owner:"consumer"}),B();const d=te(be(n));S(),u="ready",c({sessionId:s.sessionId,setupData:d,sidecars:N.map(f=>({...f})),type:"ready"})}catch(e){const t=J(e),r=await I();u="cleaned",S(),c({cleanupFailures:r,error:v(e),kind:t,sessionId:s.sessionId,type:"setup-error"})}}async function me(e){if(!Z(e)||e.sessionId!==s.sessionId){await z(R("infra","Runtime node setup worker received an invalid parent message"));return}if(e.type==="abort"){if(p.abort(R(e.kind,e.error.message)),c({sessionId:s.sessionId,type:"abort-acknowledged"}),u==="ready"){u="cleaning";const r=await I();u="cleaned",S(),c({cleanupFailures:r,sessionId:s.sessionId,type:"aborted"})}return}if(u==="cleaned"){c({cleanupFailures:[],sessionId:s.sessionId,type:"cleanup-complete"});return}if(u!=="ready"){await z(R("infra",`Runtime node setup cleanup is invalid during phase "${u}"`));return}u="cleaning";const t=await I();u="cleaned",S(),c({cleanupFailures:t,sessionId:s.sessionId,type:"cleanup-complete"})}async function z(e){p.abort(e);const t=await I();u="cleaned",S(),c({cleanupFailures:t,error:v(e),kind:"infra",sessionId:s.sessionId,type:"setup-error"})}function ye(){return{deadlineMs:s.deadlineMs,env:{...s.runtimeEnv},leaseDir:s.leaseDir,productDir:s.productDir,serviceName:s.serviceName,sidecars:{httpCapture(e={}){if(B(),N.length+q>=x)return Promise.reject(new RangeError(`Runtime node setup may create at most ${x.toString()} sidecars`));q+=1;const t=Te(e).then(r=>(D.push({callback:r.cleanup,owner:"testkit"}),N.push(r.handle),c({sessionId:s.sessionId,sidecar:{...r.handle},type:"sidecar-started"}),B(),r.handle)).finally(()=>{q-=1,U.delete(t)});return U.add(t),t}},signal:p.signal,testFile:s.testFile}}async function Te(e){const t=[],r=new Set;let n=0;const o=re(e.id),d=ne(e.path),f=W(e.maxRequests,ue,le,"maxRequests"),m=W(e.maxBodyBytes,ie,ce,"maxBodyBytes"),b=_e(e.status,204),l=`${A}${H(24).toString("base64url")}/requests`,w=`${A}${H(24).toString("base64url")}`;let y="";const a=Q.createServer((i,T)=>{if(i.headers.host!==y){h(T,403,{error:`HTTP capture accepts only requests for ${y}`});return}const P=i.url??"";if(P===l){i.method==="GET"?h(T,200,{requests:t}):i.method==="DELETE"?(t.splice(0),n=0,h(T,200,{ok:!0})):h(T,405,{error:"HTTP capture requests allow only GET and DELETE"});return}if(!P.startsWith(`${w}/`)){h(T,404,{error:P.startsWith(A)?"Unknown HTTP capture operation":"HTTP capture records only requests to its url"});return}we(i,m).then(E=>{const M={bodyBytes:E.bodyBytes,bodyText:E.retainedBody.toString("utf8"),bodyTruncated:E.bodyBytes>E.retainedBody.length,headers:i.headers,method:i.method??null,path:P.slice(w.length),receivedAt:new Date().toISOString()};for(n+=Buffer.byteLength(M.bodyText),t.push(M);t.length>f||n>de;){const F=t.shift();if(!F)break;n-=Buffer.byteLength(F.bodyText)}h(T,b,{ok:!0})}).catch(E=>{h(T,500,{error:ae(E)})})});a.maxConnections=pe,a.requestTimeout=3e4,a.headersTimeout=3e4,a.keepAliveTimeout=1e3,a.on("connection",i=>{r.add(i),i.once("close",()=>r.delete(i))}),await he(a);const k=a.address();if(k===null||typeof k=="string")throw await X(a,r),new Error("Runtime node HTTP capture did not bind a TCP port");y=`127.0.0.1:${k.port.toString()}`;const C=`http://${y}`,G={id:o,requestsUrl:`${C}${l}`,resetUrl:`${C}${l}`,type:"http-capture",url:`${C}${w}${d}`};return{cleanup:()=>X(a,r),handle:G}}function he(e){return new Promise((t,r)=>{const n=o=>{r(o)};e.once("error",n),e.listen(0,"127.0.0.1",()=>{e.off("error",n),t()})})}function X(e,t){if(!e.listening){for(const r of t)r.destroy();return Promise.resolve()}return new Promise((r,n)=>{e.close(o=>{o?n(o):r()}),e.closeIdleConnections(),e.closeAllConnections();for(const o of t)o.destroy()})}function we(e,t){return new Promise((r,n)=>{const o=[];let d=0,f=0,m=!1;const b=l=>{m||(m=!0,n(se(l)))};e.on("data",l=>{if(m)return;if(typeof l!="string"&&!(l instanceof Uint8Array)){b(new TypeError("HTTP capture received an unsupported body chunk"));return}const w=Buffer.from(l);d=Math.min(Number.MAX_SAFE_INTEGER,d+w.length);const y=t-f;if(y>0){const a=w.subarray(0,y);o.push(a),f+=a.length}}),e.once("aborted",()=>{b(new Error("HTTP request was aborted"))}),e.once("error",b),e.once("end",()=>{m||(m=!0,r({bodyBytes:d,retainedBody:Buffer.concat(o,f)}))})})}function h(e,t,r){e.destroyed||e.writableEnded||(e.writeHead(t,{"content-type":"application/json"}),e.end(JSON.stringify(r)))}function Ee(e){if(!$(e))return null;const t=e.setup??e.default;return typeof t=="function"?t:null}function Re(e){if(!$(e))return null;const t=j(e,"cleanup");return typeof t=="function"?t:null}function be(e){return $(e)&&("data"in e||"cleanup"in e)?Object.hasOwn(e,"data")?j(e,"data"):{}:e??{}}function j(e,t){let r=e;for(;r!==null;){const n=Object.getOwnPropertyDescriptor(r,t);if(n){if(!("value"in n))throw new TypeError(`Runtime node setup data at $.${t} must be an enumerable data property`);return n.value}r=Object.getPrototypeOf(r)}}function I(){return g!==null||(g=(async()=>{await Promise.allSettled([...U]);const e=[],t=D.splice(0).reverse();c({owners:t.map(({owner:r})=>r),sessionId:s.sessionId,type:"cleanup-started"});for(const[r,{callback:n,owner:o}]of t.entries()){c({owner:o,remainingCallbacks:t.length-r,sessionId:s.sessionId,type:"cleanup-progress"});try{await n()}catch(d){e.push({error:d,owner:o})}}return oe(e)})()),g}function Y(){const e=s.deadlineMs-Date.now();if(e<=0){p.abort(Se());return}_=setTimeout(Y,Math.min(e,2147483647))}function S(){_!==null&&clearTimeout(_),_=null}function Se(){return R("deadline",`Runtime node setup exceeded its deadline ${s.deadlineMs.toString()}`)}function B(){if(p.signal.aborted)throw p.signal.reason instanceof Error?p.signal.reason:new Error(String(p.signal.reason))}function c(e){L.postMessage(e)}function Pe(e){if(!ee(e))throw R("infra","Runtime node setup worker received invalid worker data");return e}function ge(){if(O===null)throw R("infra","Runtime node setup worker requires a parent port");return O}function W(e,t,r,n){if(e===void 0)return t;const o=Number(e);if(!Number.isInteger(o)||o<=0||o>r)throw new RangeError(`Runtime node HTTP capture ${n} must be an integer between 1 and ${r.toString()}`);return o}function _e(e,t){if(e===void 0)return t;const r=Number(e);return Number.isInteger(r)&&r>=100&&r<=599?r:t}
@@ -1,3 +1,3 @@
1
- import{classifyRuntimeNodeSetupFailure as R,createSetupFailure as o,createSetupCleanupError as _,createSetupCleanupTimeoutError as y,createUnacknowledgedSetupCleanupError as b,withSetupCleanupFailures as h}from"./runtime-node-setup-errors.js";export{classifyRuntimeNodeSetupFailure,getRuntimeNodeSetupCleanupFailures}from"./runtime-node-setup-errors.js";import F from"node:fs";import{randomUUID as j}from"node:crypto";import a from"node:path";import{Worker as x}from"node:worker_threads";import{isEngineTaskDeadlineExceededReason as M}from"../../../engines/api/index.js";import{readRuntimeNodeSetupWorkerMessage as A,serializeRuntimeNodeSetupError as N}from"./runtime-node-setup-protocol.js";const E=100,C=5e3;export const MAX_RUNTIME_NODE_SETUP_CLEANUP_TIMEOUT_MS=6e4;class I{#o;#m;#i;#$;#w;#c=$();#d;#a;#R=!1;#k=null;#r=null;#s=null;#u=0;#h=null;#f=null;#v=!1;#t="starting";#S=!1;#e=null;#g=null;constructor(e,t,s,r){this.#$=r,this.#w=e,this.#d=t,this.#m=e.deadlineMs,this.#i=e.moduleRelativePath,this.#o=s,this.#a=new x(new URL("./runtime-node-setup-worker.js",import.meta.url),{name:"testkit-runtime-node-setup",workerData:e}),this.#a.on("message",n=>{this.#x(n).catch(c=>{this.#T(o("infra",`Runtime node setup worker for "${this.#i}" sent an invalid message`,c))})}),this.#a.once("error",n=>{this.#T(o("infra",`Runtime node setup worker failed for "${this.#i}"`,n))}),this.#a.once("exit",n=>{this.#v||this.#T(o("infra",`Runtime node setup worker for "${this.#i}" exited unexpectedly with code ${n.toString()}`))}),this.#d.addEventListener("abort",this.#D,{once:!0}),this.#F()}start(){return this.#d.aborted?this.#p(f(this.#d.reason,this.#i)):Date.now()>=this.#m&&this.#p(d(this.#m,this.#i)),this.#c.promise}cleanup=()=>{if(this.#r!==null)return this.#r.promise;const e=$();if(this.#r=e,this.#e!==null&&this.#t==="cleaning")return e.promise;if(this.#e!==null||this.#t==="terminated")return this.#W(),e.promise;if(this.#t!=="ready")return this.#E(o("infra",`Runtime node setup cleanup is invalid during phase "${this.#t}"`)),e.promise;this.#t="cleaning",this.#h=setTimeout(()=>{this.#E(this.#L())},this.#o);try{this.#a.postMessage({sessionId:this.#w.sessionId,type:"cleanup"})}catch(t){this.#E(o("infra",`Could not request runtime node setup cleanup for "${this.#i}"`,t))}return e.promise};#D=()=>{this.#p(f(this.#d.reason,this.#i))};async#x(e){const t=A(e);if(t===null||t.sessionId!==this.#w.sessionId){this.#n(`Runtime node setup worker for "${this.#i}" sent an invalid message`);return}switch(t.type){case"abort-acknowledged":this.#M();return;case"cleanup-started":this.#A(t.owners);return;case"cleanup-progress":this.#N(t.owner,t.remainingCallbacks);return;case"ready":this.#C(t);return;case"sidecar-started":this.#$(t.sidecar);return;case"setup-error":await this.#I(t);return;case"cleanup-complete":await this.#P(t);return;case"aborted":await this.#U(t.cleanupFailures)}}#M(){if(this.#e===null||this.#t==="terminated"){this.#n(`Runtime node setup worker acknowledged an unexpected abort during phase "${this.#t}"`);return}if(this.#R){this.#n("Runtime node setup worker acknowledged abort more than once");return}this.#R=!0,this.#y(this.#s!==null?this.#o:E)}#A(e){if(this.#s!==null||this.#t!=="starting"&&this.#t!=="cleaning"){this.#n(this.#s!==null?"Runtime node setup worker started cleanup more than once":`Runtime node setup worker started cleanup during phase "${this.#t}"`);return}this.#s=[...e],this.#u=0,this.#e!==null&&this.#y(this.#o)}#N(e,t){const s=this.#s?.[this.#u],r=(this.#s?.length??0)-this.#u;if(this.#s===null||this.#t!=="starting"&&this.#t!=="cleaning"||s!==e||r!==t){this.#n(`Runtime node setup worker reported invalid cleanup progress during phase "${this.#t}"`);return}this.#u+=1}#C(e){if(this.#t!=="starting"||this.#e!==null||this.#s!==null){this.#e!==null?this.#p(this.#e):this.#n(this.#s!==null?"Runtime node setup worker became ready after starting cleanup":`Runtime node setup worker became ready during phase "${this.#t}"`);return}const t=e.setupData,s=e.sidecars.map(r=>({...r}));l(this.#f),this.#f=null,this.#t="ready",this.#c.resolve({artifacts:[{name:"node-setup",kind:"testkit.node-setup",summary:`node setup ${this.#i}`,contentType:"application/json",data:{module:this.#i,sidecars:s,setupData:t},emittedAt:new Date().toISOString()}],cleanup:this.cleanup,setupData:t,sidecars:s})}async#I(e){if(this.#t!=="starting"){this.#n(`Runtime node setup worker reported setup failure during phase "${this.#t}"`);return}if(!this.#b()){this.#n("Runtime node setup worker reported setup failure before acknowledging cleanup completion");return}const t=m(e.cleanupFailures),s=U(e.error,e.kind,this.#i,t),r=this.#e===null?s:h(this.#e,t);this.#e=r,await this.#l(),this.#c.reject(this.#e)}async#P(e){if(this.#t!=="cleaning"||this.#r===null||!this.#b()){this.#n(`Runtime node setup worker reported cleanup during phase "${this.#t}"`);return}const t=m(e.cleanupFailures);l(this.#h),this.#h=null,t.length>0&&(this.#e=this.#e===null?_(t):h(this.#e,t)),await this.#l(),this.#e!==null?this.#r.reject(this.#e):this.#r.resolve(void 0)}async#U(e){if(!this.#b()){this.#n("Runtime node setup worker reported abort completion before acknowledging cleanup completion");return}const t=m(e),s=h(this.#e??o("infra",`Runtime node setup worker for "${this.#i}" aborted unexpectedly`),t);this.#e=s,await this.#l();const r=this.#e;this.#c.reject(r),this.#r?.reject(r)}#p(e,t=!1){if(this.#t==="terminated")return;if(t){if(this.#S)return;this.#S=!0}if(this.#e!==null){t&&(this.#e=h(this.#e,[{error:e,owner:"testkit"}]));return}const s=this.#t==="ready",r=s||this.#t==="cleaning"?this.#o:E;this.#e=e,s&&(this.#t="cleaning"),this.#j(),l(this.#h),this.#h=null;try{this.#a.postMessage({error:N(e),kind:R(e),sessionId:this.#w.sessionId,type:"abort"})}catch{this.#_();return}this.#y(r)}async#_(){const e=this.#e??o("infra",`Runtime node setup worker for "${this.#i}" was terminated`),t=this.#O();this.#e=h(e,[t]),await this.#l();const s=this.#e;this.#c.reject(s),this.#r?.reject(s)}#y(e){l(this.#k),this.#k=setTimeout(()=>{this.#_()},e)}#O(){if(this.#s!==null){const e=this.#s.at(Math.max(0,this.#u-1))??"testkit";return{error:y(this.#o,e),owner:e}}return{error:b(this.#R),owner:"testkit"}}#L(){return this.#s===null?b(!1):y(this.#o,this.#s.at(Math.max(0,this.#u-1))??"testkit")}#b(){return this.#s!==null&&this.#u===this.#s.length}#n(e){this.#p(o("infra",e),!0)}async#W(){await this.#l();const e=this.#e??o("infra",`Runtime node setup worker for "${this.#i}" is unavailable during cleanup`);this.#r?.reject(e)}async#E(e){this.#e??=e,await this.#l(),this.#r?.reject(this.#e)}#T(e){this.#e===null&&(this.#e=e),this.#l().then(()=>{this.#c.reject(this.#e),this.#r?.reject(this.#e)})}#F(){const e=this.#m-Date.now();if(e<=0){this.#p(d(this.#m,this.#i));return}this.#f=setTimeout(()=>{this.#F()},Math.min(e,2147483647))}#j(){l(this.#f),this.#f=null,this.#d.removeEventListener("abort",this.#D)}#l(){return this.#g!==null?this.#g:(this.#v=!0,this.#t="terminated",this.#j(),l(this.#k),l(this.#h),this.#k=null,this.#h=null,this.#g=this.#a.terminate().then(()=>{}).catch(e=>{const t=o("infra",`Failed to terminate runtime node setup worker for "${this.#i}"`,e);this.#e=this.#e===null?t:h(this.#e,[{error:t,owner:"testkit"}])}),this.#g)}}export async function runRuntimeNodeSetup({cleanupTimeoutMs:i,deadlineMs:e,targetConfig:t,task:s,signal:r,lease:n,onSidecar:c=()=>{},runtimeEnv:p={}}){const v=Math.min(i??C,MAX_RUNTIME_NODE_SETUP_CLEANUP_TIMEOUT_MS),u=findRuntimeNodeSetupFile(t.productDir,s.file);if(r.aborted)throw f(r.reason,u?w(t.productDir,u):s.file);if(Date.now()>=e)throw d(e,u?w(t.productDir,u):s.file);if(!u)return P();const k=w(t.productDir,u),S={deadlineMs:e,leaseDir:n?.leaseDir??null,modulePath:u,moduleRelativePath:k,productDir:t.productDir,runtimeEnv:p,serviceName:t.name.trim(),sessionId:j(),testFile:s.file.trim()};let g;try{g=new I(S,r,v,c)}catch(D){throw o("infra",`Could not start runtime node setup worker for "${k}"`,D)}return g.start()}export function findRuntimeNodeSetupFile(i,e){const t=a.resolve(i),s=a.resolve(t,e),r=a.relative(t,s);if(r===".."||r.startsWith(`..${a.sep}`)||a.isAbsolute(r))throw new Error("Runtime node setup test file must be inside productDir");const n=a.parse(s);return[a.join(n.dir,`${n.name}.setup.mjs`),a.join(n.dir,`${n.name}.setup.js`),a.join(n.dir,"__setup__",`${n.name}.mjs`),a.join(n.dir,"__setup__",`${n.name}.js`)].find(p=>F.existsSync(p))??null}function P(){const i=Promise.resolve();return{artifacts:[],cleanup:()=>i,setupData:{},sidecars:[]}}function U(i,e,t,s){const r=T(i),n=o(e,`Runtime node setup "${t}" failed: ${r.message}`,r);return r.stack&&(n.stack=`${n.stack??n.message}
1
+ import{classifyRuntimeNodeSetupFailure as k,createSetupFailure as o,createSetupCleanupError as _,createSetupCleanupTimeoutError as g,createUnacknowledgedSetupCleanupError as R,withSetupCleanupFailures as u}from"./runtime-node-setup-errors.js";export{classifyRuntimeNodeSetupFailure,getRuntimeNodeSetupCleanupFailures}from"./runtime-node-setup-errors.js";import D from"node:fs";import{randomUUID as F}from"node:crypto";import a from"node:path";import{Worker as j}from"node:worker_threads";import{readRuntimeNodeSetupWorkerMessage as x,serializeRuntimeNodeSetupError as M}from"./runtime-node-setup-protocol.js";const b=100,N=5e3;export const MAX_RUNTIME_NODE_SETUP_CLEANUP_TIMEOUT_MS=6e4;class C{#o;#m;#r;#E;#w;#c=$();#d;#a;#R=!1;#k=null;#s=null;#i=null;#u=0;#h=null;#f=null;#S=!1;#t="starting";#T=!1;#e=null;#g=null;constructor(e,t,s,r){this.#E=r,this.#w=e,this.#d=t,this.#m=e.deadlineMs,this.#r=e.moduleRelativePath,this.#o=s,this.#a=new j(new URL("./runtime-node-setup-worker.js",import.meta.url),{name:"testkit-runtime-node-setup",workerData:e}),this.#a.on("message",n=>{this.#x(n).catch(c=>{this.#$(o("infra",`Runtime node setup worker for "${this.#r}" sent an invalid message`,c))})}),this.#a.once("error",n=>{this.#$(o("infra",`Runtime node setup worker failed for "${this.#r}"`,n))}),this.#a.once("exit",n=>{this.#S||this.#$(o("infra",`Runtime node setup worker for "${this.#r}" exited unexpectedly with code ${n.toString()}`))}),this.#d.addEventListener("abort",this.#_,{once:!0}),this.#F()}start(){return this.#d.aborted?this.#p(m(this.#d.reason)):Date.now()>=this.#m&&this.#p(runtimeNodeSetupDeadlineError(this.#m,this.#r)),this.#c.promise}cleanup=()=>{if(this.#s!==null)return this.#s.promise;const e=$();if(this.#s=e,this.#e!==null&&this.#t==="cleaning")return e.promise;if(this.#e!==null||this.#t==="terminated")return this.#W(),e.promise;if(this.#t!=="ready")return this.#y(o("infra",`Runtime node setup cleanup is invalid during phase "${this.#t}"`)),e.promise;this.#t="cleaning",this.#h=setTimeout(()=>{this.#y(this.#O())},this.#o);try{this.#a.postMessage({sessionId:this.#w.sessionId,type:"cleanup"})}catch(t){this.#y(o("infra",`Could not request runtime node setup cleanup for "${this.#r}"`,t))}return e.promise};#_=()=>{this.#p(m(this.#d.reason))};async#x(e){const t=x(e);if(t===null||t.sessionId!==this.#w.sessionId){this.#n(`Runtime node setup worker for "${this.#r}" sent an invalid message`);return}switch(t.type){case"abort-acknowledged":this.#M();return;case"cleanup-started":this.#N(t.owners);return;case"cleanup-progress":this.#C(t.owner,t.remainingCallbacks);return;case"ready":this.#A(t);return;case"sidecar-started":this.#E(t.sidecar);return;case"setup-error":await this.#I(t);return;case"cleanup-complete":await this.#P(t);return;case"aborted":await this.#U(t.cleanupFailures)}}#M(){if(this.#e===null||this.#t==="terminated"){this.#n(`Runtime node setup worker acknowledged an unexpected abort during phase "${this.#t}"`);return}if(this.#R){this.#n("Runtime node setup worker acknowledged abort more than once");return}this.#R=!0,this.#b(this.#i!==null?this.#o:b)}#N(e){if(this.#i!==null||this.#t!=="starting"&&this.#t!=="cleaning"){this.#n(this.#i!==null?"Runtime node setup worker started cleanup more than once":`Runtime node setup worker started cleanup during phase "${this.#t}"`);return}this.#i=[...e],this.#u=0,this.#e!==null&&this.#b(this.#o)}#C(e,t){const s=this.#i?.[this.#u],r=(this.#i?.length??0)-this.#u;if(this.#i===null||this.#t!=="starting"&&this.#t!=="cleaning"||s!==e||r!==t){this.#n(`Runtime node setup worker reported invalid cleanup progress during phase "${this.#t}"`);return}this.#u+=1}#A(e){if(this.#t!=="starting"||this.#e!==null||this.#i!==null){this.#e!==null?this.#p(this.#e):this.#n(this.#i!==null?"Runtime node setup worker became ready after starting cleanup":`Runtime node setup worker became ready during phase "${this.#t}"`);return}const t=e.setupData,s=e.sidecars.map(r=>({...r}));h(this.#f),this.#f=null,this.#t="ready",this.#c.resolve({cleanup:this.cleanup,setupData:t,sidecars:s})}async#I(e){if(this.#t!=="starting"){this.#n(`Runtime node setup worker reported setup failure during phase "${this.#t}"`);return}if(!this.#v()){this.#n("Runtime node setup worker reported setup failure before acknowledging cleanup completion");return}const t=p(e.cleanupFailures),s=I(e.error,e.kind,this.#r,t),r=this.#e===null?s:u(this.#e,t);this.#e=r,await this.#l(),this.#c.reject(this.#e)}async#P(e){if(this.#t!=="cleaning"||this.#s===null||!this.#v()){this.#n(`Runtime node setup worker reported cleanup during phase "${this.#t}"`);return}const t=p(e.cleanupFailures);h(this.#h),this.#h=null,t.length>0&&(this.#e=this.#e===null?_(t):u(this.#e,t)),await this.#l(),this.#e!==null?this.#s.reject(this.#e):this.#s.resolve(void 0)}async#U(e){if(!this.#v()){this.#n("Runtime node setup worker reported abort completion before acknowledging cleanup completion");return}const t=p(e),s=u(this.#e??o("infra",`Runtime node setup worker for "${this.#r}" aborted unexpectedly`),t);this.#e=s,await this.#l();const r=this.#e;this.#c.reject(r),this.#s?.reject(r)}#p(e,t=!1){if(this.#t==="terminated")return;if(t){if(this.#T)return;this.#T=!0}if(this.#e!==null){t&&(this.#e=u(this.#e,[{error:e,owner:"testkit"}]));return}const s=this.#t==="ready",r=s||this.#t==="cleaning"?this.#o:b;this.#e=e,s&&(this.#t="cleaning"),this.#j(),h(this.#h),this.#h=null;try{this.#a.postMessage({error:M(e),kind:k(e),sessionId:this.#w.sessionId,type:"abort"})}catch{this.#D();return}this.#b(r)}async#D(){const e=this.#e??o("infra",`Runtime node setup worker for "${this.#r}" was terminated`),t=this.#L();this.#e=u(e,[t]),await this.#l();const s=this.#e;this.#c.reject(s),this.#s?.reject(s)}#b(e){h(this.#k),this.#k=setTimeout(()=>{this.#D()},e)}#L(){if(this.#i!==null){const e=this.#i.at(Math.max(0,this.#u-1))??"testkit";return{error:g(this.#o,e),owner:e}}return{error:R(this.#R),owner:"testkit"}}#O(){return this.#i===null?R(!1):g(this.#o,this.#i.at(Math.max(0,this.#u-1))??"testkit")}#v(){return this.#i!==null&&this.#u===this.#i.length}#n(e){this.#p(o("infra",e),!0)}async#W(){await this.#l();const e=this.#e??o("infra",`Runtime node setup worker for "${this.#r}" is unavailable during cleanup`);this.#s?.reject(e)}async#y(e){this.#e??=e,await this.#l(),this.#s?.reject(this.#e)}#$(e){this.#e===null&&(this.#e=e),this.#l().then(()=>{this.#c.reject(this.#e),this.#s?.reject(this.#e)})}#F(){const e=this.#m-Date.now();if(e<=0){this.#p(runtimeNodeSetupDeadlineError(this.#m,this.#r));return}this.#f=setTimeout(()=>{this.#F()},Math.min(e,2147483647))}#j(){h(this.#f),this.#f=null,this.#d.removeEventListener("abort",this.#_)}#l(){return this.#g!==null?this.#g:(this.#S=!0,this.#t="terminated",this.#j(),h(this.#k),h(this.#h),this.#k=null,this.#h=null,this.#g=this.#a.terminate().then(()=>{}).catch(e=>{const t=o("infra",`Failed to terminate runtime node setup worker for "${this.#r}"`,e);this.#e=this.#e===null?t:u(this.#e,[{error:t,owner:"testkit"}])}),this.#g)}}export async function runRuntimeNodeSetup({cleanupTimeoutMs:i,deadlineMs:e,targetConfig:t,task:s,signal:r,lease:n,onSidecar:c=()=>{},runtimeEnv:d={}}){const E=Math.min(i??N,MAX_RUNTIME_NODE_SETUP_CLEANUP_TIMEOUT_MS),l=findRuntimeNodeSetupFile(t.productDir,s.file);if(r.aborted)throw m(r.reason);if(Date.now()>=e)throw runtimeNodeSetupDeadlineError(e,l?y(t.productDir,l):s.file);if(!l)return A();const f=y(t.productDir,l),S={deadlineMs:e,leaseDir:n?.leaseDir??null,modulePath:l,moduleRelativePath:f,productDir:t.productDir,runtimeEnv:d,serviceName:t.name.trim(),sessionId:F(),testFile:s.file.trim()};let w;try{w=new C(S,r,E,c)}catch(T){throw o("infra",`Could not start runtime node setup worker for "${f}"`,T)}return w.start()}export function findRuntimeNodeSetupFile(i,e){const t=a.resolve(i),s=a.resolve(t,e),r=a.relative(t,s);if(r===".."||r.startsWith(`..${a.sep}`)||a.isAbsolute(r))throw new Error("Runtime node setup test file must be inside productDir");const n=a.parse(s);return[a.join(n.dir,`${n.name}.setup.mjs`),a.join(n.dir,`${n.name}.setup.js`),a.join(n.dir,"__setup__",`${n.name}.mjs`),a.join(n.dir,"__setup__",`${n.name}.js`)].find(d=>D.existsSync(d))??null}function A(){const i=Promise.resolve();return{cleanup:()=>i,setupData:{},sidecars:[]}}function I(i,e,t,s){const r=v(i),n=o(e,`Runtime node setup "${t}" failed: ${r.message}`,r);return r.stack&&(n.stack=`${n.stack??n.message}
2
2
  Remote stack:
3
- ${r.stack}`),h(n,s)}function T(i){const e=new Error(i.message);return e.name=i.name,i.stack&&(e.stack=i.stack),e}function m(i){return i.map(e=>({error:T(e.error),owner:e.owner}))}function f(i,e){return R(i)==="deadline"&&i instanceof Error?i:M(i)?d(i.deadlineMs,e):o("cancelled","Runtime node setup was cancelled",i)}function d(i,e){return o("deadline",`Runtime node setup exceeded its deadline ${i.toString()}${e?` for "${e}"`:""}`)}function w(i,e){return a.relative(i,e).split(a.sep).join("/")}function $(){let i,e;return{promise:new Promise((s,r)=>{i=s,e=r}),resolve:i,reject:e}}function l(i){i!==null&&clearTimeout(i)}
3
+ ${r.stack}`),u(n,s)}function v(i){const e=new Error(i.message);return e.name=i.name,i.stack&&(e.stack=i.stack),e}function p(i){return i.map(e=>({error:v(e.error),owner:e.owner}))}function m(i){return k(i)==="deadline"&&i instanceof Error?i:o("cancelled","Runtime node setup was cancelled",i)}export function runtimeNodeSetupDeadlineError(i,e){return o("deadline",`Runtime node setup exceeded its deadline ${i.toString()}${e?` for "${e}"`:""}`)}function y(i,e){return a.relative(i,e).split(a.sep).join("/")}function $(){let i,e;return{promise:new Promise((s,r)=>{i=s,e=r}),resolve:i,reject:e}}function h(i){i!==null&&clearTimeout(i)}
@@ -1 +1 @@
1
- import c from"node:fs";import f from"node:path";import{inspectRunManifests as d}from"../../process/runs/inspection.js";import{resolveProductDir as m}from"../../process/files/product-dir.js";import{asUnknownRecord as o,displayString as l,isUnknownRecord as p,optionalString as i}from"../../kernel/unknown-values.js";export async function collectRunStatus(t){if(t.runId){const e=m(process.cwd(),t.dir);if(!c.statSync(e).isDirectory())throw new Error(`Product directory is not a directory: ${e}`);return buildRunStatus({productDir:e},t)}const{loadConfigContext:r}=await import("../../config/loader/index.js"),n=await r({dir:t.dir,allowUnmigratedServices:!0,plain:!1,retired:[]});return buildRunStatus(n,t)}export function buildRunStatus(t,r={}){const n=d(t.productDir,r.runId||void 0),e=k(f.join(t.productDir,".testkit","results","live.json"));if(r.full)return Promise.resolve({productDir:t.productDir,mode:"full",runs:n,live:e});const s={productDir:t.productDir,mode:"summary",runs:n.map(g),live:w(e)};return s.live&&(s.live.state=describeLiveArtifactState(s)),Promise.resolve(s)}export function latestOperation(t){if(!Array.isArray(t))return null;const r=t.filter(p);return[...r].reverse().find(n=>n.status==="running")??r.at(-1)??null}export function describeLiveArtifactState(t){const r=o(t.live),n=o(r?.run),e=l(n?.id,"unknown"),s=l(n?.status,"unknown"),a=y(t,e);return s==="running"&&!a?{runId:e,status:"stale",reason:"no active run matches this live artifact"}:{runId:e,status:s,reason:i(n?.interruptReason)}}function y(t,r){if(!r||r==="unknown")return!1;for(const n of t.runs??[])if(n.active&&v(r,n.runId))return!0;return!1}function v(t,r){if(!r)return!1;const n=typeof r=="string"?r:typeof r=="number"&&Number.isFinite(r)?r.toString():null;return n?n===t||n.startsWith(`${t}:`):!1}function g(t){const r=A(t.status);return{runId:t.runId,pid:t.pid,status:t.status||"unknown",startedAt:t.startedAt||null,heartbeatAt:t.heartbeatAt||null,interruptReason:t.interruptReason||null,active:t.active,terminal:r,stale:!t.active&&!r,session:h(t.session)}}function A(t){return typeof t=="string"&&["finished","failed","interrupted"].includes(t)}function h(t){const r=o(t);return r?{kind:i(r.kind),status:i(r.status),stage:i(r.stage)}:null}function w(t){const r=o(t);if(!r)return null;const e=o(r.timings)?.operations??[],s=Array.isArray(e)?e:[],a=o(r.product),u=o(r.run);return{present:!0,schemaVersion:S(r.schemaVersion),generatedAt:i(r.generatedAt),product:a?{name:i(a.name),directory:i(a.directory)}:null,run:u?{id:i(u.id),status:i(u.status),startedAt:i(u.startedAt),finishedAt:i(u.finishedAt),durationMs:b(u.durationMs),interruptReason:i(u.interruptReason),testkitVersion:i(u.testkitVersion)}:null,summary:r.summary??null,operation:latestOperation(s),operationCount:s.length,artifactRef:".testkit/results/live.json",truncated:!0}}function b(t){const r=Number(t);return Number.isFinite(r)?r:null}function S(t){return typeof t=="number"&&Number.isFinite(t)?t:null}function k(t){try{return JSON.parse(c.readFileSync(t,"utf8"))}catch{return null}}
1
+ import c from"node:fs";import{inspectRunManifests as l}from"../../process/runs/inspection.js";import{resolveProductDir as f}from"../../process/files/product-dir.js";import{asUnknownRecord as u,displayString as o,optionalString as s}from"../../kernel/unknown-values.js";import{readLiveRunArtifact as d,summarizeLiveArtifact as p}from"../../evidence/live-run-summary.js";export{latestOperation}from"../../evidence/live-run-summary.js";export async function collectRunStatus(r){if(r.runId){const e=f(process.cwd(),r.dir);if(!c.statSync(e).isDirectory())throw new Error(`Product directory is not a directory: ${e}`);return buildRunStatus({productDir:e},r)}const{loadConfigContext:t}=await import("../../config/loader/index.js"),n=await t({dir:r.dir,allowUnmigratedServices:!0,plain:!1,retired:[]});return buildRunStatus(n,r)}export function buildRunStatus(r,t={}){const n=l(r.productDir,t.runId||void 0),e=d(r.productDir);if(t.full)return Promise.resolve({productDir:r.productDir,mode:"full",runs:n,live:e});const i={productDir:r.productDir,mode:"summary",runs:n.map(w),live:p(e)};return i.live&&(i.live.state=describeLiveArtifactState(i)),Promise.resolve(i)}export function describeLiveArtifactState(r){const t=u(r.live),n=u(t?.run),e=o(n?.id,"unknown"),i=o(n?.status,"unknown"),a=m(r,e);return i==="running"&&!a?{runId:e,status:"stale",reason:"no active run matches this live artifact"}:{runId:e,status:i,reason:s(n?.interruptReason)}}function m(r,t){if(!t||t==="unknown")return!1;for(const n of r.runs??[])if(n.active&&v(t,n.runId))return!0;return!1}function v(r,t){if(!t)return!1;const n=typeof t=="string"?t:typeof t=="number"&&Number.isFinite(t)?t.toString():null;return n?n===r||n.startsWith(`${r}:`):!1}function w(r){const t=y(r.status);return{runId:r.runId,pid:r.pid,status:r.status||"unknown",startedAt:r.startedAt||null,heartbeatAt:r.heartbeatAt||null,interruptReason:r.interruptReason||null,active:r.active,terminal:t,stale:!r.active&&!t,session:S(r.session)}}function y(r){return typeof r=="string"&&["finished","failed","interrupted"].includes(r)}function S(r){const t=u(r);return t?{kind:s(t.kind),status:s(t.status),stage:s(t.stage)}:null}
@@ -1 +1 @@
1
- import s from"node:fs";import{resolveProductDir as a}from"../../process/files/product-dir.js";import{createEnvironmentMaintenance as i}from"../../environment/maintenance.js";import{normalizeOperationReason as o}from"../../domain/operation-reason.js";export async function stopRuns(n,r={}){return i(n).stop(r.runId?{kind:"run",runId:r.runId}:r.all?{kind:"all"}:{kind:"latest-active"},o(r.reason,"run stopped"))}export async function stopProductRuns(n,r){const t=a(process.cwd(),n.dir);if(!s.statSync(t).isDirectory())throw new Error(`Product directory is not a directory: ${t}`);const e=o(n.reason,r),c=await stopRuns(t,{...n,reason:e});return{productDir:t,stoppedRuns:c}}
1
+ import i from"node:fs";import{resolveProductDir as s}from"../../process/files/product-dir.js";import{createEnvironmentMaintenance as a}from"../../environment/maintenance.js";import{normalizeOperationReason as e}from"../../domain/operation-reason.js";import{createRunEvidencePublisher as u}from"../../evidence/run-evidence.js";export async function stopRuns(n,r={}){return a(n).stop(r.runId?{kind:"run",runId:r.runId}:r.all?{kind:"all"}:{kind:"latest-active"},e(r.reason,"run stopped"),u())}export async function stopProductRuns(n,r){const t=s(process.cwd(),n.dir);if(!i.statSync(t).isDirectory())throw new Error(`Product directory is not a directory: ${t}`);const o=e(n.reason,r),c=await stopRuns(t,{...n,reason:o});return{productDir:t,stoppedRuns:c}}
@@ -1 +1 @@
1
- import{runManagedGitCommand as l}from"../../repo/managed-git.js";import{readGiteaVersion as p}from"../../tracker/gitea.js";import{createTracker as u}from"../../tracker/index.js";import{displayRepositoryReference as m,githubActionsApiUrl as h,parseTrackerRemote as g,parseTrackerRepositoryOverride as c,TrackerTargetError as n}from"./remote.js";import{readGitCredentialToken as d,trackerTokenFromEnvironment as f,trackerTokenVariables as w}from"./token.js";export{TrackerTargetError}from"./remote.js";const v="github.com",T="https://api.github.com";export async function resolveTracker(r){return u((await resolveRepositoryConnection(r)).target)}export async function resolveRepositoryConnection(r){const t=await k(r),{actionsRun:o,apiUrl:e,provider:i}=await R(t,r.env),a=w(i,o),s=f(a,r.env)??await d(r.productDir,t,r.env);if(s===null)throw new n(`Testkit found no token for ${t.origin}. Set ${a.join(" or ")}, or give Git a credential helper that holds one for that host.`);return{location:t,target:{apiUrl:e,name:t.name,owner:t.owner,provider:i,token:s}}}async function k(r){const t=r.repository?.trim()??"";if(/^https?:\/\//iu.test(t))return c(t,null);const o=await y(r),e=o===null?null:g(o);if(t.length>0)return c(t,e);if(e!==null)return e;throw new n(o===null?`${r.productDir} has no origin remote, so Testkit cannot tell which repository to report to. Add the remote, or name the repository as a URL such as https://host/owner/name.`:`Testkit cannot read a host, owner, and repository name from the origin remote ${JSON.stringify(m(o))}. Name the repository as a URL such as https://host/owner/name.`)}async function y(r){const t=await l(r.productDir,["config","--get","remote.origin.url"],{env:r.env,repositoryConfigOnly:!0}),o=t.stdout.trim();return t.exitCode===0&&o.length>0?o:null}async function R(r,t){if(new URL(r.origin).host===v)return{actionsRun:!1,apiUrl:T,provider:"github"};const o=h(r.origin,t);if(await p(r.origin)!==null)return{actionsRun:o!==null,apiUrl:o??`${r.origin}/api/v1`,provider:"gitea"};if(o!==null)return{actionsRun:!0,apiUrl:o,provider:"github"};throw new n(`${r.origin} does not answer as GitHub or Gitea. Testkit supports GitHub and Gitea.`)}
1
+ import{runManagedGitCommand as p}from"../../repo/managed-git.js";import{readGiteaVersion as u}from"../../tracker/gitea.js";import{createTracker as m}from"../../tracker/index.js";import{displayRepositoryReference as h,githubActionsApiUrl as g,parseTrackerRemote as d,parseTrackerRepositoryOverride as c,TrackerTargetError as n}from"./remote.js";import{missingTrackerTokenMessage as f,readGitCredentialToken as T,trackerTokenFromEnvironment as w,trackerTokenVariables as y}from"./token.js";export{TrackerTargetError}from"./remote.js";const k="github.com",v=/^(?:localhost|127(?:\.\d{1,3}){3}|\[::1\])$/iu,U="https://api.github.com";export async function resolveTracker(r){return m((await resolveRepositoryConnection(r)).target)}export async function resolveRepositoryConnection(r){const t=await R(r);l(t.origin);const{actionsRun:o,apiUrl:e,provider:i}=await G(t,r.env);l(e);const a=y(i,o,t.origin,r.env),s=w(a,r.env)??await T(r.productDir,t,r.env);if(s===null)throw new n(f(t.origin,a));return{location:t,target:{apiUrl:e,name:t.name,owner:t.owner,provider:i,token:s}}}function l(r){const t=new URL(r);if(!(t.protocol==="https:"||v.test(t.hostname)))throw new n(`Testkit sends a token only over HTTPS, and ${t.origin} uses HTTP. Use an https:// remote or repository URL. Plain HTTP is allowed only for a loopback host.`)}async function R(r){const t=r.repository?.trim()??"";if(/^https?:\/\//iu.test(t))return c(t,null);const o=await b(r),e=o===null?null:d(o);if(t.length>0)return c(t,e);if(e!==null)return e;throw new n(o===null?`${r.productDir} has no origin remote, so Testkit cannot tell which repository to report to. Add the remote, or name the repository as a URL such as https://host/owner/name.`:`Testkit cannot read a host, owner, and repository name from the origin remote ${JSON.stringify(h(o))}. Name the repository as a URL such as https://host/owner/name.`)}async function b(r){const t=await p(r.productDir,["config","--get","remote.origin.url"],{env:r.env,repositoryConfigOnly:!0}),o=t.stdout.trim();return t.exitCode===0&&o.length>0?o:null}async function G(r,t){if(new URL(r.origin).host===k)return{actionsRun:!1,apiUrl:U,provider:"github"};const o=g(r.origin,t);if(await u(r.origin)!==null)return{actionsRun:o!==null,apiUrl:o??`${r.origin}/api/v1`,provider:"gitea"};if(o!==null)return{actionsRun:!0,apiUrl:o,provider:"github"};throw new n(`${r.origin} does not answer as GitHub or Gitea. Testkit supports GitHub and Gitea.`)}
@@ -1,5 +1,5 @@
1
- import{startManagedHostProcess as a}from"../../process/index.js";import{createManagedGitEnvironment as l,DEFAULT_GIT_COMMAND_TIMEOUT_MS as u}from"../../repo/managed-git.js";const d=["GITHUB_TOKEN","GH_TOKEN"],T=["GITEA_TOKEN"],A=["GITEA_TOKEN","GITHUB_TOKEN"],E=new Set(["GIT_ASKPASS","SSH_ASKPASS"]),i="password=";export function trackerTokenVariables(e,t){return e==="github"?d:t?A:T}export function trackerTokenFromEnvironment(e,t){for(const n of e){const r=t[n]?.trim();if(r)return r}return null}export function gitCredentialRequest(e){const t=new URL(e.origin);return`protocol=${t.protocol.slice(0,-1)}
1
+ import{startManagedHostProcess as c}from"../../process/index.js";import{createManagedGitEnvironment as l,DEFAULT_GIT_COMMAND_TIMEOUT_MS as u}from"../../repo/managed-git.js";const d=["GITHUB_TOKEN","GH_TOKEN"],T=["GITEA_TOKEN"],E=["GITEA_TOKEN","GITHUB_TOKEN"],A=new Set(["GIT_ASKPASS","SSH_ASKPASS"]),i="password=";export function trackerTokenVariables(e,t,n,r){return e==="github"?d:t?E:f(r)===n?T:[]}export function missingTrackerTokenMessage(e,t){const n=t.length>0?t.join(" or "):`GITEA_TOKEN and GITEA_SERVER_URL=${e}`;return`Testkit found no token for ${e}. Set ${n}, or give Git a credential helper that holds one for that host.`}function f(e){const t=e.GITEA_SERVER_URL?.trim()??"";if(!URL.canParse(t))return null;const n=new URL(t);return n.username===""&&n.password===""?n.origin:null}export function trackerTokenFromEnvironment(e,t){for(const n of e){const r=t[n]?.trim();if(r)return r}return null}export function gitCredentialRequest(e){const t=new URL(e.origin);return`protocol=${t.protocol.slice(0,-1)}
2
2
  host=${t.host}
3
3
  path=${e.credentialPath}
4
4
 
5
- `}export function passwordFromGitCredential(e){const t=e.split(/\r?\n/u).find(n=>n.startsWith(i));return t===void 0||t.length===i.length?null:t.slice(i.length)}export async function readGitCredentialToken(e,t,n){const r=Object.fromEntries(Object.entries(n).filter(([c])=>!E.has(c.toUpperCase())));let o;try{o=a({args:["-c","core.askPass=","credential","fill"],cwd:e,displayLabel:"Git credential fill",env:{...l(r),GCM_INTERACTIVE:"never",GIT_TERMINAL_PROMPT:"0"},executable:"git",outputMode:"bounded-capture",recoveryIdentity:"none",timeoutMs:u})}catch{return null}o.stdin.on("error",()=>{}),o.stdin.end(gitCredentialRequest(t));const s=await o.completion;return s.exitCode===0?passwordFromGitCredential(s.stdout):null}
5
+ `}export function passwordFromGitCredential(e){const t=e.split(/\r?\n/u).find(n=>n.startsWith(i));return t===void 0||t.length===i.length?null:t.slice(i.length)}export async function readGitCredentialToken(e,t,n){const r=Object.fromEntries(Object.entries(n).filter(([a])=>!A.has(a.toUpperCase())));let o;try{o=c({args:["-c","core.askPass=","credential","fill"],cwd:e,displayLabel:"Git credential fill",env:{...l(r),GCM_INTERACTIVE:"never",GIT_TERMINAL_PROMPT:"0"},executable:"git",outputMode:"bounded-capture",recoveryIdentity:"none",timeoutMs:u})}catch{return null}o.stdin.on("error",()=>{}),o.stdin.end(gitCredentialRequest(t));const s=await o.completion;return s.exitCode===0?passwordFromGitCredential(s.stdout):null}
@@ -1,4 +1,4 @@
1
- import{attachErrorDiagnostics as F}from"../../kernel/error-serialization.js";import{isPathWithin as b}from"../../process/files/paths.js";import{resolveProductDir as _}from"../../process/files/product-dir.js";import f from"fs";import i from"path";import{createHash as M}from"crypto";import{createRequire as O}from"module";import{fileURLToPath as A}from"url";import{resolveVitestSelection as R}from"../../engines/catalog.js";import{NODE_VITEST_PACKAGE_REQUIREMENTS as I}from"../../engines/installations/package-requirements.js";import{PLAYWRIGHT_PACKAGE_FAMILY_REQUIREMENTS as N}from"../../engines/installations/package-requirements.js";import{hasDiscoveryErrors as D,buildDiscoveryErrorFromDiagnostics as G,detectNextApp as L,loadDiscoveredConfigContext as U}from"../../config/loader/index.js";import{managedHostProcessInfrastructureError as J,runManagedHostProcess as z}from"../../process/index.js";import{findNearestPlaywrightConfigFile as B}from"../../engines/playwright/runtime/config-file.js";import{isUnknownRecord as x}from"../../kernel/unknown-values.js";const j=/\.[cm]?tsx?$/u,K=600*1e3,k=i.resolve(i.dirname(A(import.meta.url)),"..","..",".."),v=O(i.join(k,"package.json")),P=()=>v("typescript");export class TestkitTypecheckError extends Error{result;results;constructor(t,n,s){super(`testkit typecheck failed for ${t}`),this.result=n,this.results=s,F(this,{result:n,results:s})}}export async function runTestkitTypecheck(e={}){T(e.timeoutMs);const t=_(process.cwd(),e.dir),n=R(e.vitest??{},t),{context:s,discovery:o}=await U({dir:t,group:e.group,vitest:n});return runTestkitTypecheckForContext(s,o,e)}export async function runTestkitTypecheckForContext(e,t,n={}){const s=T(n.timeoutMs);if(D(e.discovery.diagnostics))throw G(e.discovery.diagnostics);const o=e.productDir,r=i.join(o,".testkit","_typecheck");f.mkdirSync(r,{recursive:!0});const c=[];if(e.configFile){const p=q({productDir:o,configFile:e.configFile,outputDir:r});c.push({label:"config",cwd:o,tsconfigPath:p})}const u=Y(e),l=t.files.filter(p=>j.test(p.path)).flatMap(p=>(u.get(p.path)??[[]]).map(g=>({engineId:p.engineId,path:p.path,setupFiles:g}))),a=new Set(t.files.filter(p=>p.engineId==="playwright").map(p=>p.group));for(const p of e.groups){if(!a.has(p.name))continue;const g=B(o,p.roots);g&&l.push({engineId:"playwright",path:g,setupFiles:[]})}const m=W(o,l);for(const p of m){const g=H({...p,outputDir:r,productDir:o});c.push({label:Z(p,o),cwd:p.cwd,tsconfigPath:g})}const y=[];for(const p of c){const g=await te(p,s,n.signal);if(y.push(g),!g.ok)throw new TestkitTypecheckError(p.label,g,y)}return{ok:!0,productDir:o,results:y}}function q({productDir:e,configFile:t,outputDir:n}){const s=i.join(n,"config.tsconfig.json"),o=S(e),r={...o?{extends:d(s,o)}:{},compilerOptions:buildTypecheckCompilerOptions({tsconfigPath:s,extendsPath:o,libs:["ES2022"],rootDir:d(s,e),types:["node"]}),files:[d(s,t)],include:[],exclude:[h(s,e,"node_modules"),h(s,e,"dist"),h(s,e,".testkit")]};return f.writeFileSync(s,`${JSON.stringify(r,null,2)}
2
- `),s}function H({productDir:e,cwd:t,engineId:n,extendsPath:s,files:o,outputDir:r,scopeName:c,setupFiles:u}){const l=u.length===0?"":`.setup-${w(u)}`,a=i.join(r,`${n}.${ee(c)}${l}.tsconfig.json`),m=n==="playwright",y=m&&L(t),p={...s?{extends:d(a,s)}:{},compilerOptions:{...buildTypecheckCompilerOptions({engineId:n,tsconfigPath:a,extendsPath:s,libs:Q(n,s),...y?{extraPaths:nextRuntimeTypePaths({tsconfigPath:a,serviceDir:t})}:{},types:["node"]}),...m?{allowJs:!0,checkJs:!1}:{}},files:o.map(g=>d(a,g)),include:y?[...f.existsSync(i.join(t,"next-env.d.ts"))?[d(a,i.join(t,"next-env.d.ts"))]:[],h(a,t,"tests/playwright-fixtures/**/*.ts"),h(a,t,"tests/playwright-fixtures/**/*.tsx"),h(a,t,"tests/playwright-fixtures/**/*.mts"),h(a,t,".next/types/**/*.ts"),h(a,t,".next/dev/types/**/*.ts"),h(a,e,".next-testkit/**/dist/types/**/*.ts"),h(a,e,".next-testkit/**/dist/dev/types/**/*.ts")]:[],exclude:["node_modules",".next/cache",".next/dev"]};return f.writeFileSync(a,`${JSON.stringify(p,null,2)}
3
- `),a}function W(e,t){const n=new Map,s=new Map;for(const o of t){const r=i.resolve(e,o.path),c=[o.engineId,r,...o.setupFiles].join("\0");s.set(c,{...o,path:r})}for(const o of s.values()){const r=o.path,c=o.engineId==="playwright"?C(i.dirname(r),e,["tsconfig.playwright.json"]):null,u=c??C(i.dirname(r),e),l=u?i.dirname(u):e,a=X(e,l),m=[o.engineId,u??"",a,...o.setupFiles].join("\0"),y=n.get(m);if(y){y.files.push(r);continue}n.set(m,{cwd:l,engineId:o.engineId,extendsPath:u,files:[r,...o.setupFiles,...c?V(c):[]],scopeName:a,setupFiles:o.setupFiles})}return[...n.values()].map(o=>({...o,files:[...new Set(o.files)].sort((r,c)=>r.localeCompare(c))})).sort((o,r)=>E(o.engineId)-E(r.engineId)||o.scopeName.localeCompare(r.scopeName)||o.setupFiles.join("\0").localeCompare(r.setupFiles.join("\0")))}function Y(e){const t=new Map;for(const{file:n,setupFiles:s}of e.plainFiles){const o=[...new Set(s.filter(c=>j.test(c)))].sort((c,u)=>c.localeCompare(u)),r=t.get(n)??[];r.some(c=>c.join("\0")===o.join("\0"))||r.push(o),t.set(n,r)}return t}function V(e){const t=P(),n=o=>{throw new Error(`${e}: ${t.flattenDiagnosticMessageText(o.messageText,`
4
- `)}`)},s=t.getParsedCommandLineOfConfigFile(e,{},{...t.sys,onUnRecoverableConfigFileDiagnostic:n});for(const o of s?.errors??[])o.code!==18002&&o.code!==18003&&n(o);return s?.fileNames??[]}function Q(e,t){return e==="playwright"?["ES2022","DOM","DOM.Iterable"]:t?void 0:["ES2022"]}function E(e){return["node-vitest","playwright"].indexOf(e)}function Z({engineId:e,scopeName:t,setupFiles:n},s){const o=t==="repo"?e:`${e}:${t}`;if(n.length===0)return o;const r=n.map(c=>i.relative(s,c).split(i.sep).join("/"));return`${o} with ${r.join(", ")}`}function X(e,t){const n=i.relative(e,t);return n===""?"repo":n.split(i.sep).join("/")}function ee(e){const t=e.replace(/[^a-zA-Z0-9_-]+/g,"-");return t===e?e:`${t}-${w([e])}`}function w(e){return M("sha256").update(e.join("\0")).digest("hex").slice(0,12)}function te({label:e,cwd:t,tsconfigPath:n},s,o){const r=ne(t);return z({args:[...r.args,"--noEmit","-p",n],cwd:t,displayLabel:`Testkit typecheck ${e}`,env:process.env,executable:r.command,signal:o,timeoutMs:s}).then(c=>{const u=J(`Testkit typecheck ${e}`,c);if(u)throw u;return{label:e,tsconfigPath:n,ok:c.exitCode===0,exitCode:c.exitCode,stdout:c.stdout,stderr:c.stderr}})}function T(e){const t=e??K;if(!Number.isSafeInteger(t)||t<=0)throw new Error("Testkit typecheck timeout must be a positive safe integer in milliseconds");return t}function ne(e){let t=e;for(;;){const s=i.join(t,"node_modules","typescript","bin","tsc");if(f.existsSync(s))return{command:process.execPath,args:[s]};const o=i.dirname(t);if(o===t)break;t=o}const n=i.join(k,"node_modules","typescript","bin","tsc");if(f.existsSync(n))return{command:process.execPath,args:[n]};throw new Error(`TypeScript compiler not found from ${e} upward`)}function S(e,t=["tsconfig.json","tsconfig.build.json"]){for(const n of t){const s=i.join(e,n);if(f.existsSync(s))return s}return null}function C(e,t,n){const s=i.resolve(t);let o=i.resolve(e);for(;;){if(!b(s,o))return null;const r=S(o,n);if(r)return r;if(o===s)return null;const c=i.dirname(o);if(c===o)return null;o=c}}function d(e,t){const n=i.relative(i.dirname(e),t).split(i.sep).join("/");return n.startsWith("../")||i.isAbsolute(n)?n:`./${n}`}function h(e,t,n){return i.join(d(e,t),n).split(i.sep).join("/")}function oe(e){const t={};for(const[n,s]of Object.entries($("@gfdelarue/testkit",k)))!x(s)||typeof s.types!="string"||(t[`@gfdelarue/testkit${n.slice(1)}`]=[d(e,i.join(k,s.types))]);return t}const se=Object.freeze({packages:Object.freeze(I.filter(({authoringResolution:e})=>e==="package-exports").map(({packageName:e})=>Object.freeze({name:e}))),supportedExports:"exact-manifest-exports"}),re=Object.freeze({packages:Object.freeze(N.map(({packageName:e})=>Object.freeze({name:e}))),supportedExports:"exact-manifest-exports"});function ie(e,t){const n=ce(e);if(n===null)return{};const s={},o=d(t,i.join(k,"dist","app","typecheck","unavailable.d.ts"));for(const r of n.packages){const c=ae(r.name),u=$(r.name,c);s[`${r.name}/*`]=[o];for(const l of Object.keys(u).sort()){if(l!=="."&&!l.startsWith("./")||l.includes("*"))continue;const a=l==="."?r.name:`${r.name}${l.slice(1)}`,m=ue(a,r.name,c);s[a]=[m===null?o:d(t,m)]}}return s}function ce(e){switch(e){case"node-vitest":return se;case"playwright":return re;case void 0:return null}}function ae(e){const t=pe(e);let n;try{n=v.resolve(`${e}/package.json`)}catch(r){throw new Error(`Testkit typecheck requires managed public package ${e}`,{cause:r})}const s=f.realpathSync.native(i.dirname(n)),o=JSON.parse(f.readFileSync(i.join(s,"package.json"),"utf8"));return requireManagedPublicPackageIdentity(o,e,t),s}export function requireManagedPublicPackageIdentity(e,t,n){if(!x(e)||e.name!==t||e.version!==n)throw new Error(`Testkit managed public package has invalid identity: expected ${t}@${n}`)}function pe(e){const t=JSON.parse(f.readFileSync(i.join(k,"package.json"),"utf8"));if(!x(t)||!x(t.dependencies))throw new Error("Testkit package has invalid dependency metadata");const n=t.dependencies[e];if(typeof n!="string"||!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u.test(n))throw new Error(`Testkit managed public package ${e} must be an exact root dependency`);return n}function $(e,t){const n=JSON.parse(f.readFileSync(i.join(t,"package.json"),"utf8"));if(typeof n!="object"||n===null||Array.isArray(n)||!("name"in n)||n.name!==e||!("exports"in n)||typeof n.exports!="object"||n.exports===null||Array.isArray(n.exports))throw new Error(`Testkit managed public package has invalid exports: ${e}`);return n.exports}function ue(e,t,n){const s=P(),o=s.resolveModuleName(e,i.join(k,"__testkit_managed_public_types__.ts"),{module:s.ModuleKind.ESNext,moduleResolution:s.ModuleResolutionKind.Bundler,resolveJsonModule:!0,target:s.ScriptTarget.ES2022},s.sys).resolvedModule;if(o===void 0)return null;const r=f.realpathSync.native(o.resolvedFileName);if(!b(n,r))throw new Error(`Managed public package type ${e} resolved outside ${t}`);return r}export function buildTypecheckCompilerOptions({engineId:e,tsconfigPath:t,extendsPath:n,rootDir:s,extraPaths:o={},libs:r,types:c}){const l={...n?de({tsconfigPath:t,extendsPath:n}):{},...o,...oe(t),...ie(e,t)};return{baseUrl:null,...r?{lib:[...r]}:{},module:"ESNext",moduleResolution:"Bundler",noEmit:!0,...s?{rootDir:s}:{},target:"ES2022",typeRoots:le(c,t),types:[...c],paths:l}}function le(e,t){const n=new Set;for(const s of e){const o=fe(s);let r;try{r=v.resolve(`${o}/package.json`)}catch(u){throw new Error(`Testkit typecheck requires installed type package ${o}`,{cause:u})}const c=i.dirname(r);n.add(i.dirname(c))}return[...n].map(s=>d(t,s))}function fe(e){return e==="node"?`@types/${e}`:e}function de({tsconfigPath:e,extendsPath:t}){const n=P(),s=n.getParsedCommandLineOfConfigFile(t,{},{...n.sys,onUnRecoverableConfigFileDiagnostic:()=>{}}),o=s?.options.paths;if(!o||Object.keys(o).length===0)return{};const r=s.options.baseUrl||(typeof s.options.pathsBasePath=="string"?s.options.pathsBasePath:i.dirname(t)),c={};for(const[u,l]of Object.entries(o))c[u]=l.map(a=>d(e,i.resolve(r,a)));return c}export function nextRuntimeTypePaths({tsconfigPath:e,serviceDir:t}){const n=ge(t,[i.join("next","types.d.ts"),i.join("next","types.js")]);if(!n)return{};const s=d(e,n);return{"next/types":[s],"next/types.js":[s]}}function ge(e,t){let n=i.resolve(e);for(;;){for(const o of t){const r=i.join(n,"node_modules",o);if(f.existsSync(r))return r}const s=i.dirname(n);if(s===n)return null;n=s}}
1
+ import{attachErrorDiagnostics as _}from"../../kernel/error-serialization.js";import{isPathWithin as P}from"../../process/files/paths.js";import{resolveProductDir as M}from"../../process/files/product-dir.js";import d from"fs";import i from"path";import{createHash as O}from"crypto";import{createRequire as A}from"module";import{fileURLToPath as R}from"url";import{resolveVitestSelection as I}from"../../engines/catalog.js";import{NODE_VITEST_PACKAGE_REQUIREMENTS as N}from"../../engines/installations/package-requirements.js";import{PLAYWRIGHT_PACKAGE_FAMILY_REQUIREMENTS as D}from"../../engines/installations/package-requirements.js";import{hasDiscoveryErrors as G,buildDiscoveryErrorFromDiagnostics as L,detectNextApp as U,loadDiscoveredConfigContext as J}from"../../config/loader/index.js";import{managedHostProcessInfrastructureError as z,runManagedHostProcess as B}from"../../process/index.js";import{findNearestPlaywrightConfigFile as K}from"../../engines/playwright/runtime/config-file.js";import{isUnknownRecord as x}from"../../kernel/unknown-values.js";const j=/\.[cm]?tsx?$/u,q=600*1e3,k=i.resolve(i.dirname(R(import.meta.url)),"..","..",".."),v=A(i.join(k,"package.json")),b=()=>v("typescript");export class TestkitTypecheckError extends Error{result;results;constructor(n,t,s){super(`testkit typecheck failed for ${n}`),this.result=t,this.results=s,_(this,{result:t,results:s})}}export async function runTestkitTypecheck(e={}){T(e.timeoutMs);const n=M(process.cwd(),e.dir),t=I(e.vitest??{},n),{context:s,discovery:o}=await J({dir:n,group:e.group,vitest:t});return runTestkitTypecheckForContext(s,o,e)}export async function runTestkitTypecheckForContext(e,n,t={}){const s=T(t.timeoutMs);if(G(e.discovery.diagnostics))throw L(e.discovery.diagnostics);const o=e.productDir,r=i.join(o,".testkit","_typecheck");d.mkdirSync(r,{recursive:!0});const c=[];if(e.configFile){const a=H({productDir:o,configFile:e.configFile,outputDir:r});c.push({label:"config",cwd:o,tsconfigPath:a})}const l=V(e),u=n.files.filter(a=>j.test(a.path)).flatMap(a=>(l.get(a.path)??[[]]).map(g=>({engineId:a.engineId,path:a.path,setupFiles:g}))),m=new Set(n.files.filter(a=>a.engineId==="playwright").map(a=>a.group));for(const a of e.groups){if(!m.has(a.name))continue;const g=K(o,a.roots);g&&u.push({engineId:"playwright",path:g,setupFiles:[]})}const p=Y(o,u);for(const a of p){const g=W({...a,outputDir:r,productDir:o});c.push({label:X(a,o),cwd:a.cwd,tsconfigPath:g})}const y=[];for(const a of c){const g=await ne(a,s,t.signal);if(y.push(g),!g.ok)throw new TestkitTypecheckError(a.label,g,y)}return{ok:!0,productDir:o,results:y}}function H({productDir:e,configFile:n,outputDir:t}){const s=i.join(t,"config.tsconfig.json"),o=S(e),r={...o?{extends:f(s,o)}:{},compilerOptions:buildTypecheckCompilerOptions({tsconfigPath:s,extendsPath:o,libs:["ES2022"],rootDir:f(s,e),types:["node"]}),files:[f(s,n)],include:[],exclude:[h(s,e,"node_modules"),h(s,e,"dist"),h(s,e,".testkit")]};return d.writeFileSync(s,`${JSON.stringify(r,null,2)}
2
+ `),s}function W({productDir:e,companion:n,cwd:t,engineId:s,extendsPath:o,files:r,outputDir:c,scopeName:l,setupFiles:u}){const m=u.length===0?"":`.setup-${w(u)}`,p=i.join(c,`${s}.${te(l)}${m}.tsconfig.json`),y=s==="playwright",a=y&&U(t),g={...o?{extends:f(p,o)}:{},compilerOptions:{...buildTypecheckCompilerOptions({engineId:s,tsconfigPath:p,extendsPath:o,libs:Z(s,o),...a?{extraPaths:nextRuntimeTypePaths({tsconfigPath:p,serviceDir:t})}:{},types:["node"]}),...y?{allowJs:!0,checkJs:!1}:{}},files:r.map(C=>f(p,C)),include:a&&!n?[...d.existsSync(i.join(t,"next-env.d.ts"))?[f(p,i.join(t,"next-env.d.ts"))]:[],h(p,t,"tests/playwright-fixtures/**/*.ts"),h(p,t,"tests/playwright-fixtures/**/*.tsx"),h(p,t,"tests/playwright-fixtures/**/*.mts"),h(p,t,".next/types/**/*.ts"),h(p,t,".next/dev/types/**/*.ts"),h(p,e,".next-testkit/**/dist/types/**/*.ts"),h(p,e,".next-testkit/**/dist/dev/types/**/*.ts")]:[],exclude:["node_modules",".next/cache",".next/dev"]};return d.writeFileSync(p,`${JSON.stringify(g,null,2)}
3
+ `),p}function Y(e,n){const t=new Map,s=new Map;for(const o of n){const r=i.resolve(e,o.path),c=[o.engineId,r,...o.setupFiles].join("\0");s.set(c,{...o,path:r})}for(const o of s.values()){const r=o.path,c=o.engineId==="playwright"?F(i.dirname(r),e,["tsconfig.playwright.json"]):null,l=c??F(i.dirname(r),e),u=l?i.dirname(l):e,m=ee(e,u),p=[o.engineId,l??"",m,...o.setupFiles].join("\0"),y=t.get(p);if(y){y.files.push(r);continue}t.set(p,{companion:c!==null,cwd:u,engineId:o.engineId,extendsPath:l,files:[r,...o.setupFiles,...c?Q(c):[]],scopeName:m,setupFiles:o.setupFiles})}return[...t.values()].map(o=>({...o,files:[...new Set(o.files)].sort((r,c)=>r.localeCompare(c))})).sort((o,r)=>E(o.engineId)-E(r.engineId)||o.scopeName.localeCompare(r.scopeName)||o.setupFiles.join("\0").localeCompare(r.setupFiles.join("\0")))}function V(e){const n=new Map;for(const{file:t,setupFiles:s}of e.plainFiles){const o=[...new Set(s.filter(c=>j.test(c)))].sort((c,l)=>c.localeCompare(l)),r=n.get(t)??[];r.some(c=>c.join("\0")===o.join("\0"))||r.push(o),n.set(t,r)}return n}function Q(e){const n=b(),t=o=>{throw new Error(`${e}: ${n.flattenDiagnosticMessageText(o.messageText,`
4
+ `)}`)},s=n.getParsedCommandLineOfConfigFile(e,{},{...n.sys,onUnRecoverableConfigFileDiagnostic:t});for(const o of s?.errors??[])o.code!==18002&&o.code!==18003&&t(o);return s?.fileNames??[]}function Z(e,n){return e==="playwright"?["ES2022","DOM","DOM.Iterable"]:n?void 0:["ES2022"]}function E(e){return["node-vitest","playwright"].indexOf(e)}function X({engineId:e,scopeName:n,setupFiles:t},s){const o=n==="repo"?e:`${e}:${n}`;if(t.length===0)return o;const r=t.map(c=>i.relative(s,c).split(i.sep).join("/"));return`${o} with ${r.join(", ")}`}function ee(e,n){const t=i.relative(e,n);return t===""?"repo":t.split(i.sep).join("/")}function te(e){const n=e.replace(/[^a-zA-Z0-9_-]+/g,"-");return n===e?e:`${n}-${w([e])}`}function w(e){return O("sha256").update(e.join("\0")).digest("hex").slice(0,12)}function ne({label:e,cwd:n,tsconfigPath:t},s,o){const r=oe(n);return B({args:[...r.args,"--noEmit","-p",t],cwd:n,displayLabel:`Testkit typecheck ${e}`,env:process.env,executable:r.command,signal:o,timeoutMs:s}).then(c=>{const l=z(`Testkit typecheck ${e}`,c);if(l)throw l;return{label:e,tsconfigPath:t,ok:c.exitCode===0,exitCode:c.exitCode,stdout:c.stdout,stderr:c.stderr}})}function T(e){const n=e??q;if(!Number.isSafeInteger(n)||n<=0)throw new Error("Testkit typecheck timeout must be a positive safe integer in milliseconds");return n}function oe(e){let n=e;for(;;){const s=i.join(n,"node_modules","typescript","bin","tsc");if(d.existsSync(s))return{command:process.execPath,args:[s]};const o=i.dirname(n);if(o===n)break;n=o}const t=i.join(k,"node_modules","typescript","bin","tsc");if(d.existsSync(t))return{command:process.execPath,args:[t]};throw new Error(`TypeScript compiler not found from ${e} upward`)}function S(e,n=["tsconfig.json","tsconfig.build.json"]){for(const t of n){const s=i.join(e,t);if(d.existsSync(s))return s}return null}function F(e,n,t){const s=i.resolve(n);let o=i.resolve(e);for(;;){if(!P(s,o))return null;const r=S(o,t);if(r)return r;if(o===s)return null;const c=i.dirname(o);if(c===o)return null;o=c}}function f(e,n){const t=i.relative(i.dirname(e),n).split(i.sep).join("/");return t.startsWith("../")||i.isAbsolute(t)?t:`./${t}`}function h(e,n,t){return i.join(f(e,n),t).split(i.sep).join("/")}function se(e){const n={};for(const[t,s]of Object.entries($("@gfdelarue/testkit",k)))!x(s)||typeof s.types!="string"||(n[`@gfdelarue/testkit${t.slice(1)}`]=[f(e,i.join(k,s.types))]);return n}const re=Object.freeze({packages:Object.freeze(N.filter(({authoringResolution:e})=>e==="package-exports").map(({packageName:e})=>Object.freeze({name:e}))),supportedExports:"exact-manifest-exports"}),ie=Object.freeze({packages:Object.freeze(D.map(({packageName:e})=>Object.freeze({name:e}))),supportedExports:"exact-manifest-exports"});function ce(e,n){const t=ae(e);if(t===null)return{};const s={},o=f(n,i.join(k,"dist","app","typecheck","unavailable.d.ts"));for(const r of t.packages){const c=pe(r.name),l=$(r.name,c);s[`${r.name}/*`]=[o];for(const u of Object.keys(l).sort()){if(u!=="."&&!u.startsWith("./")||u.includes("*"))continue;const m=u==="."?r.name:`${r.name}${u.slice(1)}`,p=ue(m,r.name,c);s[m]=[p===null?o:f(n,p)]}}return s}function ae(e){switch(e){case"node-vitest":return re;case"playwright":return ie;case void 0:return null}}function pe(e){const n=le(e);let t;try{t=v.resolve(`${e}/package.json`)}catch(r){throw new Error(`Testkit typecheck requires managed public package ${e}`,{cause:r})}const s=d.realpathSync.native(i.dirname(t)),o=JSON.parse(d.readFileSync(i.join(s,"package.json"),"utf8"));return requireManagedPublicPackageIdentity(o,e,n),s}export function requireManagedPublicPackageIdentity(e,n,t){if(!x(e)||e.name!==n||e.version!==t)throw new Error(`Testkit managed public package has invalid identity: expected ${n}@${t}`)}function le(e){const n=JSON.parse(d.readFileSync(i.join(k,"package.json"),"utf8"));if(!x(n)||!x(n.dependencies))throw new Error("Testkit package has invalid dependency metadata");const t=n.dependencies[e];if(typeof t!="string"||!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u.test(t))throw new Error(`Testkit managed public package ${e} must be an exact root dependency`);return t}function $(e,n){const t=JSON.parse(d.readFileSync(i.join(n,"package.json"),"utf8"));if(typeof t!="object"||t===null||Array.isArray(t)||!("name"in t)||t.name!==e||!("exports"in t)||typeof t.exports!="object"||t.exports===null||Array.isArray(t.exports))throw new Error(`Testkit managed public package has invalid exports: ${e}`);return t.exports}function ue(e,n,t){const s=b(),o=s.resolveModuleName(e,i.join(k,"__testkit_managed_public_types__.ts"),{module:s.ModuleKind.ESNext,moduleResolution:s.ModuleResolutionKind.Bundler,resolveJsonModule:!0,target:s.ScriptTarget.ES2022},s.sys).resolvedModule;if(o===void 0)return null;const r=d.realpathSync.native(o.resolvedFileName);if(!P(t,r))throw new Error(`Managed public package type ${e} resolved outside ${n}`);return r}export function buildTypecheckCompilerOptions({engineId:e,tsconfigPath:n,extendsPath:t,rootDir:s,extraPaths:o={},libs:r,types:c}){const u={...t?ge({tsconfigPath:n,extendsPath:t}):{},...o,...se(n),...ce(e,n)};return{baseUrl:null,declarationDir:null,...r?{lib:[...r]}:{},mapRoot:null,module:"ESNext",moduleResolution:"Bundler",noEmit:!0,outDir:null,outFile:null,rootDir:s??null,sourceRoot:null,target:"ES2022",tsBuildInfoFile:f(n,n.replace(/\.json$/u,".tsbuildinfo")),typeRoots:fe(c,n),types:[...c],paths:u}}function fe(e,n){const t=new Set;for(const s of e){const o=de(s);let r;try{r=v.resolve(`${o}/package.json`)}catch(l){throw new Error(`Testkit typecheck requires installed type package ${o}`,{cause:l})}const c=i.dirname(r);t.add(i.dirname(c))}return[...t].map(s=>f(n,s))}function de(e){return e==="node"?`@types/${e}`:e}function ge({tsconfigPath:e,extendsPath:n}){const t=b(),s=t.getParsedCommandLineOfConfigFile(n,{},{...t.sys,onUnRecoverableConfigFileDiagnostic:()=>{}}),o=s?.options.paths;if(!o||Object.keys(o).length===0)return{};const r=s.options.baseUrl||(typeof s.options.pathsBasePath=="string"?s.options.pathsBasePath:i.dirname(n)),c={};for(const[l,u]of Object.entries(o))c[l]=u.map(m=>f(e,i.resolve(r,m)));return c}export function nextRuntimeTypePaths({tsconfigPath:e,serviceDir:n}){const t=me(n,[i.join("next","types.d.ts"),i.join("next","types.js")]);if(!t)return{};const s=f(e,t);return{"next/types":[s],"next/types.js":[s]}}function me(e,n){let t=i.resolve(e);for(;;){for(const o of n){const r=i.join(t,"node_modules",o);if(d.existsSync(r))return r}const s=i.dirname(t);if(s===t)return null;t=s}}
@@ -1 +1 @@
1
- import{Args as i,Flags as e}from"./framework/definitions.js";import{defineCommand as t}from"./framework/command.js";import{runArgs as n,runFlags as o,sharedFlags as r,suppliedEnvironmentFlags as l,testSelectionFlags as s,vitestSelectionFlags as a}from"./command-flags.js";import{publicTestTypeListText as u}from"../domain/test-types.js";export const commandCatalog=Object.freeze({artifacts:t({summary:"List, verify, or inspect artifacts from immutable Testkit runs",enableJsonFlag:!0,args:{file:i.string({description:"Optional test file selector",required:!1})},flags:{...s,kind:e.string({description:"Limit artifacts to one kind"}),format:e.string({description:"Output format",options:["list","text"],default:"list"}),run:e.string({description:"Read one immutable run ID instead of latest"}),list:e.boolean({description:"List retained immutable runs",default:!1}),verify:e.boolean({description:"Verify the selected run manifest and checksums",default:!1})}}),"bug file":t({summary:"Run a recorded bug's test and file the bug as an issue",enableJsonFlag:!0,args:{id:i.string({description:"Draft id that `testkit bug record` printed",required:!0})},flags:{dir:e.string({description:"Product directory"}),...l,...a}}),"bug guide":t({summary:"Print the bug record format, the severities, and the category lists",enableJsonFlag:!0}),"bug record":t({summary:"Record a confirmed bug as a local draft",enableJsonFlag:!0,args:{file:i.string({description:"Bug record JSON file, or - to read standard input",required:!0})},flags:{dir:e.string({description:"Product directory"}),...a}}),"campaign finish":t({summary:"Finish the active campaign and write its pull request summary",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"})}}),"campaign start":t({summary:"Start a bug-hunting campaign and print the agent's brief",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"}),id:e.string({description:"Campaign id, instead of one made from the target and the date"}),target:e.string({description:"What the agent should hunt, in one line of at most 200 characters",required:!0})}}),"campaign status":t({summary:"Show the active campaign, its recorded bugs, and the area map coverage",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"})}}),"ci report":t({summary:"Post the last run as a commit status, a step summary, and one pull request comment",enableJsonFlag:!0,flags:{context:e.string({description:"Commit status context, instead of testkit"}),dir:e.string({description:"Product directory"})}}),cleanup:t({summary:"Clean stale local testkit state",enableJsonFlag:!0,flags:{...r,"dry-run":e.boolean({description:"Show cleanup actions without deleting state",default:!1}),cache:e.string({description:"Clean cache state: runtime, bundles, or all",multiple:!0,options:["runtime","bundles","all"]}),resources:e.boolean({description:"Clean Testkit-owned Docker resources that are stale or orphaned",default:!1})}}),"db schema refresh":t({summary:"Refresh a source database schema cache",enableJsonFlag:!0,flags:r}),"db schema verify":t({summary:"Verify local schema replay against the source database schema",enableJsonFlag:!0,flags:{...r,"skip-schema-source-verify":e.boolean({description:"Skip the check that local schema replay matches the source database schema",default:!1})}}),destroy:t({summary:"Remove runtime state, databases, and Docker resources; run results and other .testkit files stay",enableJsonFlag:!0,flags:r}),discover:t({summary:"Discover managed tests and report their metadata",enableJsonFlag:!0,flags:{...s,...a,type:e.string({char:"t",multiple:!0,description:`Filter by suite type(s): ${u({includeAll:!0})}`}),suite:e.string({char:"s",multiple:!0,description:"Filter by suite selector(s)"}),file:e.string({char:"f",multiple:!0,description:"Filter by exact file path(s)"}),"runnable-only":e.boolean({description:"Show only files not skipped by repo rules",default:!1}),strict:e.boolean({description:"Fail instead of reporting discovery/config diagnostics",default:!1}),output:e.string({description:"Write machine-readable discovery JSON to a file"}),"output-mode":e.string({description:"Human-readable output mode",options:["compact","verbose"],default:"compact"})}}),doctor:t({summary:"Run built-in config, discovery, and hygiene checks",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"}),typecheck:e.boolean({description:"Include generated TypeScript checks",default:!0,allowNo:!0})}}),"guard unsafe-entrypoint":t({summary:"Fail a local script that must be run through Testkit",flags:{command:e.string({char:"c",description:"Disabled command name shown in the error message",required:!0}),use:e.string({char:"u",description:"Replacement command shown in the error message",required:!0})}}),lint:t({summary:"Run built-in Testkit repository hygiene checks",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"})}}),"report prepare":t({summary:"Prepare a local report with optional template, bug records, and selected run evidence",enableJsonFlag:!0,args:{directory:i.string({description:"New bundle directory",required:!0})},flags:{dir:e.string({description:"Product directory containing bug records and runs"}),template:e.string({description:"Copy this file unchanged to report.md"}),instructions:e.string({description:"Print this repository-owned drafting guide for the author"}),bug:e.string({description:"Include this recorded bug ID as context",multiple:!0}),run:e.string({description:"Immutable run ID containing the selected artifacts"}),artifact:e.string({description:"Exact artifact ref.path from testkit artifacts --json",multiple:!0})}}),"report publish":t({summary:"Publish a prepared directory at one Git revision without changing your checkout",enableJsonFlag:!0,args:{directory:i.string({description:"Directory containing only the files to publish",required:!0})},flags:{dir:e.string({description:"Product directory used to resolve provider credentials"}),repository:e.string({description:"Destination GitHub or Gitea repository URL, or owner/name",required:!0}),branch:e.string({description:"Destination branch",required:!0}),path:e.string({description:"Bundle directory within the destination repository",required:!0}),message:e.string({description:"Publication commit message"})}}),"run status":t({summary:"Show active Testkit run progress",enableJsonFlag:!0,flags:{...r,run:e.string({description:"Specific run id to show"}),full:e.boolean({description:"Include the full live status payload in JSON output"})}}),"run stop":t({summary:"Stop active Testkit run work",enableJsonFlag:!0,flags:{...r,run:e.string({description:"Specific run id to stop; defaults to the newest active run"}),all:e.boolean({description:"Stop all active runs for this product",default:!1}),reason:e.string({description:"Operator-visible reason recorded with cancelled runs and dirty slots"})}}),run:t({summary:"Run test suites",enableJsonFlag:!0,args:n,flags:o}),"setup playwright":t({summary:"Install the managed Playwright Chromium runtime",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"}),force:e.boolean({default:!1,description:"Force Playwright to repair the browser installation"}),"with-deps":e.boolean({default:!1,description:"Install required host dependencies with Chromium"})}}),status:t({summary:"Show local testkit state",enableJsonFlag:!0,flags:r}),stop:t({summary:"Stop all Testkit-owned runs",enableJsonFlag:!0,flags:{...r,reason:e.string({description:"Operator-visible reason recorded with cancelled runs"})}}),typecheck:t({summary:"Typecheck testkit config, helpers, and suites",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"}),group:s.group,...a}}),workspace:t({summary:"Inspect Testkit workstation data for a product",enableJsonFlag:!0,flags:{...s,"target-limit":e.integer({description:"Maximum number of quick-run target previews",default:24}),"no-status":e.boolean({description:"Skip runtime status collection",default:!1})}})});
1
+ import{Args as i,Flags as e}from"./framework/definitions.js";import{defineCommand as t}from"./framework/command.js";import{runArgs as o,runFlags as l,schemaOperationFlags as n,sharedFlags as r,suppliedEnvironmentFlags as u,testSelectionFlags as s,vitestSelectionFlags as a}from"./command-flags.js";import{publicTestTypeListText as c}from"../domain/test-types.js";export const commandCatalog=Object.freeze({artifacts:t({summary:"List, verify, or inspect artifacts from immutable Testkit runs",enableJsonFlag:!0,args:{file:i.string({description:"Optional test file selector",required:!1})},flags:{...s,kind:e.string({description:"Limit artifacts to one kind"}),format:e.string({description:"Output format",options:["list","text"],default:"list"}),run:e.string({description:"Read one immutable run ID instead of latest"}),list:e.boolean({description:"List retained immutable runs",default:!1}),verify:e.boolean({description:"Verify the selected run manifest and checksums",default:!1})}}),"bug file":t({summary:"Run a recorded bug's test and file the bug as an issue",enableJsonFlag:!0,args:{id:i.string({description:"Draft id that `testkit bug record` printed; with --batch, any number of them",multiple:!0})},flags:{batch:e.boolean({description:"File the named drafts, or every unfiled draft when none is named, and report each one",default:!1}),dir:e.string({description:"Product directory"}),...u,...a}}),"bug guide":t({summary:"Print the bug record format, the severities, and the category lists",enableJsonFlag:!0}),"bug record":t({summary:"Record a confirmed bug as a local draft",enableJsonFlag:!0,args:{file:i.string({description:"Bug record JSON file, or - to read standard input",required:!0})},flags:{dir:e.string({description:"Product directory"}),...a}}),"campaign finish":t({summary:"Finish the active campaign and write its pull request summary",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"})}}),"campaign start":t({summary:"Start a bug-hunting campaign and print the agent's brief",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"}),id:e.string({description:"Campaign id, instead of one made from the target and the date"}),target:e.string({description:"What the agent should hunt, in one line of at most 200 characters",required:!0})}}),"campaign status":t({summary:"Show the active campaign, its recorded bugs, and the area map coverage",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"})}}),"ci report":t({summary:"Post the last run as a commit status, a step summary, and one pull request comment",enableJsonFlag:!0,flags:{context:e.string({description:"Commit status context, instead of testkit"}),dir:e.string({description:"Product directory"})}}),cleanup:t({summary:"Clean stale local testkit state",enableJsonFlag:!0,flags:{...r,"dry-run":e.boolean({description:"Show cleanup actions without deleting state",default:!1}),cache:e.string({description:"Clean cache state: runtime, bundles, or all",multiple:!0,options:["runtime","bundles","all"]}),resources:e.boolean({description:"Clean Testkit-owned Docker resources that are stale or orphaned",default:!1})}}),"db schema refresh":t({summary:"Refresh a source database schema cache",enableJsonFlag:!0,flags:{...r,...n}}),"db schema verify":t({summary:"Verify local schema replay against the source database schema",enableJsonFlag:!0,flags:{...r,...n,"skip-schema-source-verify":e.boolean({description:"Skip the check that local schema replay matches the source database schema",default:!1})}}),destroy:t({summary:"Remove runtime state, databases, and Docker resources; run results and other .testkit files stay",enableJsonFlag:!0,flags:r}),discover:t({summary:"Discover managed tests and report their metadata",enableJsonFlag:!0,flags:{...s,...a,type:e.string({char:"t",multiple:!0,description:`Filter by suite type(s): ${c({includeAll:!0})}`}),suite:e.string({char:"s",multiple:!0,description:"Filter by suite selector(s)"}),file:e.string({char:"f",multiple:!0,description:"Filter by exact file path(s)"}),"runnable-only":e.boolean({description:"Show only files not skipped by repo rules",default:!1}),strict:e.boolean({description:"Fail instead of reporting discovery/config diagnostics",default:!1}),output:e.string({description:"Write machine-readable discovery JSON to a file"}),"output-mode":e.string({description:"Human-readable output mode",options:["compact","verbose"],default:"compact"})}}),doctor:t({summary:"Run built-in config, discovery, and hygiene checks",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"}),typecheck:e.boolean({description:"Include generated TypeScript checks",default:!0,allowNo:!0})}}),"guard unsafe-entrypoint":t({summary:"Fail a local script that must be run through Testkit",flags:{command:e.string({char:"c",description:"Disabled command name shown in the error message",required:!0}),use:e.string({char:"u",description:"Replacement command shown in the error message",required:!0})}}),lint:t({summary:"Run built-in Testkit repository hygiene checks",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"})}}),"report prepare":t({summary:"Prepare a local report with optional template, bug records, and selected run evidence",enableJsonFlag:!0,args:{directory:i.string({description:"New bundle directory",required:!0})},flags:{dir:e.string({description:"Product directory containing bug records and runs"}),template:e.string({description:"Copy this file unchanged to report.md"}),instructions:e.string({description:"Print this repository-owned drafting guide for the author"}),bug:e.string({description:"Include this recorded bug ID as context",multiple:!0}),run:e.string({description:"Immutable run ID containing the selected artifacts"}),artifact:e.string({description:"Exact artifact ref.path from testkit artifacts --json",multiple:!0})}}),"report publish":t({summary:"Publish a prepared directory at one Git revision without changing your checkout",enableJsonFlag:!0,args:{directory:i.string({description:"Directory containing only the files to publish",required:!0})},flags:{dir:e.string({description:"Product directory used to resolve provider credentials"}),repository:e.string({description:"Destination GitHub or Gitea repository URL, or owner/name",required:!0}),branch:e.string({description:"Destination branch",required:!0}),path:e.string({description:"Bundle directory within the destination repository",required:!0}),message:e.string({description:"Publication commit message"})}}),"run status":t({summary:"Show active Testkit run progress",enableJsonFlag:!0,flags:{...r,run:e.string({description:"Specific run id to show"}),full:e.boolean({description:"Include the full live status payload in JSON output"})}}),"run stop":t({summary:"Stop active Testkit run work",enableJsonFlag:!0,flags:{...r,run:e.string({description:"Specific run id to stop; defaults to the newest active run"}),all:e.boolean({description:"Stop all active runs for this product",default:!1}),reason:e.string({description:"Operator-visible reason recorded with cancelled runs and dirty slots"})}}),run:t({summary:"Run test suites",enableJsonFlag:!0,args:o,flags:l}),"setup playwright":t({summary:"Install the managed Playwright Chromium runtime",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"}),force:e.boolean({default:!1,description:"Force Playwright to repair the browser installation"}),"with-deps":e.boolean({default:!1,description:"Install required host dependencies with Chromium"})}}),status:t({summary:"Show local testkit state",enableJsonFlag:!0,flags:r}),stop:t({summary:"Stop all Testkit-owned runs",enableJsonFlag:!0,flags:{...r,reason:e.string({description:"Operator-visible reason recorded with cancelled runs"})}}),typecheck:t({summary:"Typecheck testkit config, helpers, and suites",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"}),group:s.group,...a}}),workspace:t({summary:"Inspect Testkit workstation data for a product",enableJsonFlag:!0,flags:{...s,"target-limit":e.integer({description:"Maximum number of quick-run target previews",default:24}),"no-status":e.boolean({description:"Skip runtime status collection",default:!1})}})});
@@ -1 +1 @@
1
- import{RETIRED_TEST_TYPES as s,rejectRetiredTestType as n}from"../domain/retired-test-types.js";import{publicTestTypeList as o,publicTestTypeListText as r}from"../domain/test-types.js";import{CliUsageError as p}from"./framework/errors.js";import{Args as c,Flags as e}from"./framework/index.js";export const sharedFlags={dir:e.string({description:"Explicit product directory"}),service:e.string({description:"Limit the operation to one service"})},testSelectionFlags={dir:sharedFlags.dir,group:e.string({description:"Limit the operation to one test group"}),service:e.string({description:"Removed in 0.6.0; use --group"})};export function selectedGroup(t){if(t.service!==void 0)throw new p(`Testkit 0.6.0 removed --service from run, discover, artifacts, and workspace; use --group ${t.service}`);return t.group??null}export const vitestSelectionFlags={"vitest-config":e.string({description:"Run plain test files with this Vitest config, relative to the product directory"}),"vitest-project":e.string({description:"Run plain test files from this Vitest project only"}),"vitest-runner":e.string({description:"Run plain test files with the repository's Vitest (repo) or Testkit's own (managed)",options:["repo","managed"]})},suppliedEnvironmentFlags={"base-url":e.string({description:"Supply the default endpoint, BASE_URL, instead of provisioning it"}),endpoint:e.string({multiple:!0,description:"Supply a named endpoint as NAME=URL"}),database:e.string({multiple:!0,description:"Supply a named PostgreSQL database as NAME=URL"}),env:e.string({multiple:!0,description:"Supply an environment value as NAME=VALUE"}),"env-from":e.string({multiple:!0,description:"Supply an environment value from a host variable as NAME=HOST_KEY"}),"environment-file":e.string({description:"Supply values, prepared setup data and handle descriptors from a version 1 environment file"})};export function runTypeArgument(t=s){return c.string({check:i=>{n(i,`Type "${i}"`,t)},description:`Optional suite type shortcut: ${r({includeAll:!0})}`,required:!1,options:o({includeAll:!0})})}export const runArgs={type:runTypeArgument()},runFlags={...testSelectionFlags,...suppliedEnvironmentFlags,...vitestSelectionFlags,type:e.string({char:"t",multiple:!0,description:`Run specific suite type(s): ${r({includeAll:!0})}`}),suite:e.string({char:"s",multiple:!0,description:"Run specific suite(s)"}),file:e.string({char:"f",multiple:!0,description:"Run specific file(s)"}),"task-concurrency":e.string({description:"Maximum number of concurrent test tasks for the whole run"}),workers:e.string({description:"Deprecated alias for --task-concurrency"}),"file-timeout-seconds":e.string({description:"Per-file wall-clock timeout in seconds"}),"allow-empty":e.boolean({description:"Permit a successful run when no runnable files execute",default:!1}),"ignore-skip-rules":e.boolean({description:"Run files even if testkit.config.ts marks them skipped",default:!1}),"skip-schema-source-verify":e.boolean({description:"Skip the check that local schema replay matches the source database schema",default:!1}),"output-mode":e.string({description:"Reporter mode",options:["compact","debug","events"]}),debug:e.boolean({description:"Alias for --output-mode debug",default:!1})};
1
+ import{RETIRED_TEST_TYPES as s,rejectRetiredTestType as n}from"../domain/retired-test-types.js";import{publicTestTypeList as o,publicTestTypeListText as r}from"../domain/test-types.js";import{CliUsageError as p}from"./framework/errors.js";import{Args as c,Flags as e}from"./framework/index.js";export const sharedFlags={dir:e.string({description:"Explicit product directory"}),service:e.string({description:"Limit the operation to one service"})},schemaOperationFlags={"timeout-seconds":e.string({description:"Seconds the operation may run, not counting configured template steps (default 600). A longer limit lengthens the caps on Testkit's own database calls in proportion"})},testSelectionFlags={dir:sharedFlags.dir,group:e.string({description:"Limit the operation to one test group"}),service:e.string({description:"Removed in 0.6.0; use --group"})};export function selectedGroup(t){if(t.service!==void 0)throw new p(`Testkit 0.6.0 removed --service from run, discover, artifacts, and workspace; use --group ${t.service}`);return t.group??null}export const vitestSelectionFlags={"vitest-config":e.string({description:"Run plain test files with this Vitest config, relative to the product directory"}),"vitest-project":e.string({description:"Run plain test files from this Vitest project only"}),"vitest-runner":e.string({description:"Run plain test files with the repository's Vitest (repo) or Testkit's own (managed)",options:["repo","managed"]})},suppliedEnvironmentFlags={"base-url":e.string({description:"Supply the default endpoint, BASE_URL, instead of provisioning it"}),endpoint:e.string({multiple:!0,description:"Supply a named endpoint as NAME=URL"}),database:e.string({multiple:!0,description:"Supply a named PostgreSQL database as NAME=URL"}),env:e.string({multiple:!0,description:"Supply an environment value as NAME=VALUE"}),"env-from":e.string({multiple:!0,description:"Supply an environment value from a host variable as NAME=HOST_KEY"}),"environment-file":e.string({description:"Supply values, prepared setup data and handle descriptors from a version 1 environment file"})};export function runTypeArgument(t=s){return c.string({check:i=>{n(i,`Type "${i}"`,t)},description:`Optional suite type shortcut: ${r({includeAll:!0})}`,required:!1,options:o({includeAll:!0})})}export const runArgs={type:runTypeArgument()},runFlags={...testSelectionFlags,...suppliedEnvironmentFlags,...vitestSelectionFlags,type:e.string({char:"t",multiple:!0,description:`Run specific suite type(s): ${r({includeAll:!0})}`}),suite:e.string({char:"s",multiple:!0,description:"Run specific suite(s)"}),file:e.string({char:"f",multiple:!0,description:"Run specific file(s)"}),"task-concurrency":e.string({description:"Maximum number of concurrent test tasks for the whole run"}),workers:e.string({description:"Deprecated alias for --task-concurrency"}),"file-timeout-seconds":e.string({description:"Per-file wall-clock timeout in seconds"}),"allow-empty":e.boolean({description:"Permit a successful run when no runnable files execute",default:!1}),"ignore-skip-rules":e.boolean({description:"Run files even if testkit.config.ts marks them skipped",default:!1}),"skip-schema-source-verify":e.boolean({description:"Skip the check that local schema replay matches the source database schema",default:!1}),"output-mode":e.string({description:"Reporter mode",options:["compact","debug","events"]}),debug:e.boolean({description:"Alias for --output-mode debug",default:!1})};
@@ -1,2 +1,3 @@
1
- import{bugRerun as c,fileBug as p,prepareBugFiling as f}from"../../../app/bugs/file.js";import{readRunArtifact as l}from"../../../app/bugs/run-reading.js";import{buildRunRequest as m}from"../../../app/run/request.js";import{executeRunRequest as v}from"../../operations/run/operation.js";import{renderAlreadyFiledBug as w,renderBugFileResult as R,renderBugFileRun as y}from"../../renderers/bug/text.js";export default async function b({args:o,flags:e,json:t,log:i}){const n={baseUrl:e["base-url"],database:e.database,endpoint:e.endpoint,env:e.env,envFrom:e["env-from"],environmentFile:e["environment-file"]},r=await f({cwd:process.cwd(),dir:e.dir,env:process.env,id:o.id,supplied:n});if(r.status==="already-filed")return t||i(w(r.issue)),{ok:!0,created:!1,draft:r.draft,issue:r.issue};t||i(y(r));const u=await c(r,e,n),d=await m({allowEmpty:!1,dir:e.dir,file:u.files,ignoreSkipRules:!1,skipSchemaSourceVerify:!1,supplied:n,vitest:u.ready.draft.vitest},null,process.cwd(),process.cwd(),process.env),s=await v(d,{json:t,outputMode:"compact"}),a=await p(u.ready,l(s.runArtifact,r.checkout.productPath));return t||i(R(a).join(`
2
- `)),{ok:!0,created:!0,...a}}
1
+ import{bugBatchReport as R,fileBugBatch as y,planBugBatchRuns as b,prepareBugBatch as k,rejectedBugBatchDraft as F}from"../../../app/bugs/batch.js";import{bugRerun as S,fileBug as q,prepareBugFiling as A}from"../../../app/bugs/file.js";import{readRunArtifact as j,readRunArtifactFile as E}from"../../../app/bugs/run-reading.js";import{buildRunRequest as g}from"../../../app/run/request.js";import{CliUsageError as w}from"../../framework/errors.js";import{executeRunRequest as B}from"../../operations/run/operation.js";import{renderAlreadyFiledBug as M,renderBugBatchResult as P,renderBugBatchRun as U,renderBugFileResult as x,renderBugFileRun as V}from"../../renderers/bug/text.js";export default async function C(o){const{args:t,flags:e,json:a,log:s}=o,r={baseUrl:e["base-url"],database:e.database,endpoint:e.endpoint,env:e.env,envFrom:e["env-from"],environmentFile:e["environment-file"]};if(e.batch)return D(o,r);if(t.id.length===0)throw new w("Missing required argument id");if(t.id.length>1)throw new w(`Unexpected argument ${JSON.stringify(t.id[1])}. Pass --batch to file several drafts.`);const[c]=t.id,i=await A({cwd:process.cwd(),dir:e.dir,env:process.env,id:c,supplied:r});if(i.status==="already-filed")return a||s(M(i.issue)),{ok:!0,created:!1,draft:i.draft,issue:i.issue};a||s(V(i));const n=await S(i,e,r),f=await g({allowEmpty:!1,dir:e.dir,file:n.files,ignoreSkipRules:!1,skipSchemaSourceVerify:!1,supplied:r,vitest:n.ready.draft.vitest},null,process.cwd(),process.cwd(),process.env),d=await B(f,{json:a,outputMode:"compact"}),u=await q(n.ready,j(d.runArtifact,i.checkout.productPath));return a||s(x(u).join(`
2
+ `)),{ok:!0,created:!0,...u}}async function D({args:o,flags:t,json:e,log:a,setExitCode:s},r){const c=await k({cwd:process.cwd(),dir:t.dir,env:process.env,ids:o.id,supplied:r}),i=await b(c,t,r),n=[...c.settled,...i.rejected],f=[];for(const{drafts:u,files:m,vitest:v}of i.runs){e||a(U(u,m));try{const{runArtifact:l}=await J(m,v,t.dir,r,e),{productPath:p}=c.checkout;f.push(...u.map(h=>({ready:h,run:E(l,p,h.draft.test.file)})))}catch(l){n.push(...u.map(p=>F(p.draft.id,l)))}}n.push(...await y(f));const d=R(c,n);return d.ok||s(1),e||a(P(d.drafts).join(`
3
+ `)),d}async function J(o,t,e,a,s){const r=await g({allowEmpty:!1,dir:e,file:o,ignoreSkipRules:!1,skipSchemaSourceVerify:!1,supplied:a,vitest:t},null,process.cwd(),process.cwd(),process.env);return B(r,{json:s,outputMode:"compact"})}
@@ -1,8 +1,8 @@
1
- export function renderVersion(n){return`${n.name}/${n.version} ${process.platform}-${process.arch} node-v${process.versions.node}
2
- `}export function renderRootHelp(n,e){const o=[...e].sort(u);return`${[n.description,"","Usage:"," testkit <command> [options]","","Commands:",...o.map(a=>i(a)),"","Global options:"," -h, --help Show help"," -v, --version Show version"].join(`
1
+ export function renderVersion(e){return`${e.name}/${e.version} ${process.platform}-${process.arch} node-v${process.versions.node}
2
+ `}export function renderRootHelp(e,n){const o=[...n].sort(p);return`${[e.description,"","Usage:"," testkit <command> [options]","","Commands:",...o.map(a=>i(a)),"","Global options:"," -h, --help Show help"," -v, --version Show version"].join(`
3
3
  `)}
4
- `}export function renderTopicHelp(n,e){const o=n.join(" "),c=e.filter(s=>s.id.length>n.length&&n.every((l,r)=>s.id[r]===l)).sort(u);return`${[`Testkit ${o} commands`,"","Usage:",` testkit ${o} <command> [options]`,"","Commands:",...c.map(s=>i(s))].join(`
4
+ `}export function renderTopicHelp(e,n){const o=e.join(" "),m=n.filter(s=>s.id.length>e.length&&e.every((l,r)=>s.id[r]===l)).sort(p);return`${[`Testkit ${o} commands`,"","Usage:",` testkit ${o} <command> [options]`,"","Commands:",...m.map(s=>i(s))].join(`
5
5
  `)}
6
- `}export function renderCommandHelp(n,e){const o=n,c=n.id.join(" "),a=Object.entries(o.args??{}),s=e.filter(t=>t.id.length===n.id.length+1&&n.id.every((m,$)=>t.id[$]===m)).sort(u),l=a.map(([t,m])=>m.required?`<${t}>`:`[${t}]`).join(" "),r=[o.summary??c,"","Usage:",` testkit ${c}${l?` ${l}`:""} [options]`];a.length>0&&r.push("","Arguments:",...a.map(([t,m])=>h(t,m)));const p=Object.entries(o.flags??{});return(p.length>0||o.enableJsonFlag)&&(r.push("","Options:",...p.map(([t,m])=>g(t,m))),o.enableJsonFlag&&r.push(" --json Write one machine-readable JSON document")),r.push(" -h, --help Show help for this command"),s.length>0&&r.push("","Subcommands:",...s.map(t=>i(t))),o.description&&r.push("",o.description),`${r.join(`
6
+ `}export function renderCommandHelp(e,n){const o=e,m=e.id.join(" "),a=Object.entries(o.args??{}),s=n.filter(t=>t.id.length===e.id.length+1&&e.id.every((c,u)=>t.id[u]===c)).sort(p),l=a.map(([t,c])=>{const u=c.multiple?`${t}...`:t;return c.required?`<${u}>`:`[${u}]`}).join(" "),r=[o.summary??m,"","Usage:",` testkit ${m}${l?` ${l}`:""} [options]`];a.length>0&&r.push("","Arguments:",...a.map(([t,c])=>h(t,c)));const d=Object.entries(o.flags??{});return(d.length>0||o.enableJsonFlag)&&(r.push("","Options:",...d.map(([t,c])=>g(t,c))),o.enableJsonFlag&&r.push(" --json Write one machine-readable JSON document")),r.push(" -h, --help Show help for this command"),s.length>0&&r.push("","Subcommands:",...s.map(t=>i(t))),o.description&&r.push("",o.description),`${r.join(`
7
7
  `)}
8
- `}export function commandPath(n){return n.id.join(" ")}function i(n){return` ${n.id.join(" ")} ${n.summary??""}`.trimEnd()}function h(n,e){const o=d(e.options);return` ${n}${e.required?" (required)":""}${o} ${e.description??""}`.trimEnd()}function g(n,e){const o=e.char?`-${e.char}, `:"",c=e.kind==="boolean"&&e.allowNo?`--[no-]${n}`:`--${n}`,a=e.kind==="boolean"?"":` <${e.kind==="integer"?"integer":"value"}>`,s=e.multiple?" (repeatable)":"",l=e.required?" (required)":"",r=e.default===void 0?"":` (default: ${String(e.default)})`;return` ${o}${c}${a}${s}${l}${d(e.options)}${r} ${e.description??""}`.trimEnd()}function d(n){return n?` (${n.join("|")})`:""}function u(n,e){return commandPath(n).localeCompare(commandPath(e))}
8
+ `}export function commandPath(e){return e.id.join(" ")}function i(e){return` ${e.id.join(" ")} ${e.summary??""}`.trimEnd()}function h(e,n){const o=$(n.options);return` ${e}${n.required?" (required)":""}${o} ${n.description??""}`.trimEnd()}function g(e,n){const o=n.char?`-${n.char}, `:"",m=n.kind==="boolean"&&n.allowNo?`--[no-]${e}`:`--${e}`,a=n.kind==="boolean"?"":` <${n.kind==="integer"?"integer":"value"}>`,s=n.multiple?" (repeatable)":"",l=n.required?" (required)":"",r=n.default===void 0?"":` (default: ${String(n.default)})`;return` ${o}${m}${a}${s}${l}${$(n.options)}${r} ${n.description??""}`.trimEnd()}function $(e){return e?` (${e.join("|")})`:""}function p(e,n){return commandPath(e).localeCompare(commandPath(n))}
@@ -1 +1 @@
1
- import"./definitions.js";import{CliUsageError as l}from"./errors.js";export function parseCommandArguments(n,t,e,i){const s=t??{},o=y(s),a=q(s),r=new Set,f=[];let c=!1;for(let u=0;u<n.length;u+=1){const d=n[u];if(c){f.push(d);continue}if(d==="--"){c=!0;continue}if(d==="--json"){if(!i)throw g(d);continue}if(d.startsWith("--")){u=m(n,u,{aliases:o,definitions:s,flags:a,seenFlags:r});continue}if(d.startsWith("-")&&d!=="-"){u=v(n,u,{aliases:o,definitions:s,flags:a,seenFlags:r});continue}f.push(d)}return E(s,a),{args:j(f,e??{}),flags:a}}function m(n,t,e){const i=n[t],s=i.indexOf("="),o=i.slice(2,s<0?void 0:s),a=k(o,e.definitions);if(!a)throw g(`--${o}`);const{definition:r,name:f,negated:c}=a;if(r.kind==="boolean"){if(s>=0)throw new l(`Boolean flag --${o} does not take a value`);if(c&&!r.allowNo)throw g(`--${o}`);return h(f,r,e.seenFlags),e.flags[f]=!c,t}if(c)throw g(`--${o}`);const w=s<0?null:i.slice(s+1),u=p(n,t,`--${f}`,w,r);return h(f,r,e.seenFlags),$(e.flags,f,r,u.values),u.index}function v(n,t,e){const i=n[t],s=i.slice(1,2),o=e.aliases.get(s);if(!o)throw g(`-${s}`);const a=e.definitions[o],r=i.slice(2);if(a.kind==="boolean"){if(r.startsWith("="))throw new l(`Boolean flag -${s} does not take a value`);if(r.length>0)throw g(i);return h(o,a,e.seenFlags),e.flags[o]=!0,t}const f=r.length===0?null:r.startsWith("=")?r.slice(1):r,c=p(n,t,`-${s}`,f,a);return h(o,a,e.seenFlags),$(e.flags,o,a,c.values),c.index}function p(n,t,e,i,s){const o=[];let a=t;if(i!==null){if(i.length===0)throw new l(`Flag ${e} requires a value`);o.push(i)}else{const r=n.at(t+1);if(r===void 0||F(r,s))throw new l(`Flag ${e} requires a value`);o.push(r),a+=1}if(s.multiple)for(;;){const r=n.at(a+1);if(r===void 0||F(r,s))break;o.push(r),a+=1}return{index:a,values:o}}function $(n,t,e,i){if(e.multiple){const o=n[t];if(o!==void 0&&!Array.isArray(o))throw new Error(`Invalid internal default for multiple flag --${t}`);const a=o??[];for(const r of i)a.push(b(t,e,r));n[t]=a;return}const s=i.at(0);if(s===void 0)throw new Error(`Missing parsed value for --${t}`);n[t]=b(t,e,s)}function k(n,t){if(Object.hasOwn(t,n))return{definition:t[n],name:n,negated:!1};if(!n.startsWith("no-"))return null;const e=n.slice(3);return Object.hasOwn(t,e)?{definition:t[e],name:e,negated:!0}:null}function h(n,t,e){if(!t.multiple&&e.has(n))throw new l(`Flag --${n} can only be specified once`);e.add(n)}function F(n,t){return n==="-"||t.kind==="integer"&&/^-\d+$/u.test(n)?!1:n.startsWith("-")}function b(n,t,e){if(t.kind==="integer"){if(!/^-?\d+$/u.test(e))throw new l(`Expected --${n} to be an integer; received ${JSON.stringify(e)}`);const i=Number(e);if(!Number.isSafeInteger(i))throw new l(`Expected --${n} to be a safe integer; received ${JSON.stringify(e)}`);return i}return O(e,t.options,`--${n}`),e}function j(n,t){const e=Object.entries(t);if(n.length>e.length)throw new l(`Unexpected argument ${JSON.stringify(n[e.length])}`);const i={};for(const[s,[o,a]]of e.entries()){const r=n.at(s);if(r===void 0){if(a.required)throw new l(`Missing required argument ${o}`);i[o]=void 0;continue}a.check?.(r),O(r,a.options,o),i[o]=r}return i}function O(n,t,e){if(!(!t||t.includes(n)))throw new l(`Expected ${e} to be one of: ${t.join(", ")}; received ${JSON.stringify(n)}`)}function q(n){const t={};for(const[e,i]of Object.entries(n))t[e]=i.default;return t}function y(n){const t=new Map;for(const[e,i]of Object.entries(n)){if(!i.char)continue;if(!/^[A-Za-z0-9]$/u.test(i.char))throw new Error(`Invalid short flag alias for --${e}: ${i.char}`);const s=t.get(i.char);if(s)throw new Error(`Duplicate short flag alias -${i.char} for --${s} and --${e}`);t.set(i.char,e)}return t}function E(n,t){const e=Object.entries(n).filter(([i,s])=>s.required&&t[i]===void 0).map(([i])=>`--${i}`);if(e.length>0)throw new l(`Missing required flag${e.length===1?"":"s"}: ${e.join(", ")}`)}function g(n){return new l(`Unknown flag: ${n}`)}
1
+ import"./definitions.js";import{CliUsageError as f}from"./errors.js";export function parseCommandArguments(n,t,e,i){const s=t??{},r=y(s),l=j(s),o=new Set,a=[];let u=!1;for(let c=0;c<n.length;c+=1){const d=n[c];if(u){a.push(d);continue}if(d==="--"){u=!0;continue}if(d==="--json"){if(!i)throw g(d);continue}if(d.startsWith("--")){c=O(n,c,{aliases:r,definitions:s,flags:l,seenFlags:o});continue}if(d.startsWith("-")&&d!=="-"){c=v(n,c,{aliases:r,definitions:s,flags:l,seenFlags:o});continue}a.push(d)}return E(s,l),{args:q(a,e??{}),flags:l}}function O(n,t,e){const i=n[t],s=i.indexOf("="),r=i.slice(2,s<0?void 0:s),l=k(r,e.definitions);if(!l)throw g(`--${r}`);const{definition:o,name:a,negated:u}=l;if(o.kind==="boolean"){if(s>=0)throw new f(`Boolean flag --${r} does not take a value`);if(u&&!o.allowNo)throw g(`--${r}`);return h(a,o,e.seenFlags),e.flags[a]=!u,t}if(u)throw g(`--${r}`);const w=s<0?null:i.slice(s+1),c=$(n,t,`--${a}`,w,o);return h(a,o,e.seenFlags),m(e.flags,a,o,c.values),c.index}function v(n,t,e){const i=n[t],s=i.slice(1,2),r=e.aliases.get(s);if(!r)throw g(`-${s}`);const l=e.definitions[r],o=i.slice(2);if(l.kind==="boolean"){if(o.startsWith("="))throw new f(`Boolean flag -${s} does not take a value`);if(o.length>0)throw g(i);return h(r,l,e.seenFlags),e.flags[r]=!0,t}const a=o.length===0?null:o.startsWith("=")?o.slice(1):o,u=$(n,t,`-${s}`,a,l);return h(r,l,e.seenFlags),m(e.flags,r,l,u.values),u.index}function $(n,t,e,i,s){const r=[];let l=t;if(i!==null){if(i.length===0)throw new f(`Flag ${e} requires a value`);r.push(i)}else{const o=n.at(t+1);if(o===void 0||F(o,s))throw new f(`Flag ${e} requires a value`);r.push(o),l+=1}if(s.multiple)for(;;){const o=n.at(l+1);if(o===void 0||F(o,s))break;r.push(o),l+=1}return{index:l,values:r}}function m(n,t,e,i){if(e.multiple){const r=n[t];if(r!==void 0&&!Array.isArray(r))throw new Error(`Invalid internal default for multiple flag --${t}`);const l=r??[];for(const o of i)l.push(b(t,e,o));n[t]=l;return}const s=i.at(0);if(s===void 0)throw new Error(`Missing parsed value for --${t}`);n[t]=b(t,e,s)}function k(n,t){if(Object.hasOwn(t,n))return{definition:t[n],name:n,negated:!1};if(!n.startsWith("no-"))return null;const e=n.slice(3);return Object.hasOwn(t,e)?{definition:t[e],name:e,negated:!0}:null}function h(n,t,e){if(!t.multiple&&e.has(n))throw new f(`Flag --${n} can only be specified once`);e.add(n)}function F(n,t){return n==="-"||t.kind==="integer"&&/^-\d+$/u.test(n)?!1:n.startsWith("-")}function b(n,t,e){if(t.kind==="integer"){if(!/^-?\d+$/u.test(e))throw new f(`Expected --${n} to be an integer; received ${JSON.stringify(e)}`);const i=Number(e);if(!Number.isSafeInteger(i))throw new f(`Expected --${n} to be a safe integer; received ${JSON.stringify(e)}`);return i}return p(e,t.options,`--${n}`),e}function q(n,t){const e=Object.entries(t);if(n.length>e.length&&e.at(-1)?.[1].multiple!==!0)throw new f(`Unexpected argument ${JSON.stringify(n[e.length])}`);const i={};for(const[s,[r,l]]of e.entries()){if(l.multiple){const a=n.slice(s);if(a.length===0&&l.required)throw new f(`Missing required argument ${r}`);for(const u of a)l.check?.(u),p(u,l.options,r);i[r]=a;continue}const o=n.at(s);if(o===void 0){if(l.required)throw new f(`Missing required argument ${r}`);i[r]=void 0;continue}l.check?.(o),p(o,l.options,r),i[r]=o}return i}function p(n,t,e){if(!(!t||t.includes(n)))throw new f(`Expected ${e} to be one of: ${t.join(", ")}; received ${JSON.stringify(n)}`)}function j(n){const t={};for(const[e,i]of Object.entries(n))t[e]=i.default;return t}function y(n){const t=new Map;for(const[e,i]of Object.entries(n)){if(!i.char)continue;if(!/^[A-Za-z0-9]$/u.test(i.char))throw new Error(`Invalid short flag alias for --${e}: ${i.char}`);const s=t.get(i.char);if(s)throw new Error(`Duplicate short flag alias -${i.char} for --${s} and --${e}`);t.set(i.char,e)}return t}function E(n,t){const e=Object.entries(n).filter(([i,s])=>s.required&&t[i]===void 0).map(([i])=>`--${i}`);if(e.length>0)throw new f(`Missing required flag${e.length===1?"":"s"}: ${e.join(", ")}`)}function g(n){return new f(`Unknown flag: ${n}`)}
@@ -1 +1 @@
1
- import{hasErrorCode as i,serializeError as n}from"../kernel/error-serialization.js";export function serializeCliError(r){return n(r)}export function writeStandardOutput(r){return new Promise((o,s)=>{const e=t=>{t?i(t,"EPIPE")?o():s(t):(process.stdout.off("error",e),o())};process.stdout.once("error",e),process.stdout.write(r,e)})}
1
+ import{hasErrorCode as s,serializeError as n}from"../kernel/error-serialization.js";export function serializeCliError(r){return n(r)}export function writeStandardOutput(r){return process.stdout.listeners("error").includes(o)||process.stdout.on("error",o),new Promise((t,i)=>{process.stdout.write(r,e=>{!e||s(e,"EPIPE")?t():i(e)})})}function o(){}
@@ -1 +1 @@
1
- import{formatShare as s}from"../../../app/campaigns/brief.js";const i=12,a=20;export function renderBugRecordResult(e){return[`Recorded ${e.draft.id}`,`Draft: ${e.path}`,...e.redactedFields.length===0?[]:[`Redacted secrets in: ${e.redactedFields.join(", ")}`],...e.replaced?["Replaced the earlier draft for this test."]:[],...e.unreadableDrafts.map(n=>`Testkit cannot read ${n.file}: ${n.problem}`),`Next: commit and push the test, then run \`testkit bug file ${e.draft.id}\`.`]}export function renderAlreadyFiledBug(e){return`Already filed as #${String(e.number)}: ${e.url}`}export function renderBugFileRun(e){return`Running ${e.draft.test.file} at ${e.filing.foundAt.slice(0,i)}. Testkit files the bug when the test fails.`}export function renderBugFileResult(e){return[`Filed #${String(e.issue.number)}: ${e.issue.url}`,e.area===null?"Area: none, because testkit.config.ts has no areas":`Area: ${e.area.id}, ${s(e.area.share)} of source bytes`,e.introducedBy===null?"Introduced by: unknown, because this shallow clone stops before the commit that wrote the faulty lines":`Introduced by: ${e.introducedBy.slice(0,i)}`,`Draft: ${e.path}`]}export function renderKnownFailures(e){const n=e.known.length,t=n-a;return[...e.trackerProblem===null?[]:[`Testkit could not read the open bugs, so every failure counts as new: ${e.trackerProblem}`],e.new.length===0?`Known failures: ${String(n)}. Every failing test has an open bug, so the run passes.`:`New failures: ${String(e.new.length)}. Known failures: ${String(n)}. The run fails on the new failures.`,...e.known.slice(0,a).map(({failure:r,issue:o})=>` #${String(o.issue)} ${r.file}: ${r.title??"failed without naming a test"}`),...t>0?[` and ${String(t)} more`]:[]]}
1
+ import{formatShare as d}from"../../../app/campaigns/brief.js";const r=12,a=20;export function renderBugRecordResult(e){return[`Recorded ${e.draft.id}`,`Draft: ${e.path}`,...e.redactedFields.length===0?[]:[`Redacted secrets in: ${e.redactedFields.join(", ")}`],...e.replaced?["Replaced the earlier draft for this test."]:[],...e.unreadableDrafts.map(t=>`Testkit cannot read ${t.file}: ${t.problem}`),`Next: commit and push the test, then run \`testkit bug file ${e.draft.id}\`.`]}export function renderAlreadyFiledBug(e){return`Already filed as #${String(e.number)}: ${e.url}`}export function renderBugFileRun(e){return`Running ${e.draft.test.file} at ${e.filing.foundAt.slice(0,r)}. Testkit files the bug when the test fails.`}export function renderBugFileResult(e){return[`Filed #${String(e.issue.number)}: ${e.issue.url}`,e.area===null?"Area: none, because testkit.config.ts has no areas":`Area: ${e.area.id}, ${d(e.area.share)} of source bytes`,e.introducedBy===null?"Introduced by: unknown, because this shallow clone stops before the commit that wrote the faulty lines":`Introduced by: ${e.introducedBy.slice(0,r)}`,`Draft: ${e.path}`,...e.listed?[]:[`The tracker had not listed #${String(e.issue.number)} among the open issues after 7.5 seconds, so Testkit could not check whether another checkout filed this test at the same time. Look for a second open issue for this test.`]]}export function renderBugBatchRun(e,t){const n=e.at(0)?.filing.foundAt.slice(0,r)??"";return`Running ${String(t.length)} ${t.length===1?"file":"files"} at ${n} for ${e.map(({draft:i})=>i.id).join(", ")}. Testkit files each bug whose test fails.`}export function renderBugBatchResult(e){const t=n=>e.filter(i=>i.status===n).length;return[...e.map(n=>{switch(n.status){case"filed":return`Filed ${n.id} as #${String(n.issue.number)}: ${n.issue.url}`;case"already-filed":return`Already filed ${n.id} as #${String(n.issue.number)}: ${n.issue.url}`;case"rejected":return`Rejected ${n.id}: ${n.reason}`;case"not-filed":return`Not filed ${n.id}: ${n.reason}`}}),`Filed ${String(t("filed"))}, already filed ${String(t("already-filed"))}, rejected ${String(t("rejected"))}, not filed ${String(t("not-filed"))}.`]}export function renderKnownFailures(e){const t=e.known.length,n=t-a;return[...e.trackerProblem===null?[]:[`Testkit could not read the open bugs, so every failure counts as new: ${e.trackerProblem}`],e.new.length===0?`Known failures: ${String(t)}. Every failing test has an open bug, so the run passes.`:`New failures: ${String(e.new.length)}. Known failures: ${String(t)}. The run fails on the new failures.`,...e.known.slice(0,a).map(({failure:i,issue:s})=>` #${String(s.issue)} ${i.file}: ${i.title??"failed without naming a test"}`),...n>0?[` and ${String(n)} more`]:[]]}
@@ -1 +1 @@
1
- import{sanitizeErrorMessage as c}from"../../../kernel/error-serialization.js";export function formatDuration(e){const t=Math.max(0,Math.round(e/1e3)),i=Math.floor(t/60),r=t%60;return i===0?`${String(r)}s`:`${String(i)}m ${String(r).padStart(2,"0")}s`}function x(e){const t=e.skippedSuiteCount||0,i=e.completedSuiteCount-e.failedSuiteCount-t,r=e.suiteCount-e.completedSuiteCount;let s=`${String(i)}/${String(e.suiteCount)} suites passed`;return(e.totalFileCount||0)>0&&(s+=`, ${String(e.passedFileCount)}/${String(e.totalFileCount)} files passed`),t>0?s+=`, ${String(t)} ${f(t,"suite","suites")} skipped`:(e.skippedFileCount||0)>0&&(s+=`, ${String(e.skippedFileCount)} ${f(e.skippedFileCount,"file","files")} skipped`),r>0?s+=`, ${String(r)} ${f(r,"suite","suites")} not run`:(e.notRunFileCount||0)>0&&(s+=`, ${String(e.notRunFileCount)} ${f(e.notRunFileCount,"file","files")} not run`),s}function A(e){return e.reduce((t,i)=>Math.max(t,i.name.length),4)}export function buildRunSummaryData(e,t,i=null){const r=M(e),s=L(e);return{result:i==="failed"||i==="interrupted"||r.failedServices>0||r.infraFailedServices>0?"FAILED":"PASSED",totalServices:r.totalServices,failedServices:r.failedServices,infraFailedServices:r.infraFailedServices,blockedByRuntimeServices:r.blockedByRuntimeServices,passed:r.passedFiles,failed:r.failedFiles,skipped:r.skippedFiles,notRun:r.notRunFiles,files:r.totalFiles,duration:formatDuration(t),serviceErrors:s}}export function buildDebugRunSummaryLines(e,t,i=null){const r=e.length,s=e.filter(n=>!n.skipped),u=e.filter(n=>n.skipped),d=s.filter(n=>n.failed),p=s.filter(n=>n.infraFailed),m=s.filter(n=>!n.failed&&!n.infraFailed),C=s.reduce((n,o)=>n+o.suiteCount,0),h=s.reduce((n,o)=>n+o.completedSuiteCount,0),S=s.reduce((n,o)=>n+(o.skippedSuiteCount||0),0),R=s.reduce((n,o)=>n+o.failedSuiteCount,0),E=h-R-S,g=s.reduce((n,o)=>n+(o.totalFileCount||0),0),D=s.reduce((n,o)=>n+(o.passedFileCount||0),0),$=s.reduce((n,o)=>n+(o.skippedFileCount||0),0),a=["","\u2550\u2550 Summary \u2550\u2550",[`groups ${String(m.length)}/${String(s.length)} passed`,`suites ${String(E)}/${String(C)} passed`,g>0?`files ${String(D)}/${String(g)} passed`:null,S>0?`suites ${String(S)} skipped`:null,$>0?`files ${String($)} skipped`:null,u.length>0?`${String(u.length)} skipped`:null,`duration ${formatDuration(t)}`].filter(Boolean).join(" \xB7 ")];for(const n of e){const o=j(n)?"SKIP":n.failed?"FAIL":n.infraFailed?"INFRA":"PASS",b=n.skipped?"no matching suites":x(n);if(a.push(`${o.padEnd(4)} ${n.name.padEnd(A(e))} ${b} \xB7 ${formatDuration(n.durationMs)}`),!(!n.failed&&!n.infraFailed)){for(const l of n.suites.filter(F=>F.failed)){const F=l.failedFiles.length>0?` (${l.failedFiles.join(", ")})`:"",y=[l.testType,l.name].filter(Boolean).join(":");a.push(` - ${y}${F} \xB7 ${formatDuration(l.durationMs)}`),l.error&&a.push(` ${l.error}`)}for(const l of n.errors)a.push(` - error: ${l}`)}}return i==="failed"||i==="interrupted"||d.length>0||p.length>0?(a.push("",d.length===0&&p.length===0?"Result: FAILED (run gate)":`Result: FAILED (${I({failedServices:d.length,infraFailedServices:p.length,totalServices:r})})`),a):(a.push("","Result: PASSED"),a)}export function buildFailurePresentation(e){const t=P(e.failureDetails||[]),i=t[0]||null,r=t.map(u=>c((u.message||u.title).trim())),s=[];for(const u of t.slice(1)){const d=v(u.message||u.title,220);d&&!s.includes(d)&&s.push(d)}return{primary:v(B(e,{error:e.suiteError||null},i,r),240),details:s}}function M(e){const t=e.filter(i=>!i.skipped);return{totalServices:e.length,failedServices:t.filter(i=>i.failed).length,infraFailedServices:t.filter(i=>i.infraFailed).length,blockedByRuntimeServices:t.filter(i=>i.blockedByRuntime).length,totalFiles:t.reduce((i,r)=>i+(r.totalFileCount||0),0),passedFiles:t.reduce((i,r)=>i+(r.passedFileCount||0),0),failedFiles:t.reduce((i,r)=>i+(r.failedFileCount||0),0),skippedFiles:t.reduce((i,r)=>i+(r.skippedFileCount||0),0),notRunFiles:t.reduce((i,r)=>i+(r.notRunFileCount||0),0)}}function I(e){const t=[`${String(e.failedServices)}/${String(e.totalServices)} groups failed`];return(e.infraFailedServices||0)>0&&t.push(`${String(e.infraFailedServices)} infra-blocked`),t.join(", ")}function B(e,t,i,r){if(i?.message)return c(i.message.trim());const s=c(e.error||"");return s||(r.length>0?r[0]:s||c(t.error||"Failed"))}function P(e){return[...e].sort((t,i)=>k(t)-k(i)||(t.key||"").localeCompare(i.key||""))}function k(e){return e.location||e.stack?3:e.message?4:5}function v(e,t=180){return e.replace(/\s+/g," ").trim().slice(0,t)}function L(e){return e.reduce((t,i)=>t+i.errors.length,0)}function f(e,t,i){return e===1?t:i}function j(e){return e.skipped?!0:!e.failed&&(e.skippedSuiteCount||0)>0&&(e.skippedSuiteCount||0)===e.suiteCount&&(e.notRunFileCount||0)===0}
1
+ import{stripVTControlCharacters as M}from"node:util";import{sanitizeErrorMessage as I}from"../../../kernel/error-serialization.js";const B=new Intl.Segmenter(void 0,{granularity:"grapheme"}),P=40;export function formatDuration(e){const t=Math.max(0,Math.round(e/1e3)),i=Math.floor(t/60),n=t%60;return i===0?`${String(n)}s`:`${String(i)}m ${String(n).padStart(2,"0")}s`}function x(e){const t=e.skippedSuiteCount||0,i=e.completedSuiteCount-e.failedSuiteCount-t,n=e.suiteCount-e.completedSuiteCount;let s=`${String(i)}/${String(e.suiteCount)} suites passed`;return(e.totalFileCount||0)>0&&(s+=`, ${String(e.passedFileCount)}/${String(e.totalFileCount)} files passed`),t>0?s+=`, ${String(t)} ${f(t,"suite","suites")} skipped`:(e.skippedFileCount||0)>0&&(s+=`, ${String(e.skippedFileCount)} ${f(e.skippedFileCount,"file","files")} skipped`),n>0?s+=`, ${String(n)} ${f(n,"suite","suites")} not run`:(e.notRunFileCount||0)>0&&(s+=`, ${String(e.notRunFileCount)} ${f(e.notRunFileCount,"file","files")} not run`),s}function j(e){return e.reduce((t,i)=>Math.max(t,i.name.length),4)}export function buildRunSummaryData(e,t,i=null){const n=w(e),s=H(e);return{result:i==="failed"||i==="interrupted"||n.failedServices>0||n.infraFailedServices>0?"FAILED":"PASSED",totalServices:n.totalServices,failedServices:n.failedServices,infraFailedServices:n.infraFailedServices,blockedByRuntimeServices:n.blockedByRuntimeServices,passed:n.passedFiles,failed:n.failedFiles,skipped:n.skippedFiles,notRun:n.notRunFiles,files:n.totalFiles,duration:formatDuration(t),serviceErrors:s}}export function buildDebugRunSummaryLines(e,t,i=null){const n=e.length,s=e.filter(r=>!r.skipped),a=e.filter(r=>r.skipped),u=s.filter(r=>r.failed),c=s.filter(r=>r.infraFailed),C=s.filter(r=>!r.failed&&!r.infraFailed),v=s.reduce((r,o)=>r+o.suiteCount,0),R=s.reduce((r,o)=>r+o.completedSuiteCount,0),S=s.reduce((r,o)=>r+(o.skippedSuiteCount||0),0),D=s.reduce((r,o)=>r+o.failedSuiteCount,0),E=R-D-S,F=s.reduce((r,o)=>r+(o.totalFileCount||0),0),y=s.reduce((r,o)=>r+(o.passedFileCount||0),0),$=s.reduce((r,o)=>r+(o.skippedFileCount||0),0),d=["","\u2550\u2550 Summary \u2550\u2550",[`groups ${String(C.length)}/${String(s.length)} passed`,`suites ${String(E)}/${String(v)} passed`,F>0?`files ${String(y)}/${String(F)} passed`:null,S>0?`suites ${String(S)} skipped`:null,$>0?`files ${String($)} skipped`:null,a.length>0?`${String(a.length)} skipped`:null,`duration ${formatDuration(t)}`].filter(Boolean).join(" \xB7 ")];for(const r of e){const o=L(r)?"SKIP":r.failed?"FAIL":r.infraFailed?"INFRA":"PASS",A=r.skipped?"no matching suites":x(r);if(d.push(`${o.padEnd(4)} ${r.name.padEnd(j(e))} ${A} \xB7 ${formatDuration(r.durationMs)}`),!(!r.failed&&!r.infraFailed)){for(const l of r.suites.filter(g=>g.failed)){const g=l.failedFiles.length>0?` (${l.failedFiles.join(", ")})`:"",b=[l.testType,l.name].filter(Boolean).join(":");d.push(` - ${b}${g} \xB7 ${formatDuration(l.durationMs)}`),l.error&&d.push(` ${l.error}`)}for(const l of r.errors)d.push(` - error: ${l}`)}}return i==="failed"||i==="interrupted"||u.length>0||c.length>0?(d.push("",u.length===0&&c.length===0?"Result: FAILED (run gate)":`Result: FAILED (${z({failedServices:u.length,infraFailedServices:c.length,totalServices:n})})`),d):(d.push("","Result: PASSED"),d)}export function buildFailurePresentation(e){const t=O(e.failureDetails||[]),i=t[0]||null,n=t.map(W),s=k(N(e,{error:e.suiteError||null},i,n)),a=new Set([s]),u=[];for(const c of n.slice(1).map(k))!c||a.has(c)||(a.add(c),u.push(h(c,220)));return{primary:h(s,240),details:u}}function w(e){const t=e.filter(i=>!i.skipped);return{totalServices:e.length,failedServices:t.filter(i=>i.failed).length,infraFailedServices:t.filter(i=>i.infraFailed).length,blockedByRuntimeServices:t.filter(i=>i.blockedByRuntime).length,totalFiles:t.reduce((i,n)=>i+(n.totalFileCount||0),0),passedFiles:t.reduce((i,n)=>i+(n.passedFileCount||0),0),failedFiles:t.reduce((i,n)=>i+(n.failedFileCount||0),0),skippedFiles:t.reduce((i,n)=>i+(n.skippedFileCount||0),0),notRunFiles:t.reduce((i,n)=>i+(n.notRunFileCount||0),0)}}function z(e){const t=[`${String(e.failedServices)}/${String(e.totalServices)} groups failed`];return(e.infraFailedServices||0)>0&&t.push(`${String(e.infraFailedServices)} infra-blocked`),t.join(", ")}function N(e,t,i,n){return i?.message&&n[0]?n[0]:p(e.error||"")||n.find(s=>s!=="")||p(t.error||"")||"Failed"}function O(e){return[...e].sort((t,i)=>m(t)-m(i)||(t.key||"").localeCompare(i.key||""))}function m(e){return e.location||e.stack?3:e.message?4:5}function W(e){return p(e.message||"")||p(e.title)}function p(e){return I(M(e).trim())}function k(e){return e.replace(/\s+/g," ").trim()}function h(e,t){const i=Array.from(B.segment(e),({segment:u})=>u);if(i.length<=t)return e;const n=i.lastIndexOf(" ",t),s=t-n<=P?n:t,a=i.length-s-(i[s]===" "?1:0);return`${i.slice(0,s).join("")} \u2026 (${String(a)} more ${f(a,"character","characters")})`}function H(e){return e.reduce((t,i)=>t+i.errors.length,0)}function f(e,t,i){return e===1?t:i}function L(e){return e.skipped?!0:!e.failed&&(e.skippedSuiteCount||0)>0&&(e.skippedSuiteCount||0)===e.suiteCount&&(e.notRunFileCount||0)===0}
@@ -1 +1 @@
1
- import g from"node:crypto";import{rejectUnknownKeys as p}from"../../kernel/known-keys.js";import{isUnknownRecord as c}from"../../kernel/unknown-values.js";import r from"node:fs";import i from"node:path";import{fileURLToPath as d,pathToFileURL as h}from"node:url";import{buildWithEsbuildRetry as k}from"../../toolchains/esbuild.js";import{migrationText as b}from"../../domain/retired-test-types.js";const u=i.resolve(i.dirname(d(import.meta.url)),"..","..",".."),w=i.join(u,"dist","index.js"),y=i.join(u,"dist","config","authoring","index.js"),j=new Set(["/environment","/node-vitest/data","/playwright"]),E=["testkit.config.ts","testkit.config.mts","testkit.config.mjs","testkit.config.js"],T=Object.keys({areas:!0,artifacts:!0,bugs:!0,discovery:!0,execution:!0,fingerprints:!0,groups:!0,lint:!0,redact:!0,services:!0,toolchains:!0,ui:!0});export function findConfigFile(t){for(const e of E){const o=i.join(t,e);if(r.existsSync(o))return o}return null}export async function loadTestkitConfig(t,e={}){const o=findConfigFile(t);if(!o)return{config:{},configFile:null};const s=i.join(t,".testkit","_config");r.mkdirSync(s,{recursive:!0});const a=C(o),f=i.join(s,`config-${a.slice(0,12)}.mjs`);await k({absWorkingDir:t,bundle:!0,entryPoints:[o],format:"esm",legalComments:"none",outfile:f,platform:"node",sourcemap:"inline",target:"es2020",plugins:[v()]});const l=[a,e.importCacheSalt||""].filter(Boolean).join("-"),m=await import(`${h(f).href}?v=${l}`),n=x(m);if(!c(n))throw new Error(`testkit config file must export an object: ${i.basename(o)}`);if(Object.hasOwn(n,"profiles"))throw new Error(`${i.basename(o)}: Testkit 0.6.0 removed HTTP profiles; log in from test code. ${b("Remove the data test type")}.`);return p(n,T,i.basename(o)),{config:n,configFile:o}}function C(t){const e=r.readFileSync(t,"utf8");return g.createHash("sha256").update(t).update("\0").update(e).digest("hex")}function v(){return{name:"testkit-config-alias",setup(t){t.onResolve({filter:/^@gfdelarue\/testkit(?:\/.*)?$/},e=>({namespace:"file",path:O(e.path)}))}}}function O(t){const e=t.slice(18);if(!e)return w;if(e==="/config")return y;throw j.has(e)?new Error(`testkit.config.ts may not import engine authoring module "${t}". Use repository config helpers from @gfdelarue/testkit/config instead.`):new Error(`Unsupported @gfdelarue/testkit import "${t}" while loading testkit config`)}function x(t){if(c(t))return t.default||t.config||{}}
1
+ import g from"node:crypto";import{rejectUnknownKeys as p}from"../../kernel/known-keys.js";import{isUnknownRecord as c}from"../../kernel/unknown-values.js";import r from"node:fs";import i from"node:path";import{fileURLToPath as d,pathToFileURL as h}from"node:url";import{buildWithEsbuildRetry as k}from"../../toolchains/esbuild.js";import{migrationText as b}from"../../domain/retired-test-types.js";import{protectTestkitStateRoot as w}from"../../process/files/product-dir.js";const u=i.resolve(i.dirname(d(import.meta.url)),"..","..",".."),y=i.join(u,"dist","index.js"),j=i.join(u,"dist","config","authoring","index.js"),T=new Set(["/environment","/node-vitest/data","/playwright"]),E=["testkit.config.ts","testkit.config.mts","testkit.config.mjs","testkit.config.js"],C=Object.keys({areas:!0,artifacts:!0,bugs:!0,discovery:!0,execution:!0,fingerprints:!0,groups:!0,lint:!0,redact:!0,services:!0,toolchains:!0,ui:!0});export function findConfigFile(t){for(const e of E){const o=i.join(t,e);if(r.existsSync(o))return o}return null}export async function loadTestkitConfig(t,e={}){const o=findConfigFile(t);if(!o)return{config:{},configFile:null};w(t,!0);const s=i.join(t,".testkit","_config");r.mkdirSync(s,{recursive:!0});const a=v(o),f=i.join(s,`config-${a.slice(0,12)}.mjs`);await k({absWorkingDir:t,bundle:!0,entryPoints:[o],format:"esm",legalComments:"none",outfile:f,platform:"node",sourcemap:"inline",target:"es2020",plugins:[O()]});const l=[a,e.importCacheSalt||""].filter(Boolean).join("-"),m=await import(`${h(f).href}?v=${l}`),n=x(m);if(!c(n))throw new Error(`testkit config file must export an object: ${i.basename(o)}`);if(Object.hasOwn(n,"profiles"))throw new Error(`${i.basename(o)}: Testkit 0.6.0 removed HTTP profiles; log in from test code. ${b("Remove the data test type")}.`);return p(n,C,i.basename(o)),{config:n,configFile:o}}function v(t){const e=r.readFileSync(t,"utf8");return g.createHash("sha256").update(t).update("\0").update(e).digest("hex")}function O(){return{name:"testkit-config-alias",setup(t){t.onResolve({filter:/^@gfdelarue\/testkit(?:\/.*)?$/},e=>({namespace:"file",path:S(e.path)}))}}}function S(t){const e=t.slice(18);if(!e)return y;if(e==="/config")return j;throw T.has(e)?new Error(`testkit.config.ts may not import engine authoring module "${t}". Use repository config helpers from @gfdelarue/testkit/config instead.`):new Error(`Unsupported @gfdelarue/testkit import "${t}" while loading testkit config`)}function x(t){if(c(t))return t.default||t.config||{}}