@gfdelarue/testkit 0.5.0 → 0.6.1

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 (215) hide show
  1. package/README.md +590 -165
  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 +2 -2
  11. package/dist/app/bugs/run-reading.js +1 -1
  12. package/dist/app/bugs/store.js +1 -1
  13. package/dist/app/campaigns/index.js +2 -2
  14. package/dist/app/campaigns/summary.js +1 -1
  15. package/dist/app/cleanup/index.js +1 -1
  16. package/dist/app/db-schema/prepare.js +1 -1
  17. package/dist/app/db-schema/refresh.js +1 -1
  18. package/dist/app/db-schema/verify.js +1 -1
  19. package/dist/app/destroy/index.js +1 -1
  20. package/dist/app/discover/index.js +2 -2
  21. package/dist/app/environments/config-driven.js +1 -1
  22. package/dist/app/environments/file-setup.js +1 -1
  23. package/dist/app/environments/routing.js +1 -1
  24. package/dist/app/environments/static-values.js +1 -1
  25. package/dist/app/environments/supplied.js +1 -1
  26. package/dist/app/inspect/index.js +1 -1
  27. package/dist/app/lint/native.js +1 -1
  28. package/dist/app/lint/options.js +1 -1
  29. package/dist/app/lint/shared.js +1 -1
  30. package/dist/app/run/engine-orchestration.js +1 -1
  31. package/dist/app/run/environment.js +1 -1
  32. package/dist/app/run/live-run.js +1 -1
  33. package/dist/app/run/orchestrator.js +1 -1
  34. package/dist/app/run/planning.js +1 -1
  35. package/dist/app/run/request.js +1 -1
  36. package/dist/app/run/results.js +1 -1
  37. package/dist/app/run/run-finalization.js +1 -1
  38. package/dist/app/run/run-finisher.js +1 -0
  39. package/dist/app/run/setup/runtime-node-setup-protocol.js +1 -1
  40. package/dist/app/run/setup/runtime-node-setup-worker.js +1 -1
  41. package/dist/app/run/setup/runtime-node-setup.js +2 -2
  42. package/dist/app/run/task-batches.js +1 -1
  43. package/dist/app/run/worker-loop.js +1 -1
  44. package/dist/app/status/model.js +1 -1
  45. package/dist/app/status/runs.js +1 -1
  46. package/dist/app/stop/index.js +1 -1
  47. package/dist/app/tracker/index.js +1 -1
  48. package/dist/app/tracker/token.js +2 -2
  49. package/dist/app/typecheck/index.js +4 -4
  50. package/dist/cli/command-catalog.js +1 -1
  51. package/dist/cli/command-flags.js +1 -1
  52. package/dist/cli/commands/artifacts.js +2 -2
  53. package/dist/cli/commands/bug/file.js +3 -2
  54. package/dist/cli/commands/discover.js +1 -1
  55. package/dist/cli/commands/guard/unsafe-entrypoint.js +1 -1
  56. package/dist/cli/commands/run.js +2 -2
  57. package/dist/cli/commands/typecheck.js +1 -1
  58. package/dist/cli/commands/workspace.js +1 -1
  59. package/dist/cli/framework/help.js +5 -5
  60. package/dist/cli/framework/parser.js +1 -1
  61. package/dist/cli/json-errors.js +1 -1
  62. package/dist/cli/renderers/bug/text.js +1 -1
  63. package/dist/cli/renderers/run/formatting.js +1 -1
  64. package/dist/config/authoring/database-fixture-discovery.js +1 -0
  65. package/dist/config/authoring/index.js +1 -1
  66. package/dist/config/loader/config-loader.js +1 -1
  67. package/dist/config/loader/decode-discovery.js +1 -1
  68. package/dist/config/loader/discover-tests.js +1 -1
  69. package/dist/config/loader/discovery-result.js +1 -0
  70. package/dist/config/loader/discovery.js +2 -2
  71. package/dist/config/loader/file-metadata.js +1 -1
  72. package/dist/config/loader/groups.js +2 -1
  73. package/dist/config/loader/index.js +1 -1
  74. package/dist/config/loader/load.js +1 -1
  75. package/dist/config/loader/managed-configs.js +1 -1
  76. package/dist/config/loader/owned-discovery.js +1 -1
  77. package/dist/config/loader/runtime-isolation.js +1 -1
  78. package/dist/config/loader/skip-config.js +1 -1
  79. package/dist/config/loader/test-selection.js +1 -1
  80. package/dist/database/admin.js +5 -2
  81. package/dist/database/call-caps.js +1 -0
  82. package/dist/database/cleanup.js +1 -1
  83. package/dist/database/configured-steps.js +1 -1
  84. package/dist/database/constants.js +1 -1
  85. package/dist/database/docker-endpoint.js +1 -0
  86. package/dist/database/docker-metadata.js +2 -2
  87. package/dist/database/docker.js +5 -5
  88. package/dist/database/index.js +2 -1
  89. package/dist/database/lifecycle-cleanup.js +1 -1
  90. package/dist/database/local-postgres.js +4 -1
  91. package/dist/database/naming.js +1 -1
  92. package/dist/database/operation-deadline.js +1 -0
  93. package/dist/database/postgres/connection-check.js +1 -1
  94. package/dist/database/postgres/index.js +2 -2
  95. package/dist/database/postgres/owned-exec.js +27 -0
  96. package/dist/database/postgres/protocol.js +2 -2
  97. package/dist/database/provider.js +1 -0
  98. package/dist/database/roles.js +2 -2
  99. package/dist/database/schema-operations.js +5 -1
  100. package/dist/database/schema-source.js +3 -3
  101. package/dist/database/steps.js +1 -1
  102. package/dist/database/template-steps.js +4 -4
  103. package/dist/domain/identifiers.js +1 -1
  104. package/dist/domain/retired-test-types.js +1 -1
  105. package/dist/domain/suite-selection.js +1 -1
  106. package/dist/domain/test-types.js +1 -1
  107. package/dist/engines/adapters/evidence-files.js +1 -1
  108. package/dist/engines/adapters/execution.js +1 -1
  109. package/dist/engines/catalog.js +1 -1
  110. package/dist/engines/definitions.js +1 -1
  111. package/dist/engines/environment/index.js +1 -1
  112. package/dist/engines/installations/node-vitest.js +1 -1
  113. package/dist/engines/installations/playwright.js +1 -1
  114. package/dist/engines/managed-module-resolution.js +6 -2
  115. package/dist/engines/node-vitest/adapter/adapter.js +1 -1
  116. package/dist/engines/node-vitest/adapter/driver.js +2 -2
  117. package/dist/engines/node-vitest/adapter/evidence.js +2 -2
  118. package/dist/engines/node-vitest/adapter/workspace.js +8 -24
  119. package/dist/engines/node-vitest/inventory/index.js +2 -2
  120. package/dist/engines/node-vitest/inventory/list-files.js +3 -3
  121. package/dist/engines/node-vitest/runtime/data/index.js +1 -1
  122. package/dist/engines/node-vitest/runtime/data/report.js +1 -1
  123. package/dist/engines/node-vitest/runtime/data/scenario.js +2 -2
  124. package/dist/engines/node-vitest/runtime/runtime-input.js +1 -1
  125. package/dist/engines/playwright/adapter/adapter.js +1 -1
  126. package/dist/engines/playwright/adapter/browser-network-evidence.js +4 -0
  127. package/dist/engines/playwright/adapter/config.js +16 -6
  128. package/dist/engines/playwright/adapter/evidence.js +2 -1
  129. package/dist/engines/playwright/adapter/failure-details.js +2 -2
  130. package/dist/engines/playwright/adapter/reporter.js +1 -1
  131. package/dist/engines/playwright/runtime/browser-network.js +1 -0
  132. package/dist/engines/playwright/runtime/browser-traffic.js +1 -0
  133. package/dist/engines/playwright/runtime/chromium-environment.js +1 -0
  134. package/dist/engines/playwright/runtime/emulation.js +1 -0
  135. package/dist/engines/playwright/runtime/index.js +1 -1
  136. package/dist/engines/playwright/runtime/managed-test.js +1 -0
  137. package/dist/engines/playwright/runtime/runtime-input.js +2 -2
  138. package/dist/environment/lifecycle-controller.js +1 -1
  139. package/dist/environment/lifecycle.js +1 -1
  140. package/dist/environment/maintenance.js +1 -1
  141. package/dist/environment/run-environment.js +1 -1
  142. package/dist/environment/run-stopping.js +1 -1
  143. package/dist/environment/runtime-manager.js +1 -1
  144. package/dist/evidence/artifact-config.js +1 -1
  145. package/dist/evidence/artifacts.js +5 -2
  146. package/dist/evidence/bundles.js +2 -1
  147. package/dist/evidence/compressed-artifacts.js +1 -0
  148. package/dist/evidence/environment-record.js +1 -1
  149. package/dist/evidence/index.js +1 -1
  150. package/dist/evidence/live-run-summary.js +1 -0
  151. package/dist/evidence/local-paths.js +1 -0
  152. package/dist/evidence/maintenance.js +1 -0
  153. package/dist/evidence/operation-timings.js +1 -1
  154. package/dist/evidence/read-run.js +1 -1
  155. package/dist/evidence/redaction.js +4 -4
  156. package/dist/evidence/run-evidence.js +1 -0
  157. package/dist/evidence/setup-progress.js +1 -1
  158. package/dist/evidence/storage-inventory.js +1 -1
  159. package/dist/evidence/storage.js +2 -2
  160. package/dist/kernel/known-keys.js +1 -0
  161. package/dist/kernel/redaction-targets.js +1 -1
  162. package/dist/kernel/redaction.js +7 -4
  163. package/dist/process/engine-environment-keys.js +1 -0
  164. package/dist/process/environment-overlay.js +1 -1
  165. package/dist/process/environment.js +1 -1
  166. package/dist/process/execa-bundle.json +3 -3
  167. package/dist/process/files/evidence-writes.js +1 -0
  168. package/dist/process/files/product-dir.js +1 -1
  169. package/dist/process/host-process.js +1 -1
  170. package/dist/process/index.js +1 -1
  171. package/dist/process/lifecycle-fence.js +1 -1
  172. package/dist/process/lock-progress.js +2 -0
  173. package/dist/process/locks.js +4 -4
  174. package/dist/process/process-identity.js +1 -1
  175. package/dist/process/process.js +35 -35
  176. package/dist/process/recovery.js +1 -1
  177. package/dist/process/runs/manifest.js +1 -1
  178. package/dist/process/runs/ownership.js +1 -1
  179. package/dist/process/runs/stop.js +1 -1
  180. package/dist/process/shutdown.js +1 -1
  181. package/dist/process/windows-native.js +1 -1
  182. package/dist/repo/fingerprint-policy.js +1 -1
  183. package/dist/repo/history.js +7 -4
  184. package/dist/repo/managed-git.js +1 -1
  185. package/dist/repo/state.js +4 -4
  186. package/dist/steps/configured-step-testkit-modules.js +1 -1
  187. package/dist/steps/environment.js +1 -1
  188. package/dist/toolchains/index.js +2 -2
  189. package/dist/toolchains/node-provisioning.js +2 -2
  190. package/dist/toolchains/vitest/index.js +1 -1
  191. package/dist/tracker/gitea.js +1 -1
  192. package/dist/tracker/github.js +1 -1
  193. package/dist/tracker/http.js +1 -1
  194. package/dist/tracker/ownership.js +1 -0
  195. package/dist/tracker/redaction.js +1 -1
  196. package/dist/tracker/responses.js +1 -1
  197. package/dist/types/config.d.ts +7 -86
  198. package/dist/types/environment.d.ts +6 -0
  199. package/dist/types/node-vitest-data.d.ts +2 -218
  200. package/dist/types/playwright.d.ts +21 -0
  201. package/dist/types/workspace.d.ts +30 -14
  202. package/docs/bug-records.md +19 -18
  203. package/docs/engines.md +115 -21
  204. package/docs/release-notes.md +2846 -0
  205. package/docs/test-types.md +19 -69
  206. package/package.json +15 -6
  207. package/testkit-config-schema.md +101 -170
  208. package/dist/app/lint/rules/data.js +0 -1
  209. package/dist/config/authoring/runtime.js +0 -1
  210. package/dist/config/loader/task-policy.js +0 -1
  211. package/dist/engines/node-vitest/runtime/contract/index.js +0 -1
  212. package/dist/engines/node-vitest/runtime/data/http.js +0 -1
  213. package/dist/engines/node-vitest/runtime/data/runner.js +0 -1
  214. package/dist/environment/lifecycle-artifacts.js +0 -1
  215. package/dist/types/node-vitest-contract.d.ts +0 -75
@@ -1 +1 @@
1
- import{execFile as c}from"node:child_process";import p from"node:path";import{promisify as d}from"node:util";import{processCreationIdentityMatches as l}from"./process-identity.js";import{cleanupExitedProcessGroup as f,processGroupExists as m}from"./process-group.js";import{hasErrorCode as t}from"../kernel/error-serialization.js";const w=d(c);export async function terminateAttributedProcessLineage(r){if(i(r.pid),s(r.displayLabel),a(r.pid,r.expectedCreationIdentity),process.platform==="win32"){await g(r.pid,r.displayLabel);return}u(r.pid,r.signal)}export async function terminateOwnedPosixProcessGroup(r){if(process.platform==="win32")throw new Error("POSIX process-group recovery is unavailable on Windows");if(i(r.leaderPid),i(r.processGroupId),s(r.displayLabel),r.leaderPid!==r.processGroupId)throw new Error("Owned POSIX process-group leader pid must equal its process-group id");if(!Number.isFinite(r.graceMs)||!Number.isInteger(r.graceMs)||r.graceMs<0)throw new Error("Owned POSIX process-group grace must be non-negative");if(!m(r.processGroupId))return;a(r.leaderPid,r.leaderCreationIdentity);const e=await f(r.processGroupId,{graceMs:r.graceMs});if(e!==null)throw new Error(`${r.displayLabel}: ${e.message}`,{cause:e})}function i(r){if(!Number.isSafeInteger(r)||r<=0)throw new Error("Process pid must be a positive safe integer")}function s(r){if(r.trim().length===0)throw new Error("Process display label must be non-empty")}function a(r,e){if(e!==void 0&&!l(r,e))throw new Error(`Process creation identity no longer matches pid ${r.toString()}; refusing unsafe cleanup`)}function u(r,e){try{process.kill(r,e)}catch(o){if(!t(o,"ESRCH"))throw o}}async function g(r,e){const o=p.join(process.env.SystemRoot||"C:\\Windows","System32","taskkill.exe");try{await w(o,["/pid",r.toString(),"/t","/f"],{timeout:1e4,windowsHide:!0})}catch(n){if(t(n,"ESRCH"))return;throw new Error(`Could not terminate ${e} process tree`,{cause:n})}}
1
+ import{execFile as c}from"node:child_process";import p from"node:path";import{promisify as d}from"node:util";import{inspectProcessLiveness as l,observeProcessCreationIdentity as f}from"./process-identity.js";import{cleanupExitedProcessGroup as w,processGroupExists as m}from"./process-group.js";import{hasErrorCode as t}from"../kernel/error-serialization.js";const u=d(c);export async function terminateAttributedProcessLineage(r){if(i(r.pid),s(r.displayLabel),!(r.expectedCreationIdentity!==void 0&&!a(r.pid,r.expectedCreationIdentity))){if(process.platform==="win32"){await y(r.pid,r.displayLabel);return}g(r.pid,r.signal)}}export async function terminateOwnedPosixProcessGroup(r){if(process.platform==="win32")throw new Error("POSIX process-group recovery is unavailable on Windows");if(i(r.leaderPid),i(r.processGroupId),s(r.displayLabel),r.leaderPid!==r.processGroupId)throw new Error("Owned POSIX process-group leader pid must equal its process-group id");if(!Number.isFinite(r.graceMs)||!Number.isInteger(r.graceMs)||r.graceMs<0)throw new Error("Owned POSIX process-group grace must be non-negative");if(!m(r.processGroupId))return;if(!a(r.leaderPid,r.leaderCreationIdentity)){if(l(r.leaderPid)==="alive")return;throw new Error(`Process group ${r.processGroupId.toString()} outlived its leader, and its owner can't be established; refusing unsafe cleanup`)}const e=await w(r.processGroupId,{graceMs:r.graceMs});if(e!==null)throw new Error(`${r.displayLabel}: ${e.message}`,{cause:e})}function i(r){if(!Number.isSafeInteger(r)||r<=0)throw new Error("Process pid must be a positive safe integer")}function s(r){if(r.trim().length===0)throw new Error("Process display label must be non-empty")}function a(r,e){const o=f(r,e);if(o==="unknown")throw new Error(`Cannot establish the creation identity of pid ${r.toString()}; refusing unsafe cleanup`);return o==="matches"}function g(r,e){try{process.kill(r,e)}catch(o){if(!t(o,"ESRCH"))throw o}}async function y(r,e){const o=p.join(process.env.SystemRoot||"C:\\Windows","System32","taskkill.exe");try{await u(o,["/pid",r.toString(),"/t","/f"],{timeout:1e4,windowsHide:!0})}catch(n){if(t(n,"ESRCH"))return;throw new Error(`Could not terminate ${e} process tree`,{cause:n})}}
@@ -1 +1 @@
1
- import{isWorkObserved as $,recordWork as I}from"../../kernel/work-metrics.js";import i from"node:fs";import l from"node:path";import{randomUUID as F}from"node:crypto";import{publishWindowsFile as D}from"../windows-native.js";import{serializeJson as U,writeJsonFileAtomicWithRetry as S}from"../files/json-file.js";import{RUN_MAINTENANCE_STOP_REQUEST_SCHEMA_VERSION as g,RUN_MANIFEST_SCHEMA_VERSION as h}from"./schema.js";import{isNullableString as R,isUnknownRecord as m}from"../../kernel/unknown-values.js";import{hasErrorCode as T}from"../../kernel/error-serialization.js";export{g as RUN_MAINTENANCE_STOP_REQUEST_SCHEMA_VERSION,h as RUN_MANIFEST_SCHEMA_VERSION};const O=l.join(".testkit","_runs"),k=".maintenance-stop",M=128,C=/^[A-Za-z0-9][A-Za-z0-9._-]*$/u,L=/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u,j=/^(?:CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(?:\.|$)/iu;export class RunManifestCollisionError extends Error{code="TESTKIT_RUN_MANIFEST_COLLISION";manifestPath;constructor(t,e){super(`Testkit run manifest already exists at ${t}`,e),this.name="RunManifestCollisionError",this.manifestPath=t}}export class RunManifestInventoryError extends Error{code="TESTKIT_RUN_MANIFEST_INVENTORY_FAILED";manifestPath;constructor(t,e,r){super(`Cannot inventory Testkit run manifest ${t}: ${e}`,r),this.name="RunManifestInventoryError",this.manifestPath=t}}export function runManifestPath(n,t){const e=requireSafeRunId(t,"Testkit run id");return l.join(p(n),`${e}.json`)}export function writeRunManifest(n,t){const e=runManifestPath(n,t.runId);i.mkdirSync(p(n),{recursive:!0}),S(e,t)}export function prepareRunShutdownDeadlines(n,t){return{...n,services:n.services.map(e=>e.shutdownTimeoutMs===void 0?e:{...e,shutdownDeadlineAt:Math.min(e.shutdownDeadlineAt??Number.MAX_VALUE,Date.parse(t)+e.shutdownTimeoutMs)})}}export function persistRunShutdownDeadlines(n,t){S(runManifestPath(n,t.runId),t)}export function createRunManifest(n,t){const e=runManifestPath(n,t.runId),r=p(n);i.mkdirSync(r,{recursive:!0});const s=l.join(r,`.${l.basename(e)}.${process.pid.toString()}.${F()}.tmp`);i.writeFileSync(s,U(t,`run manifest ${t.runId}`),{flag:"wx"});try{process.platform==="win32"?D(s,e):i.linkSync(s,e)}catch(o){throw T(o,"EEXIST")?new RunManifestCollisionError(e,{cause:o}):o}finally{i.rmSync(s,{force:!0})}}export function writeRunMaintenanceStopRequest(n,t){const e=N(n,t.runId);i.mkdirSync(p(n),{recursive:!0}),S(e,t)}export function readRunMaintenanceStopRequest(n,t){const e=requireSafeRunId(t,"Testkit run id"),r=N(n,e);let s;try{s=JSON.parse(i.readFileSync(r,"utf8"))}catch{return null}const o=z(s);return o?.runId===e?o:null}export function removeRunMaintenanceStopRequest(n,t){i.rmSync(N(n,t),{force:!0})}export function removeRunManifestFile(n,t){const e=runManifestPath(n,t),r=p(n);i.rmSync(e,{force:!0}),removeRunMaintenanceStopRequest(n,t),i.existsSync(r)&&i.readdirSync(r).length===0&&i.rmSync(r,{recursive:!0,force:!0})}export function loadRunManifest(n,t){const e=runManifestPath(n,t);I("runs.manifestLookups");let r;try{r=i.lstatSync(e)}catch(s){if(T(s,"ENOENT"))return null;throw new RunManifestInventoryError(e,"manifest path could not be inspected",{cause:s})}if(!r.isFile())throw new RunManifestInventoryError(e,"manifest path is not a regular file");return _(e)}export function listRunManifests(n){const t=p(n);if(!i.existsSync(t))return[];const e=i.readdirSync(t,{withFileTypes:!0});return I("runs.manifestDirectoriesRead"),I("runs.manifestEntriesVisited",e.length),e.filter(r=>r.name.endsWith(".json")).map(r=>{const s=l.join(t,r.name);if(!r.isFile())throw new RunManifestInventoryError(s,"manifest path is not a regular file");return _(s)}).sort((r,s)=>(r.startedAt||"").localeCompare(s.startedAt||""))}export function requireSafeRunId(n,t){const e=A(n);if(e!==null)return e;throw new Error(`${t} must be 1-${M.toString()} characters using only ASCII letters, digits, dot, underscore, or hyphen, must start with a letter or digit, and must not use a reserved Windows device name`)}export function normalizeRunManifest(n){if(!m(n)||n.schemaVersion!==h)return null;const t=A(n.runId);if(t===null)return null;const e=n.controller,r=n.finishedAt,s=n.heartbeatAt,o=P(n.incarnationId),u=n.interruptReason,f=n.pid,d=n.processIdentity,c=n.session,a=n.startedAt,y=n.status,b=V(n.services),E=J(n.runtimeDirs);return typeof f!="number"||!Number.isSafeInteger(f)||f<0||o===null||b===null||E===null||f===0&&d!==null||f>0&&(typeof d!="string"||d.length===0)?null:{incarnationId:o,runId:t,runtimeDirs:E,schemaVersion:h,services:b,pid:f,processIdentity:typeof d=="string"?d:null,...G(e)?{controller:e}:{},...typeof r=="string"?{finishedAt:r}:{},...R(s)?{heartbeatAt:s}:{},...R(u)?{interruptReason:u}:{},...X(c)?{session:c}:{},...typeof a=="string"?{startedAt:a}:{},...typeof y=="string"?{status:y}:{}}}function N(n,t){const e=requireSafeRunId(t,"Testkit run id");return l.join(p(n),`${e}${k}`)}function z(n){if(!m(n)||n.schemaVersion!==g)return null;const t=A(n.runId),e=P(n.targetIncarnationId);return t===null||e===null||!x(n.coordinatorPid)||typeof n.coordinatorProcessIdentity!="string"||n.coordinatorProcessIdentity.length===0||typeof n.reason!="string"||n.reason.length===0||typeof n.requestedAt!="string"||!Number.isFinite(Date.parse(n.requestedAt))||!x(n.targetPid)||typeof n.targetProcessIdentity!="string"||n.targetProcessIdentity.length===0?null:{coordinatorPid:n.coordinatorPid,coordinatorProcessIdentity:n.coordinatorProcessIdentity,reason:n.reason,requestedAt:n.requestedAt,runId:t,schemaVersion:g,targetIncarnationId:e,targetPid:n.targetPid,targetProcessIdentity:n.targetProcessIdentity}}function _(n){let t;try{if($()){const u=i.readFileSync(n);I("runs.manifestFilesRead"),I("runs.manifestBytesRead",u.byteLength),t=u.toString("utf8")}else t=i.readFileSync(n,"utf8")}catch(u){throw new RunManifestInventoryError(n,"file could not be read",{cause:u})}let e;try{e=JSON.parse(t)}catch(u){throw new RunManifestInventoryError(n,"file is not valid JSON",{cause:u})}const r=normalizeRunManifest(e);if(r!==null){const u=l.basename(n,".json");if(r.runId!==u)throw new RunManifestInventoryError(n,"embedded runId does not match the manifest filename");return r}const s=m(e)?e.schemaVersion:void 0,o=s===h?`schema-v${h.toString()} record is invalid`:`unsupported schema version ${typeof s=="number"?s.toString():"missing"}`;throw new RunManifestInventoryError(n,o)}function A(n){return typeof n=="string"&&n.length<=M&&C.test(n)&&!j.test(n)?n:null}function P(n){return typeof n=="string"&&L.test(n)?n:null}function V(n){if(!Array.isArray(n))return null;const t=[];for(const e of n){const r=q(e);if(r===null)return null;t.push(r)}return t}function q(n){if(!m(n))return null;const t=n.command,e=n.cwd,r=n.pid,s=n.processIdentity,o=n.processGroupId,u=n.runtimeLabel,f=n.serviceName,d=n.startedAt,c=n.shutdownTimeoutMs,a=n.shutdownDeadlineAt;if(c!==void 0&&(typeof c!="number"||!Number.isFinite(c)||!Number.isInteger(c)||c<=0)||a!=null&&(c===void 0||typeof a!="number"||!Number.isFinite(a)||a<0))return null;const y=W(n.ports);return typeof r!="number"||!Number.isSafeInteger(r)||r<=0||o!==null&&(typeof o!="number"||!Number.isSafeInteger(o)||o!==r)||typeof s!="string"||s.length===0||y===null?null:{...c===void 0?{}:{shutdownTimeoutMs:c},...a===void 0?{}:{shutdownDeadlineAt:a},ports:y,pid:r,processGroupId:o,processIdentity:s,...R(t)?{command:t}:{},...typeof e=="string"?{cwd:e}:{},...typeof u=="string"?{runtimeLabel:u}:{},...typeof f=="string"?{serviceName:f}:{},...typeof d=="string"?{startedAt:d}:{}}}function W(n){if(!Array.isArray(n))return null;const t=[];for(const e of n){if(!m(e))return null;const r=e.host,s=e.port;if(typeof r!="string"||typeof s!="number"||!Number.isSafeInteger(s)||s<=0||s>65535)return null;t.push({...e,host:r,port:s})}return t}function J(n){return!Array.isArray(n)||!n.every(t=>typeof t=="string")?null:n}function x(n){return typeof n=="number"&&Number.isSafeInteger(n)&&n>0}function p(n){return l.join(n,O)}function X(n){return n===null||m(n)}function G(n){return n===null?!0:m(n)?Object.values(n).every(t=>t===null||typeof t=="string"):!1}const w=String;export function formatRunSummary(n){const t=normalizeRunManifest(n);if(!t)return w(n);const e=[...new Set(t.services.map(r=>r.runtimeLabel).filter(r=>!!r))];return`${w(t.runId)} pid=${w(t.pid)}${e.length>0?` runtimes=${e.join(",")}`:""}`}
1
+ import{isWorkObserved as $,recordWork as I}from"../../kernel/work-metrics.js";import o from"node:fs";import l from"node:path";import{randomUUID as F}from"node:crypto";import{publishWindowsFile as O}from"../windows-native.js";import{serializeJson as U,writeJsonFileAtomicWithRetry as S}from"../files/json-file.js";import{RUN_MAINTENANCE_STOP_REQUEST_SCHEMA_VERSION as g,RUN_MANIFEST_SCHEMA_VERSION as h}from"./schema.js";import{isNullableString as R,isUnknownRecord as m}from"../../kernel/unknown-values.js";import{hasErrorCode as T}from"../../kernel/error-serialization.js";export{g as RUN_MAINTENANCE_STOP_REQUEST_SCHEMA_VERSION,h as RUN_MANIFEST_SCHEMA_VERSION};const k=l.join(".testkit","_runs"),C=".maintenance-stop",_=128,D=/^[A-Za-z0-9][A-Za-z0-9._-]*$/u,j=/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u,z=/^(?:CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(?:\.|$)/iu;export class RunManifestCollisionError extends Error{code="TESTKIT_RUN_MANIFEST_COLLISION";manifestPath;constructor(t,r){super(`Testkit run manifest already exists at ${t}`,r),this.name="RunManifestCollisionError",this.manifestPath=t}}export class RunManifestInventoryError extends Error{code="TESTKIT_RUN_MANIFEST_INVENTORY_FAILED";manifestPath;constructor(t,r,e){super(`Cannot inventory Testkit run manifest ${t}: ${r}`,e),this.name="RunManifestInventoryError",this.manifestPath=t}}export function runManifestPath(n,t){const r=requireSafeRunId(t,"Testkit run id");return l.join(p(n),`${r}.json`)}export function writeRunManifest(n,t){const r=runManifestPath(n,t.runId);o.mkdirSync(p(n),{recursive:!0}),S(r,t)}export function persistRunShutdownDeadlines(n,t){S(runManifestPath(n,t.runId),t)}export function createRunManifest(n,t){const r=runManifestPath(n,t.runId),e=p(n);o.mkdirSync(e,{recursive:!0});const s=l.join(e,`.${l.basename(r)}.${process.pid.toString()}.${F()}.tmp`);o.writeFileSync(s,U(t,`run manifest ${t.runId}`),{flag:"wx"});try{process.platform==="win32"?O(s,r):o.linkSync(s,r)}catch(i){throw T(i,"EEXIST")?new RunManifestCollisionError(r,{cause:i}):i}finally{o.rmSync(s,{force:!0})}}export function writeRunMaintenanceStopRequest(n,t){const r=N(n,t.runId);o.mkdirSync(p(n),{recursive:!0}),S(r,t)}export function readRunMaintenanceStopRequest(n,t){const r=requireSafeRunId(t,"Testkit run id"),e=N(n,r);let s;try{s=JSON.parse(o.readFileSync(e,"utf8"))}catch{return null}const i=L(s);return i?.runId===r?i:null}export function removeRunMaintenanceStopRequest(n,t){o.rmSync(N(n,t),{force:!0})}export function removeRunManifestFile(n,t){const r=runManifestPath(n,t),e=p(n);o.rmSync(r,{force:!0}),removeRunMaintenanceStopRequest(n,t),o.existsSync(e)&&o.readdirSync(e).length===0&&o.rmSync(e,{recursive:!0,force:!0})}export function loadRunManifest(n,t){const r=runManifestPath(n,t);I("runs.manifestLookups");let e;try{e=o.lstatSync(r)}catch(s){if(T(s,"ENOENT"))return null;throw new RunManifestInventoryError(r,"manifest path could not be inspected",{cause:s})}if(!e.isFile())throw new RunManifestInventoryError(r,"manifest path is not a regular file");return M(r)}export function listRunManifests(n){const t=p(n);if(!o.existsSync(t))return[];const r=o.readdirSync(t,{withFileTypes:!0});return I("runs.manifestDirectoriesRead"),I("runs.manifestEntriesVisited",r.length),r.filter(e=>e.name.endsWith(".json")).map(e=>{const s=l.join(t,e.name);if(!e.isFile())throw new RunManifestInventoryError(s,"manifest path is not a regular file");return M(s)}).sort((e,s)=>(e.startedAt||"").localeCompare(s.startedAt||""))}export function requireSafeRunId(n,t){const r=A(n);if(r!==null)return r;throw new Error(`${t} must be 1-${_.toString()} characters using only ASCII letters, digits, dot, underscore, or hyphen, must start with a letter or digit, and must not use a reserved Windows device name`)}export function normalizeRunManifest(n){if(!m(n)||n.schemaVersion!==h)return null;const t=A(n.runId);if(t===null)return null;const r=n.controller,e=n.finishedAt,s=n.heartbeatAt,i=P(n.incarnationId),c=n.interruptReason,a=n.pid,d=n.processIdentity,u=n.session,f=n.startedAt,y=n.status,w=V(n.services),E=J(n.runtimeDirs);return typeof a!="number"||!Number.isSafeInteger(a)||a<0||i===null||w===null||E===null||a===0&&d!==null||a>0&&(typeof d!="string"||d.length===0)?null:{incarnationId:i,runId:t,runtimeDirs:E,schemaVersion:h,services:w,pid:a,processIdentity:typeof d=="string"?d:null,...G(r)?{controller:r}:{},...typeof e=="string"?{finishedAt:e}:{},...R(s)?{heartbeatAt:s}:{},...R(c)?{interruptReason:c}:{},...X(u)?{session:u}:{},...typeof f=="string"?{startedAt:f}:{},...typeof y=="string"?{status:y}:{}}}function N(n,t){const r=requireSafeRunId(t,"Testkit run id");return l.join(p(n),`${r}${C}`)}function L(n){if(!m(n)||n.schemaVersion!==g)return null;const t=A(n.runId),r=P(n.targetIncarnationId);return t===null||r===null||!x(n.coordinatorPid)||typeof n.coordinatorProcessIdentity!="string"||n.coordinatorProcessIdentity.length===0||typeof n.reason!="string"||n.reason.length===0||typeof n.requestedAt!="string"||!Number.isFinite(Date.parse(n.requestedAt))||!x(n.targetPid)||typeof n.targetProcessIdentity!="string"||n.targetProcessIdentity.length===0?null:{coordinatorPid:n.coordinatorPid,coordinatorProcessIdentity:n.coordinatorProcessIdentity,reason:n.reason,requestedAt:n.requestedAt,runId:t,schemaVersion:g,targetIncarnationId:r,targetPid:n.targetPid,targetProcessIdentity:n.targetProcessIdentity}}function M(n){let t;try{if($()){const c=o.readFileSync(n);I("runs.manifestFilesRead"),I("runs.manifestBytesRead",c.byteLength),t=c.toString("utf8")}else t=o.readFileSync(n,"utf8")}catch(c){throw new RunManifestInventoryError(n,"file could not be read",{cause:c})}let r;try{r=JSON.parse(t)}catch(c){throw new RunManifestInventoryError(n,"file is not valid JSON",{cause:c})}const e=normalizeRunManifest(r);if(e!==null){const c=l.basename(n,".json");if(e.runId!==c)throw new RunManifestInventoryError(n,"embedded runId does not match the manifest filename");return e}const s=m(r)?r.schemaVersion:void 0,i=s===h?`schema-v${h.toString()} record is invalid`:`unsupported schema version ${typeof s=="number"?s.toString():"missing"}`;throw new RunManifestInventoryError(n,i)}function A(n){return typeof n=="string"&&n.length<=_&&D.test(n)&&!z.test(n)?n:null}function P(n){return typeof n=="string"&&j.test(n)?n:null}function V(n){if(!Array.isArray(n))return null;const t=[];for(const r of n){const e=q(r);if(e===null)return null;t.push(e)}return t}function q(n){if(!m(n))return null;const t=n.command,r=n.cwd,e=n.pid,s=n.processIdentity,i=n.processGroupId,c=n.runtimeLabel,a=n.serviceName,d=n.startedAt,u=n.shutdownTimeoutMs,f=n.shutdownDeadlineAt;if(u!==void 0&&(typeof u!="number"||!Number.isFinite(u)||!Number.isInteger(u)||u<=0)||f!=null&&(u===void 0||typeof f!="number"||!Number.isFinite(f)||f<0))return null;const y=W(n.ports);return typeof e!="number"||!Number.isSafeInteger(e)||e<=0||i!==null&&(typeof i!="number"||!Number.isSafeInteger(i)||i!==e)||typeof s!="string"||s.length===0||y===null?null:{...u===void 0?{}:{shutdownTimeoutMs:u},...f===void 0?{}:{shutdownDeadlineAt:f},ports:y,pid:e,processGroupId:i,processIdentity:s,...R(t)?{command:t}:{},...typeof r=="string"?{cwd:r}:{},...typeof c=="string"?{runtimeLabel:c}:{},...typeof a=="string"?{serviceName:a}:{},...typeof d=="string"?{startedAt:d}:{}}}function W(n){if(!Array.isArray(n))return null;const t=[];for(const r of n){if(!m(r))return null;const e=r.host,s=r.port;if(typeof e!="string"||typeof s!="number"||!Number.isSafeInteger(s)||s<=0||s>65535)return null;t.push({...r,host:e,port:s})}return t}function J(n){return!Array.isArray(n)||!n.every(t=>typeof t=="string")?null:n}function x(n){return typeof n=="number"&&Number.isSafeInteger(n)&&n>0}function p(n){return l.join(n,k)}function X(n){return n===null||m(n)}function G(n){return n===null?!0:m(n)?Object.values(n).every(t=>t===null||typeof t=="string"):!1}const b=String;export function formatRunSummary(n){const t=normalizeRunManifest(n);if(!t)return b(n);const r=[...new Set(t.services.map(e=>e.runtimeLabel).filter(e=>!!e))];return`${b(t.runId)} pid=${b(t.pid)}${r.length>0?` runtimes=${r.join(",")}`:""}`}
@@ -1 +1 @@
1
- import f from"node:fs";import l from"node:path";import{observeProcessCreationIdentity as p}from"../index.js";import{isTerminalRunStatus as m,RUN_MANIFEST_SCHEMA_VERSION as d}from"./schema.js";import{readDynamicProperty as t}from"../../kernel/unknown-values.js";import{readJsonFileOrNull as I}from"../files/json-file.js";export function hasActiveRunManifest(e,n=p){return hasActiveRunManifestWithTerminalAuthority(e,n)}export function hasActiveRunManifestWithTerminalAuthority(e,n,s){const u=l.join(e,".testkit","_runs"),a=v(u);let c=null;if(s!==void 0){const o=`${s.runId}.json`,r=a.filter(i=>i.fileName===o&&h(i.value,s));if(r.length!==1)return!0;c=r[0]}for(const o of a){if(o===c)continue;if(t(o.value,"schemaVersion")!==d)return!0;const r=t(o.value,"pid"),i=t(o.value,"processIdentity");if(!(r===0&&i===null)&&(typeof r!="number"||!Number.isSafeInteger(r)||r<=0||typeof i!="string"||i.length===0||n(r,i)!=="different"))return!0}return!1}function h(e,n){const s=t(e,"status"),u=t(e,"services");return t(e,"schemaVersion")===d&&t(e,"runId")===n.runId&&t(e,"incarnationId")===n.incarnationId&&t(e,"pid")===n.pid&&t(e,"processIdentity")===n.processIdentity&&t(e,"finishedAt")===n.finishedAt&&s===n.status&&m(s)&&Array.isArray(u)&&u.length===0}function v(e){return f.existsSync(e)?f.readdirSync(e,{withFileTypes:!0}).filter(n=>n.name.endsWith(".json")).map(n=>({fileName:n.name,value:n.isFile()?I(l.join(e,n.name)):null})):[]}
1
+ import s from"node:fs";import f from"node:path";import{observeProcessCreationIdentity as m}from"../index.js";import{RUN_MANIFEST_SCHEMA_VERSION as a}from"./schema.js";import{readDynamicProperty as o}from"../../kernel/unknown-values.js";import{readJsonFileOrNull as p}from"../files/json-file.js";export function hasActiveRunManifest(r,e=m){const u=f.join(r,".testkit","_runs");for(const i of c(u)){if(o(i,"schemaVersion")!==a)return!0;const t=o(i,"pid"),n=o(i,"processIdentity");if(!(t===0&&n===null)&&(typeof t!="number"||!Number.isSafeInteger(t)||t<=0||typeof n!="string"||n.length===0||e(t,n)!=="different"))return!0}return!1}function c(r){return s.existsSync(r)?s.readdirSync(r,{withFileTypes:!0}).filter(e=>e.name.endsWith(".json")).map(e=>e.isFile()?p(f.join(r,e.name)):null):[]}
@@ -1 +1 @@
1
- import{isTerminalRunStatus as h}from"./schema.js";import{observeProcessCreationIdentity as a}from"../index.js";import{withProductLifecycleFence as c}from"../lifecycle-fence.js";import{terminateAttributedProcessLineage as f,terminateOwnedPosixProcessGroup as T}from"../service-processes.js";import{listRunManifests as d}from"./manifest.js";import{createShutdownDeadline as I,remainingShutdownMs as A,FORCE_TERMINATION_WAIT_MS as p}from"../shutdown.js";import{hasErrorCode as S}from"../../kernel/error-serialization.js";import{delay as M}from"../process-group.js";const m=5e3,E=5e3;export function isPidRunning(t){if(!Number.isInteger(Number(t))||Number(t)<=0)return!1;try{return process.kill(Number(t),0),!0}catch(n){return S(n,"EPERM")}}export async function waitForPreparedRunStop(t){if(t.controllerActive&&t.targetProcessIdentity!==null){const i=await D(t,t.targetProcessIdentity);let s=i,r=i;if(!s&&!r&&(await f(t.pid,"SIGKILL",t.targetProcessIdentity),s=await y(t.pid,m,t.targetProcessIdentity),r=!s&&await b(t)),!s&&!r)throw new Error(`Timed out stopping Testkit run process ${t.pid.toString()}`)}const n=new Set,e=await c(t.productDir,()=>d(t.productDir).find(i=>i.runId===t.runId)),o=new Map(t.manifest.services.map(i=>[runManifestServiceKey(i),i]));if(e&&sameRunManifestIncarnation(e,t.manifest))for(const i of e.services){const s=runManifestServiceKey(i),r=o.get(s);o.set(s,r?.shutdownDeadlineAt==null?i:{...i,shutdownTimeoutMs:r.shutdownTimeoutMs,shutdownDeadlineAt:Math.min(r.shutdownDeadlineAt,i.shutdownDeadlineAt??Number.MAX_VALUE)})}return await drainRunServices([...o.values()],n),n}async function D(t,n){const e=new Map,o=r=>{for(const u of r){const w=runManifestServiceKey(u),l=u.shutdownTimeoutMs??t.graceMs,g=e.get(w)??I(l+p);g.begin((u.shutdownDeadlineAt??Date.parse(t.requestedAt)+l)+p),e.set(w,g)}return Math.max(0,...r.map(u=>e.get(runManifestServiceKey(u))?.remainingMs()??0))};o(t.manifest.services);const i=I(t.graceMs);i.begin(Date.parse(t.requestedAt)+t.graceMs);let s=null;for(;;){if(a(t.pid,n)==="different")return!0;const r=await c(t.productDir,()=>d(t.productDir).find(u=>u.runId===t.runId));if(!r||!sameRunManifestIncarnation(r,t.manifest)||h(r.status))return!0;if(Math.max(i.remainingMs(),o(r.services))<=0){if(s===null){if(process.platform!=="win32")try{await f(t.pid,"SIGTERM",n)}catch{}s=performance.now()}if(performance.now()-s>=E)return!1}await M(100)}}async function b(t){return c(t.productDir,()=>{const n=d(t.productDir).find(e=>e.runId===t.runId);return n===void 0||!sameRunManifestIncarnation(n,t.manifest)||h(n.status)})}export async function drainRunServices(t,n){for(const e of[...t].reverse())await P(e),n.add(runManifestServiceKey(e))}async function P(t){const n=t.pid;if(!Number.isSafeInteger(n)||n<=0)return;const e=t.processIdentity;if(typeof e!="string"||e.length===0)throw new Error(`Application service process ${n.toString()} has no creation identity; refusing unsafe cleanup`);const o=Number(t.processGroupId);if(process.platform!=="win32"&&Number.isSafeInteger(o)&&o>0&&o===n){await T(o,e,t.shutdownTimeoutMs===void 0?m:t.shutdownDeadlineAt==null?t.shutdownTimeoutMs:A(t.shutdownTimeoutMs,t.shutdownDeadlineAt));return}if(process.platform!=="win32")throw new Error(`Application service process ${n.toString()} does not have an owned POSIX process group; refusing unsafe cleanup`);const i=a(n,e);if(i!=="matches"){if(i==="different"&&!isPidRunning(n))return;throw new Error(i==="different"?`Application service process creation identity no longer matches pid ${n.toString()}; refusing unsafe cleanup`:`Cannot establish the creation identity of application service process ${n.toString()}; refusing unsafe cleanup`)}if(await f(n,"SIGKILL",e),!await y(n,m,e))throw new Error(`Timed out stopping application service process ${n.toString()}`)}export function requestProcessStop(t,n,e){const o=a(t,n);if(o!=="matches"){if(o==="different"&&!isPidRunning(t))return;throw new Error(o==="different"?`Testkit run process creation identity no longer matches pid ${t.toString()}; refusing unsafe stop request`:`Cannot establish the creation identity of Testkit run process ${t.toString()}; refusing unsafe stop request`)}try{process.kill(t,e)}catch(i){if(!S(i,"ESRCH"))throw i}}async function y(t,n,e){const o=()=>e===void 0?isPidRunning(t):a(t,e)!=="different",i=Date.now();for(;Date.now()-i<n;){if(!o())return!0;await M(100)}return!o()}export function runManifestServiceKey(t){return`${t.pid.toString()}:${String(t.processGroupId)}:${t.processIdentity}`}export function sameRunManifestIncarnation(t,n){return t.runId===n.runId&&t.incarnationId===n.incarnationId&&t.pid===n.pid&&t.processIdentity===n.processIdentity}
1
+ import{isTerminalRunStatus as g}from"./schema.js";import{observeProcessCreationIdentity as a}from"../index.js";import{withProductLifecycleFence as c}from"../lifecycle-fence.js";import{terminateAttributedProcessLineage as f,terminateOwnedPosixProcessGroup as T}from"../service-processes.js";import{listRunManifests as d}from"./manifest.js";import{createShutdownDeadline as h,remainingShutdownMs as A,FORCE_TERMINATION_WAIT_MS as M}from"../shutdown.js";import{hasErrorCode as S}from"../../kernel/error-serialization.js";import{delay as y}from"../process-group.js";const w=5e3,E=5e3;export function isPidRunning(t){if(!Number.isInteger(Number(t))||Number(t)<=0)return!1;try{return process.kill(Number(t),0),!0}catch(n){return S(n,"EPERM")}}export async function waitForPreparedRunStop(t){if(t.controllerActive&&t.targetProcessIdentity!==null){const i=await D(t,t.targetProcessIdentity);let s=i,r=i;if(!s&&!r&&(await f(t.pid,"SIGKILL",t.targetProcessIdentity),s=await p(t.pid,w,t.targetProcessIdentity),r=!s&&await b(t)),!s&&!r)throw new Error(`Timed out stopping Testkit run process ${t.pid.toString()}`)}const n=new Set,e=await c(t.productDir,()=>d(t.productDir).find(i=>i.runId===t.runId)),o=new Map(t.manifest.services.map(i=>[runManifestServiceKey(i),i]));if(e&&sameRunManifestIncarnation(e,t.manifest))for(const i of e.services){const s=runManifestServiceKey(i),r=o.get(s);o.set(s,r?.shutdownDeadlineAt==null?i:{...i,shutdownTimeoutMs:r.shutdownTimeoutMs,shutdownDeadlineAt:Math.min(r.shutdownDeadlineAt,i.shutdownDeadlineAt??Number.MAX_VALUE)})}return await drainRunServices([...o.values()],n),n}async function D(t,n){const e=new Map,o=r=>{for(const u of r){const m=runManifestServiceKey(u),l=u.shutdownTimeoutMs??t.graceMs,I=e.get(m)??h(l+M);I.begin((u.shutdownDeadlineAt??Date.parse(t.requestedAt)+l)+M),e.set(m,I)}return Math.max(0,...r.map(u=>e.get(runManifestServiceKey(u))?.remainingMs()??0))};o(t.manifest.services);const i=h(t.graceMs);i.begin(Date.parse(t.requestedAt)+t.graceMs);let s=null;for(;;){if(a(t.pid,n)==="different")return!0;const r=await c(t.productDir,()=>d(t.productDir).find(u=>u.runId===t.runId));if(!r||!sameRunManifestIncarnation(r,t.manifest)||g(r.status))return!0;if(Math.max(i.remainingMs(),o(r.services))<=0){if(s===null){if(process.platform!=="win32")try{await f(t.pid,"SIGTERM",n)}catch{}s=performance.now()}if(performance.now()-s>=E)return!1}await y(100)}}async function b(t){return c(t.productDir,()=>{const n=d(t.productDir).find(e=>e.runId===t.runId);return n===void 0||!sameRunManifestIncarnation(n,t.manifest)||g(n.status)})}export async function drainRunServices(t,n){for(const e of[...t].reverse())await P(e),n.add(runManifestServiceKey(e))}async function P(t){const n=t.pid;if(!Number.isSafeInteger(n)||n<=0)return;const e=t.processIdentity;if(typeof e!="string"||e.length===0)throw new Error(`Application service process ${n.toString()} has no creation identity; refusing unsafe cleanup`);const o=Number(t.processGroupId);if(process.platform!=="win32"&&Number.isSafeInteger(o)&&o>0&&o===n){await T(o,e,t.shutdownTimeoutMs===void 0?w:t.shutdownDeadlineAt==null?t.shutdownTimeoutMs:A(t.shutdownTimeoutMs,t.shutdownDeadlineAt));return}if(process.platform!=="win32")throw new Error(`Application service process ${n.toString()} does not have an owned POSIX process group; refusing unsafe cleanup`);const i=a(n,e);if(i!=="different"){if(i==="unknown")throw new Error(`Cannot establish the creation identity of application service process ${n.toString()}; refusing unsafe cleanup`);if(await f(n,"SIGKILL",e),!await p(n,w,e))throw new Error(`Timed out stopping application service process ${n.toString()}`)}}export function requestProcessStop(t,n,e){const o=a(t,n);if(o!=="different"){if(o==="unknown")throw new Error(`Cannot establish the creation identity of Testkit run process ${t.toString()}; refusing unsafe stop request`);try{process.kill(t,e)}catch(i){if(!S(i,"ESRCH"))throw i}}}async function p(t,n,e){const o=()=>e===void 0?isPidRunning(t):a(t,e)!=="different",i=Date.now();for(;Date.now()-i<n;){if(!o())return!0;await y(100)}return!o()}export function runManifestServiceKey(t){return`${t.pid.toString()}:${String(t.processGroupId)}:${t.processIdentity}`}export function sameRunManifestIncarnation(t,n){return t.runId===n.runId&&t.incarnationId===n.incarnationId&&t.pid===n.pid&&t.processIdentity===n.processIdentity}
@@ -1 +1 @@
1
- export const DEFAULT_SHUTDOWN_TIMEOUT_MS=1e3,FORCE_TERMINATION_WAIT_MS=3e3;export function remainingShutdownMs(e,n,t=Date.now()){return Math.max(0,Math.min(e,n-t))}export function createShutdownDeadline(e){let n=null,t=0,a=e;const u=()=>n===null?e:Math.max(0,a-(performance.now()-t));return{get deadlineAt(){return n},remainingMs:u,begin(r){if(n!==null&&(r===void 0||r>=n))return!1;const o=Date.now();return a=Math.min(u(),r===void 0?e:remainingShutdownMs(e,r,o)),t=performance.now(),n=Math.min(n??Number.MAX_VALUE,r??Number.MAX_VALUE,o+a),!0}}}
1
+ export const DEFAULT_SHUTDOWN_TIMEOUT_MS=1e3,FORCE_TERMINATION_WAIT_MS=3e3,PROCESS_STOP_LIMIT_MS=15e3;export function remainingShutdownMs(e,n,t=Date.now()){return Math.max(0,Math.min(e,n-t))}export function createShutdownDeadline(e){let n=null,t=0,a=e;const o=()=>n===null?e:Math.max(0,a-(performance.now()-t));return{get deadlineAt(){return n},remainingMs:o,begin(r){if(n!==null&&(r===void 0||r>=n))return!1;const u=Date.now();return a=Math.min(o(),r===void 0?e:remainingShutdownMs(e,r,u)),t=performance.now(),n=Math.min(n??Number.MAX_VALUE,r??Number.MAX_VALUE,u+a),!0}}}
@@ -1 +1 @@
1
- import{execFile as p,execFileSync as u}from"node:child_process";import l from"node:fs";import i from"node:path";import{fileURLToPath as w}from"node:url";export function windowsOwnerExecutable(){if(!["x64","arm64"].includes(process.arch))throw new Error(`Unsupported Testkit Windows architecture: ${process.arch}`);return i.resolve(i.dirname(w(import.meta.url)),"../../bin",`testkit-windows-owner-${process.arch}.exe`)}export function publishWindowsFile(e,t){const n=l.lstatSync(e,{bigint:!0});try{u(windowsOwnerExecutable(),["publish-file",i.toNamespacedPath(i.resolve(e)),i.toNamespacedPath(i.resolve(t))],{encoding:"utf8",maxBuffer:16*1024,stdio:["ignore","pipe","pipe"],timeout:5e3,windowsHide:!0})}catch(r){if(typeof r=="object"&&r!==null&&Reflect.get(r,"status")===17)throw Object.assign(new Error("File already exists",{cause:r}),{code:"EEXIST"});try{const o={bigint:!0,throwIfNoEntry:!1},c=l.lstatSync(e,o),s=l.lstatSync(t,o);if(!c&&n.isFile()&&s?.isFile()&&sameFileAfterRename(n,s))return}catch{}throw r}}const f=2,E=3,h=new Map([[5,"EACCES"],[17,"EXDEV"],[32,"EBUSY"],[80,"EEXIST"],[183,"EEXIST"],[303,"EPERM"]]);export function retireWindowsClaim(e,t,n){try{return u(windowsOwnerExecutable(),["retire-claim",i.toNamespacedPath(i.resolve(e)),i.toNamespacedPath(i.resolve(t))],{encoding:"utf8",maxBuffer:16*1024,stdio:["ignore","pipe","pipe"],timeout:5e3,windowsHide:!0}),"moved"}catch(r){const o=typeof r=="object"&&r!==null&&Reflect.get(r,"status")===2?parseWindowsClaimRetirementFailure(String(Reflect.get(r,"stdout"))):null;if(o!==null){if(isAbsentClaimFailure(o))return"absent";throw windowsClaimRetirementError(e,o,r)}try{const c={bigint:!0,throwIfNoEntry:!1},s=l.lstatSync(e,c),a=l.lstatSync(t,c);if(!s&&a!==void 0&&a.dev===n.dev&&a.ino===n.ino&&a.birthtimeNs===n.birthtimeNs)return"moved"}catch{}throw r}}export function parseWindowsClaimRetirementFailure(e){const t=/^(open|rename) ([1-9]\d*)$/u.exec(e.trim());return t===null?null:{stage:t[1]==="open"?"open":"rename",win32Error:Number(t[2])}}export function isAbsentClaimFailure({stage:e,win32Error:t}){return e==="open"&&(t===f||t===E)}export function windowsClaimRetirementError(e,t,n){const r=h.get(t.win32Error);return Object.assign(new Error(`Could not retire Windows lock claim ${e}: ${t.stage} failed with Win32 error ${t.win32Error.toString()}`,{cause:n}),{...r===void 0?{}:{code:r},syscall:t.stage,win32Error:t.win32Error})}const g=11644473600000n;export function terminateWindowsProcessTree(e,t,n){const r=(BigInt(Math.floor(t))+g)*10000n;return new Promise((o,c)=>{p(windowsOwnerExecutable(),["terminate-tree",e.toString(),r.toString(),Math.ceil(n).toString()],{encoding:"utf8",maxBuffer:16*1024,timeout:n+5e3,windowsHide:!0},(s,a,m)=>{if(s===null)o("terminated");else if(s.code===3)o("absent");else{const d=m.trim()||`Could not terminate Windows process tree ${e.toString()}`;c(new Error(d,{cause:s}))}})})}export function sameFileAfterRename(e,t){return e.ino!==0n&&e.dev===t.dev&&e.ino===t.ino&&e.size===t.size&&e.mtimeNs===t.mtimeNs}
1
+ import{execFile as d,execFileSync as u}from"node:child_process";import l from"node:fs";import i from"node:path";import{fileURLToPath as p}from"node:url";export function windowsOwnerExecutable(){if(!["x64","arm64"].includes(process.arch))throw new Error(`Unsupported Testkit Windows architecture: ${process.arch}`);return i.resolve(i.dirname(p(import.meta.url)),"../../bin",`testkit-windows-owner-${process.arch}.exe`)}export function publishWindowsFile(t,e){const n=l.lstatSync(t,{bigint:!0});try{u(windowsOwnerExecutable(),["publish-file",i.toNamespacedPath(i.resolve(t)),i.toNamespacedPath(i.resolve(e))],{encoding:"utf8",maxBuffer:16*1024,stdio:["ignore","pipe","pipe"],timeout:5e3,windowsHide:!0})}catch(r){if(typeof r=="object"&&r!==null&&Reflect.get(r,"status")===17)throw Object.assign(new Error("File already exists",{cause:r}),{code:"EEXIST"});try{const o={bigint:!0,throwIfNoEntry:!1},s=l.lstatSync(t,o),a=l.lstatSync(e,o);if(!s&&n.isFile()&&a?.isFile()&&sameFileAfterRename(n,a))return}catch{}throw r}}const w=2,f=3,E=new Map([[5,"EACCES"],[17,"EXDEV"],[32,"EBUSY"],[80,"EEXIST"],[183,"EEXIST"],[303,"EPERM"]]);export function retireWindowsClaim(t,e,n){try{return u(windowsOwnerExecutable(),["retire-claim",i.toNamespacedPath(i.resolve(t)),i.toNamespacedPath(i.resolve(e))],{encoding:"utf8",maxBuffer:16*1024,stdio:["ignore","pipe","pipe"],timeout:5e3,windowsHide:!0}),"moved"}catch(r){const o=typeof r=="object"&&r!==null&&Reflect.get(r,"status")===2?parseWindowsClaimRetirementFailure(String(Reflect.get(r,"stdout"))):null;if(o!==null){if(isAbsentClaimFailure(o))return"absent";throw windowsClaimRetirementError(t,o,r)}try{const s={bigint:!0,throwIfNoEntry:!1},a=l.lstatSync(t,s),c=l.lstatSync(e,s);if(!a&&c!==void 0&&c.dev===n.dev&&c.ino===n.ino&&c.birthtimeNs===n.birthtimeNs)return"moved"}catch{}throw r}}export function parseWindowsClaimRetirementFailure(t){const e=/^(open|rename) ([1-9]\d*)$/u.exec(t.trim());return e===null?null:{stage:e[1]==="open"?"open":"rename",win32Error:Number(e[2])}}export function isAbsentClaimFailure({stage:t,win32Error:e}){return t==="open"&&(e===w||e===f)}export function windowsClaimRetirementError(t,e,n){const r=E.get(e.win32Error);return Object.assign(new Error(`Could not retire Windows lock claim ${t}: ${e.stage} failed with Win32 error ${e.win32Error.toString()}`,{cause:n}),{...r===void 0?{}:{code:r},syscall:e.stage,win32Error:e.win32Error})}const h=11644473600000n;export function terminateWindowsProcessTree(t,e,n){return terminateWindowsProcessTreeCreatedBy(t,windowsFileTimeNotAfter(e),n)}export function windowsFileTimeNotAfter(t){return((BigInt(Math.floor(t))+h)*10000n).toString()}export function terminateWindowsProcessTreeCreatedBy(t,e,n){return new Promise((r,o)=>{d(windowsOwnerExecutable(),["terminate-tree",t.toString(),e,Math.ceil(n).toString()],{encoding:"utf8",maxBuffer:16*1024,timeout:n+5e3,windowsHide:!0},(s,a,c)=>{if(s===null)r("terminated");else if(s.code===3)r("absent");else{const m=c.trim()||`Could not terminate Windows process tree ${t.toString()}`;o(new Error(m,{cause:s}))}})})}export function sameFileAfterRename(t,e){return t.ino!==0n&&t.dev===e.dev&&t.ino===e.ino&&t.size===e.size&&t.mtimeNs===e.mtimeNs}
@@ -1 +1 @@
1
- import k from"fs";import f from"path";import{physicalNodeMode as u,readPhysicalDirectoryEntries as y,readPhysicalNode as b}from"./physical-tree.js";import{isUnknownArray as $,isUnknownRecord as w}from"../kernel/unknown-values.js";const E=[".testkit",".next-testkit"],l={exclude:[],include:[]};export function normalizeFingerprintPolicy(t={}){if(t==null)return{exclude:[],include:[]};if(!w(t))throw new Error("testkit.config.ts fingerprints must be an object");return{exclude:m(t.exclude,"fingerprints.exclude"),include:m(t.include,"fingerprints.include")}}export function shouldIgnoreCanonicalFingerprintPath(t,n=l){return shouldIgnoreCanonicalFingerprintPathAtPrefix(t,"",n)}export function shouldIgnoreCanonicalFingerprintPathAtPrefix(t,n,e=l){const r=normalizeCanonicalRelativePath(t),i=g(n,r);return!i||d(i,e.include)?!1:z(r)?!0:d(i,e.exclude)}export function hasIncludedFingerprintDescendantAtPrefix(t,n,e){const r=g(n,t);if(!r)return e.include.length>0;const i=`${r}/`;return e.include.some(c=>{const o=normalizeCanonicalRelativePath(c);if(o.includes("*")){const a=L(o);return a.length===0||r.startsWith(a)||a.startsWith(i)}return h(o).startsWith(i)})}export function appendFingerprintPathToHash(t,n,e,r=l){const i=normalizeRelativePath(n,e),c=k.realpathSync.native(f.resolve(n));P(t,c,i,r)}export function normalizeRelativePath(t,n){const e=f.isAbsolute(n)?f.relative(t,n):n;return f.isAbsolute(n)?normalizeCanonicalRelativePath(e.split(f.sep).join("/")):normalizePath(e)}export function normalizePath(t){return t.split(f.sep).join("/").replace(/\\/g,"/").replace(/^\.\/+/,"").replace(/\/+$/,"")}function m(t,n){if(t==null)return[];if(!$(t))throw new Error(`testkit.config.ts ${n} must be an array of paths`);return t.map((e,r)=>{if(typeof e!="string")throw new Error(`testkit.config.ts ${n}[${String(r)}] must be a string path`);const i=normalizeCanonicalRelativePath(e.trim());if(!i)throw new Error(`testkit.config.ts ${n}[${String(r)}] must be a non-empty path`);if(I(i))throw new Error(`testkit.config.ts ${n}[${String(r)}] must use "/" as the path separator; a literal backslash is written doubled`);if(i.startsWith("/")||/^[a-z]:\//iu.test(i)||i.startsWith("../")||i==="..")throw new Error(`testkit.config.ts ${n}[${String(r)}] must be relative to the product directory`);return i})}function d(t,n){return n.some(e=>T(t,e))}function z(t){return d(t,E)}function T(t,n){const e=normalizeCanonicalRelativePath(t),r=normalizeCanonicalRelativePath(n);if(!r)return!1;const i=h(r);if(e===i||e.startsWith(`${i}/`))return!0;if(!r.includes("*"))return!1;const c=R(r);let o=e;for(;o;){if(c.test(o))return!0;const a=o.lastIndexOf("/");if(a<0)break;o=o.slice(0,a)}return!1}export function normalizeCanonicalRelativePath(t){return t.replace(/^\.\/+/,"").replace(/\/+$/,"")}function I(t){for(let n=0;n<t.length;n+=1)if(t[n]==="\\"){if(t[n+1]!=="\\")return!0;n+=1}return!1}function h(t){return t.replace(/\\\\/g,"\\")}function L(t){let n="";for(let e=0;e<t.length;e+=1){const r=t[e];if(r==="*")break;r==="\\"&&t[e+1]==="\\"?(n+="\\",e+=1):n+=r}return n}function P(t,n,e,r){const i=b(n,e),c=shouldIgnoreCanonicalFingerprintPath(e,r);if(!(c&&(i.kind!=="directory"||!hasIncludedFingerprintDescendantAtPrefix(e,"",r)))&&(c||S(t,e,i),i.kind==="directory"))for(const o of y(n,e))P(t,n,g(e,o),r)}function S(t,n,e){switch(t.update("node\0"),s(t,"path",n),s(t,"kind",e.kind),e.kind){case"file":s(t,"mode",u(e.mode)),p(t,"contents",e.contents);break;case"directory":s(t,"mode",u(e.mode));break;case"symlink":case"ancestor-symlink":"obstaclePath"in e&&s(t,"obstacle",e.obstaclePath),p(t,"target",e.target);break;case"ancestor-missing":s(t,"obstacle",e.obstaclePath);break;case"ancestor-non-directory":s(t,"obstacle",e.obstaclePath),s(t,"mode",u(e.mode));break;case"missing":break}}function s(t,n,e){p(t,n,Buffer.from(e))}function p(t,n,e){t.update(`${n}\0${e.byteLength.toString()}\0`),t.update(e),t.update("\0")}function g(t,n){const e=normalizeCanonicalRelativePath(t),r=normalizeCanonicalRelativePath(n);return e?r?`${e}/${r}`:e:r}function R(t){let n="^";for(let e=0;e<t.length;e+=1){const r=t[e],i=t[e+1];r==="\\"&&i==="\\"?(n+=x("\\"),e+=1):r==="*"&&i==="*"?(n+=".*",e+=1):r==="*"?n+="[^/]*":n+=x(r)}return n+="$",new RegExp(n)}function x(t){return t.replace(/[\\^$+?.()|[\]{}]/g,"\\$&")}export function appendFileToHash(t,n,e,r={}){appendFingerprintPathToHash(t,n,e,normalizeFingerprintPolicy(r))}
1
+ import x from"fs";import f from"path";import{physicalNodeMode as u,readPhysicalDirectoryEntries as y,readPhysicalNode as b}from"./physical-tree.js";import{rejectUnknownKeys as w}from"../kernel/known-keys.js";import{isUnknownArray as $,isUnknownRecord as E}from"../kernel/unknown-values.js";const z=[".testkit",".next-testkit"],l={exclude:[],include:[]};export function normalizeFingerprintPolicy(t={}){if(t==null)return{exclude:[],include:[]};if(!E(t))throw new Error("testkit.config.ts fingerprints must be an object");return w(t,["exclude","include"],"testkit.config.ts fingerprints"),{exclude:m(t.exclude,"fingerprints.exclude"),include:m(t.include,"fingerprints.include")}}export function shouldIgnoreCanonicalFingerprintPath(t,n=l){return shouldIgnoreCanonicalFingerprintPathAtPrefix(t,"",n)}export function shouldIgnoreCanonicalFingerprintPathAtPrefix(t,n,e=l){const r=normalizeCanonicalRelativePath(t),i=g(n,r);return!i||d(i,e.include)?!1:T(r)?!0:d(i,e.exclude)}export function hasIncludedFingerprintDescendantAtPrefix(t,n,e){const r=g(n,t);if(!r)return e.include.length>0;const i=`${r}/`;return e.include.some(c=>{const o=normalizeCanonicalRelativePath(c);if(o.includes("*")){const a=S(o);return a.length===0||r.startsWith(a)||a.startsWith(i)}return h(o).startsWith(i)})}export function appendFingerprintPathToHash(t,n,e,r=l){const i=normalizeRelativePath(n,e),c=x.realpathSync.native(f.resolve(n));P(t,c,i,r)}export function normalizeRelativePath(t,n){const e=f.isAbsolute(n)?f.relative(t,n):n;return f.isAbsolute(n)?normalizeCanonicalRelativePath(e.split(f.sep).join("/")):normalizePath(e)}export function normalizePath(t){return t.split(f.sep).join("/").replace(/\\/g,"/").replace(/^\.\/+/,"").replace(/\/+$/,"")}function m(t,n){if(t==null)return[];if(!$(t))throw new Error(`testkit.config.ts ${n} must be an array of paths`);return t.map((e,r)=>{if(typeof e!="string")throw new Error(`testkit.config.ts ${n}[${String(r)}] must be a string path`);const i=normalizeCanonicalRelativePath(e.trim());if(!i)throw new Error(`testkit.config.ts ${n}[${String(r)}] must be a non-empty path`);if(L(i))throw new Error(`testkit.config.ts ${n}[${String(r)}] must use "/" as the path separator; a literal backslash is written doubled`);if(i.startsWith("/")||/^[a-z]:\//iu.test(i)||i.startsWith("../")||i==="..")throw new Error(`testkit.config.ts ${n}[${String(r)}] must be relative to the product directory`);return i})}function d(t,n){return n.some(e=>I(t,e))}function T(t){return d(t,z)}function I(t,n){const e=normalizeCanonicalRelativePath(t),r=normalizeCanonicalRelativePath(n);if(!r)return!1;const i=h(r);if(e===i||e.startsWith(`${i}/`))return!0;if(!r.includes("*"))return!1;const c=W(r);let o=e;for(;o;){if(c.test(o))return!0;const a=o.lastIndexOf("/");if(a<0)break;o=o.slice(0,a)}return!1}export function normalizeCanonicalRelativePath(t){return t.replace(/^\.\/+/,"").replace(/\/+$/,"")}function L(t){for(let n=0;n<t.length;n+=1)if(t[n]==="\\"){if(t[n+1]!=="\\")return!0;n+=1}return!1}function h(t){return t.replace(/\\\\/g,"\\")}function S(t){let n="";for(let e=0;e<t.length;e+=1){const r=t[e];if(r==="*")break;r==="\\"&&t[e+1]==="\\"?(n+="\\",e+=1):n+=r}return n}function P(t,n,e,r){const i=b(n,e),c=shouldIgnoreCanonicalFingerprintPath(e,r);if(!(c&&(i.kind!=="directory"||!hasIncludedFingerprintDescendantAtPrefix(e,"",r)))&&(c||R(t,e,i),i.kind==="directory"))for(const o of y(n,e))P(t,n,g(e,o),r)}function R(t,n,e){switch(t.update("node\0"),s(t,"path",n),s(t,"kind",e.kind),e.kind){case"file":s(t,"mode",u(e.mode)),p(t,"contents",e.contents);break;case"directory":s(t,"mode",u(e.mode));break;case"symlink":case"ancestor-symlink":"obstaclePath"in e&&s(t,"obstacle",e.obstaclePath),p(t,"target",e.target);break;case"ancestor-missing":s(t,"obstacle",e.obstaclePath);break;case"ancestor-non-directory":s(t,"obstacle",e.obstaclePath),s(t,"mode",u(e.mode));break;case"missing":break}}function s(t,n,e){p(t,n,Buffer.from(e))}function p(t,n,e){t.update(`${n}\0${e.byteLength.toString()}\0`),t.update(e),t.update("\0")}function g(t,n){const e=normalizeCanonicalRelativePath(t),r=normalizeCanonicalRelativePath(n);return e?r?`${e}/${r}`:e:r}function W(t){let n="^";for(let e=0;e<t.length;e+=1){const r=t[e],i=t[e+1];r==="\\"&&i==="\\"?(n+=k("\\"),e+=1):r==="*"&&i==="*"?(n+=".*",e+=1):r==="*"?n+="[^/]*":n+=k(r)}return n+="$",new RegExp(n)}function k(t){return t.replace(/[\\^$+?.()|[\]{}]/g,"\\$&")}export function appendFileToHash(t,n,e,r={}){appendFingerprintPathToHash(t,n,e,normalizeFingerprintPolicy(r))}
@@ -1,4 +1,7 @@
1
- import{requireManagedHostCommandSuccess as a}from"../process/index.js";import{runManagedGitCommand as s}from"./managed-git.js";const u=/^([0-9a-f]{40}|[0-9a-f]{64}) \d+ \d+(?: \d+)?$/u,l=/^committer-time (\d+)$/u;export async function findIntroducingCommit(e,t,n,r){const i=`${String(r[0])},${String(r[1])}`,o=await s(e,["blame","-w","-M","--porcelain","-L",i,t,"--",n]);if(o.exitCode!==0)throw new Error(`Git cannot blame lines ${String(r[0])} to ${String(r[1])} of ${n} at ${t}: ${m(o.stderr)}`);const c=newestBlamedCommit(parseBlame(o.stdout));if(c===null)throw new Error(`Git blamed no lines of ${n} at ${t}`);return c.boundary&&await f(e)?{status:"outside-history"}:{commit:c.commit,status:"found"}}export function parseBlame(e){const t=new Map;let n=null;for(const r of e.split(`
2
- `)){if(r.startsWith(" "))continue;const i=u.exec(r);if(i!==null){n=t.get(i[1])??{boundary:!1,committedAt:0},t.set(i[1],n);continue}if(n===null)continue;const o=l.exec(r);o!==null?n.committedAt=Number(o[1]):r==="boundary"&&(n.boundary=!0)}return[...t].map(([r,i])=>({commit:r,...i}))}export function newestBlamedCommit(e){let t=null;for(const n of e)(t===null||n.committedAt>t.committedAt)&&(t=n);return t}export async function listRemoteRefsContaining(e,t){return a("git for-each-ref",await s(e,["for-each-ref","--format=%(refname)","--contains",t,"refs/remotes"])).stdout.split(`
3
- `).map(i=>i.trim()).filter(i=>i.length>0&&!i.endsWith("/HEAD"))}export async function listUncommittedTrackedFiles(e){return a("git diff",await s(e,["diff","--name-only","-z","HEAD","--"])).stdout.split("\0").filter(r=>r.length>0)}export async function listFilesChangedSince(e,t){if((await s(e,["merge-base","--is-ancestor",t,"HEAD"])).exitCode!==0)return null;const r=["diff","--name-only","--diff-filter=d","-z",`${t}..HEAD`,"--"];return a("git diff",await s(e,r)).stdout.split("\0").filter(o=>o.length>0)}async function f(e){return a("git rev-parse",await s(e,["rev-parse","--is-shallow-repository"])).stdout.trim()==="true"}function m(e){return e.trim().split(`
4
- `).at(-1)??""}
1
+ import{errorMessage as M}from"../kernel/error-serialization.js";import{requireManagedHostCommandSuccess as d}from"../process/index.js";import{REPOSITORY_COMMAND_GUARD_ARGS as $,runManagedGitCommand as u}from"./managed-git.js";const A=/^([0-9a-f]{40}|[0-9a-f]{64}) \d+ \d+(?: \d+)?$/u,x=/^committer-time (\d+)$/u,C=/^([0-9a-f]{40}|[0-9a-f]{64})\trefs\/heads\/(.+)$/u,p=50;export async function findIntroducingCommit(t,n,s,e){const r=`${String(e[0])},${String(e[1])}`,i=await u(t,["blame","-w","-M","--porcelain","-L",r,n,"--",s]);if(i.exitCode!==0)throw new Error(`Git cannot blame lines ${String(e[0])} to ${String(e[1])} of ${s} at ${n}: ${H(i.stderr)}`);const f=newestBlamedCommit(parseBlame(i.stdout));if(f===null)throw new Error(`Git blamed no lines of ${s} at ${n}`);return f.boundary&&await E(t)?{status:"outside-history"}:{commit:f.commit,status:"found"}}export function parseBlame(t){const n=new Map;let s=null;for(const e of t.split(`
2
+ `)){if(e.startsWith(" "))continue;const r=A.exec(e);if(r!==null){s=n.get(r[1])??{boundary:!1,committedAt:0},n.set(r[1],s);continue}if(s===null)continue;const i=x.exec(e);i!==null?s.committedAt=Number(i[1]):e==="boundary"&&(s.boundary=!0)}return[...n].map(([e,r])=>({commit:e,...r}))}export function newestBlamedCommit(t){let n=null;for(const s of t)(n===null||s.committedAt>n.committedAt)&&(n=s);return n}export async function listRemoteRefsContaining(t,n){return d("git for-each-ref",await u(t,["for-each-ref","--format=%(refname)","--contains",n,"refs/remotes"])).stdout.split(`
3
+ `).map(r=>r.trim()).filter(r=>r.length>0&&!r.endsWith("/HEAD"))}export async function findRemoteBranchHolding(t,n,s=process.env){const e=await listRemoteRefsContaining(t,n);if(e.length===0)return{status:"missing"};const r=d("git remote",await u(t,["remote"])).stdout.split(`
4
+ `).map(a=>a.trim()).filter(a=>a.length>0).sort((a,l)=>l.length-a.length),i=new Map;for(const a of e){const l=a.slice(13),o=r.find(m=>l.startsWith(`${m}/`));o!==void 0&&i.set(o,(i.get(o)??new Set).add(l.slice(o.length+1)))}const f={env:{...s,GIT_TERMINAL_PROMPT:"0"},shallow:await E(t)},c=[];for(const[a,l]of i){const o=await T(t,a,l,n,f);if(o.status==="found")return o;o.status==="unknown"&&c.push(o)}return c.length===0?{status:"missing"}:{problems:c.flatMap(a=>a.problems),shallow:c.some(a=>a.shallow),status:"unknown"}}async function T(t,n,s,e,r){const i=await y(t,["ls-remote","--heads",n],r.env);if(typeof i=="string")return{problems:[`Git could not list the branches on ${n}: ${i}`],shallow:!1,status:"unknown"};const f=_(i.stdout);let c=!1;for(const a of[f.filter(([l])=>s.has(l)),f.filter(([l])=>!s.has(l))]){const l=[];for(const[,o]of a){const m=await b(t,o,e);if(m==="yes")return{status:"found"};m==="absent"?l.push(o):c=!0}for(let o=0;o<l.length;o+=p){const m=l.slice(o,o+p),h=await y(t,["fetch","--quiet","--no-tags","--no-write-fetch-head",n,...m],r.env);if(typeof h=="string")return{problems:[`Git could not fetch the branch tips from ${n}: ${h}`],shallow:r.shallow&&c,status:"unknown"};for(const g of m){const w=await b(t,g,e);if(w==="yes")return{status:"found"};c||=w==="no"}}}return r.shallow&&c?{problems:[],shallow:!0,status:"unknown"}:{status:"missing"}}async function y(t,n,s){try{const e=await u(t,n,{env:s});return e.exitCode===0?e:S(e.stderr)}catch(e){return M(e)}}function _(t){return t.split(`
5
+ `).flatMap(n=>{const s=C.exec(n.trim());return s===null?[]:[[s[2],s[1]]]})}async function b(t,n,s){const e=await u(t,["merge-base","--is-ancestor",s,n]);return e.exitCode===0?"yes":e.exitCode===1?"no":"absent"}export async function listUncommittedTrackedFiles(t){const n=[...$,"diff","--name-only","-z","HEAD","--"];return d("git diff",await u(t,n)).stdout.split("\0").filter(e=>e.length>0)}export async function listFilesChangedSince(t,n){if((await u(t,["merge-base","--is-ancestor",n,"HEAD"])).exitCode!==0)return null;const e=["diff","--name-only","--diff-filter=d","-z",`${n}..HEAD`,"--"];return d("git diff",await u(t,e)).stdout.split("\0").filter(i=>i.length>0)}async function E(t){return d("git rev-parse",await u(t,["rev-parse","--is-shallow-repository"])).stdout.trim()==="true"}function H(t){return t.trim().split(`
6
+ `).at(-1)??""}function S(t){return t.trim().split(`
7
+ `)[0]??""}
@@ -1 +1 @@
1
- import{runManagedHostCommand as O}from"../process/index.js";export const DEFAULT_GIT_COMMAND_TIMEOUT_MS=3e4;const n=new Set(["GIT_ALTERNATE_OBJECT_DIRECTORIES","GIT_ATTR_SOURCE","GIT_COMMON_DIR","GIT_CONFIG","GIT_CONFIG_COUNT","GIT_CONFIG_GLOBAL","GIT_CONFIG_NOSYSTEM","GIT_CONFIG_PARAMETERS","GIT_CONFIG_SYSTEM","GIT_DIFF_OPTS","GIT_DIR","GIT_EXTERNAL_DIFF","GIT_GLOB_PATHSPECS","GIT_ICASE_PATHSPECS","GIT_IMPLICIT_WORK_TREE","GIT_INDEX_FILE","GIT_LITERAL_PATHSPECS","GIT_NAMESPACE","GIT_NOGLOB_PATHSPECS","GIT_NO_REPLACE_OBJECTS","GIT_OBJECT_DIRECTORY","GIT_PREFIX","GIT_QUARANTINE_PATH","GIT_REPLACE_REF_BASE","GIT_SHALLOW_FILE","GIT_WORK_TREE"]),C=new Set(["GIT_OPTIONAL_LOCKS"]);export function runManagedGitCommand(G,T,I={}){return O({args:T,cwd:G,displayLabel:gitCommandLabel(T),env:createManagedGitEnvironment(I.env??process.env,process.platform,I.repositoryConfigOnly??!1),executable:"git",signal:I.signal,timeoutMs:I.timeoutMs??DEFAULT_GIT_COMMAND_TIMEOUT_MS})}export function createManagedGitEnvironment(G,T=process.platform,I=!1){const _={};for(const[E,t]of Object.entries(G)){const e=T==="win32"?E.toUpperCase():E;n.has(e)||/^GIT_CONFIG_(?:KEY|VALUE)_\d+$/u.test(e)||C.has(e)||e==="LANG"||e==="LC_ALL"||(_[E]=t)}if(_.LANG="C",_.LC_ALL="C",_.GIT_NO_REPLACE_OBJECTS="1",_.GIT_OPTIONAL_LOCKS="0",I){const E=T==="win32"?"NUL":"/dev/null";_.GIT_CONFIG_GLOBAL=E,_.GIT_CONFIG_NOSYSTEM="1",_.GIT_CONFIG_SYSTEM=E}return _}export function gitCommandLabel(G){const T=G.find(I=>!I.startsWith("-"));return T?`Git ${T}`:"Git command"}
1
+ import{runManagedHostCommand as O}from"../process/index.js";export const DEFAULT_GIT_COMMAND_TIMEOUT_MS=3e4,REPOSITORY_COMMAND_GUARD_ARGS=["--no-optional-locks","-c","core.fsmonitor=false"];const n=new Set(["GIT_ALTERNATE_OBJECT_DIRECTORIES","GIT_ATTR_SOURCE","GIT_COMMON_DIR","GIT_CONFIG","GIT_CONFIG_COUNT","GIT_CONFIG_GLOBAL","GIT_CONFIG_NOSYSTEM","GIT_CONFIG_PARAMETERS","GIT_CONFIG_SYSTEM","GIT_DIFF_OPTS","GIT_DIR","GIT_EXTERNAL_DIFF","GIT_GLOB_PATHSPECS","GIT_ICASE_PATHSPECS","GIT_IMPLICIT_WORK_TREE","GIT_INDEX_FILE","GIT_LITERAL_PATHSPECS","GIT_NAMESPACE","GIT_NOGLOB_PATHSPECS","GIT_NO_REPLACE_OBJECTS","GIT_OBJECT_DIRECTORY","GIT_PREFIX","GIT_QUARANTINE_PATH","GIT_REPLACE_REF_BASE","GIT_SHALLOW_FILE","GIT_WORK_TREE"]),C=new Set(["GIT_OPTIONAL_LOCKS"]);export function runManagedGitCommand(E,T,I={}){return O({args:T,cwd:E,displayLabel:gitCommandLabel(T),env:createManagedGitEnvironment(I.env??process.env,process.platform,I.repositoryConfigOnly??!1),executable:"git",signal:I.signal,timeoutMs:I.timeoutMs??DEFAULT_GIT_COMMAND_TIMEOUT_MS})}export function createManagedGitEnvironment(E,T=process.platform,I=!1){const _={};for(const[G,t]of Object.entries(E)){const e=T==="win32"?G.toUpperCase():G;n.has(e)||/^GIT_CONFIG_(?:KEY|VALUE)_\d+$/u.test(e)||C.has(e)||e==="LANG"||e==="LC_ALL"||(_[G]=t)}if(_.LANG="C",_.LC_ALL="C",_.GIT_NO_REPLACE_OBJECTS="1",_.GIT_OPTIONAL_LOCKS="0",I){const G=T==="win32"?"NUL":"/dev/null";_.GIT_CONFIG_GLOBAL=G,_.GIT_CONFIG_NOSYSTEM="1",_.GIT_CONFIG_SYSTEM=G}return _}export function gitCommandLabel(E){const T=E.find(I=>!I.startsWith("-"));return T?`Git ${T}`:"Git command"}
@@ -1,5 +1,5 @@
1
- import C from"crypto";import p from"fs";import c from"path";import{appendFingerprintPathToHash as et,hasIncludedFingerprintDescendantAtPrefix as rt,normalizeFingerprintPolicy as N,shouldIgnoreCanonicalFingerprintPathAtPrefix as W,normalizeCanonicalRelativePath as k}from"./fingerprint-policy.js";import{decodeGitPathRecord as $}from"./git-path.js";import{runManagedGitCommand as nt,gitCommandLabel as D}from"./managed-git.js";import{physicalNodeMode as _,readPhysicalNode as F,sameIdentityRecord as ot,compareStrings as y}from"./physical-tree.js";import{normalizeManagedHostEnvironment as it,requireManagedHostCommandSuccess as j,resolveManagedHostExecutableIfAvailable as at}from"../process/index.js";import{readCiPullRequestHeadSha as st,resolveCiGitReference as z}from"./ci-environment.js";import{parseGitRepository as ct}from"./remote.js";import{hasErrorCode as v}from"../kernel/error-serialization.js";export async function collectRepoState(t,e={}){const r=N(e.fingerprints),o=e.env??process.env,n=z(o);return await B(t,r,o,n)??lt(t,r,n)}export async function collectGitRepoState(t,e={}){const r=N(e.fingerprints),o=e.env??process.env;return B(t,r,o,z(o))}async function B(t,e,r,o){const n=st(r),i=at("git",c.resolve(t),it(process.env));if(!i&&!jt(t)||i&&Ft(t))return null;const a=await K(t);if(a===null)return null;const s=Y(a,t),u=await R(s.repoRoot,["rev-parse","--verify","--quiet","HEAD"]),m=await ut(s.repoRoot,u,n),f=await R(s.repoRoot,["symbolic-ref","--quiet","--short","HEAD"]),b=await R(s.repoRoot,["config","--get","remote.origin.url"]),P=u!==null&&f===null,h=await dt(s,e,u,""),G=!!h,d=u||"unborn";return{kind:G?"dirty":"commit",repoRoot:s.repoRoot,worktreePath:s.repoRoot,branch:f??(P?o.branch:null),detached:P,commitSha:u,pullRequest:o.pullRequest,pullRequestHeadSha:m,remoteUrl:b,repository:ct(b),dirty:G,dirtyFingerprint:h,contentFingerprint:null,cacheKey:h?`dirty/${d}-${h}`:`commits/${d}`}}function lt(t,e,r){const o=p.realpathSync.native(c.resolve(t)),n=Q(o,e);if(Q(o,e)!==n)throw new Error("Physical repository snapshot changed while collecting repository state");const a=n.slice(0,24);return{kind:"nogit",repoRoot:null,worktreePath:o,branch:null,detached:!1,commitSha:null,pullRequest:r.pullRequest,pullRequestHeadSha:null,remoteUrl:null,repository:null,dirty:!1,dirtyFingerprint:null,contentFingerprint:a,cacheKey:`nogit/${a}`}}async function K(t){const e=["rev-parse","--show-toplevel"],r=await A(t,e);if(r.exitCode===0){const o=r.stdout.trim();if(o.length>0)return o;throw new Error("Git rev-parse returned an empty repository root")}if(Dt(r))return null;throw j(D(e),r),new Error("Unreachable Git repository probe result")}function Y(t,e){const r=p.realpathSync.native(c.resolve(t)),o=p.realpathSync.native(c.resolve(e)),n=$t(c.relative(r,o));if(n===".."||n.startsWith("../")||c.isAbsolute(n)||process.platform==="win32"&&c.win32.isAbsolute(n))throw new Error(`Product directory must be inside its Git worktree: ${o}`);return{productDir:o,productPathspec:n?Ct(n):".",productRootRelativePath:n,repoRoot:r}}export function summarizeRepoStateForMetadata(t){return t?{kind:t.kind,cacheKey:t.cacheKey,branch:t.branch,detached:t.detached,commitSha:t.commitSha,dirty:t.dirty,dirtyFingerprint:t.dirtyFingerprint,contentFingerprint:t.contentFingerprint,pullRequest:t.pullRequest,pullRequestHeadSha:t.pullRequestHeadSha,repoRoot:t.repoRoot,repository:t.repository,worktreePath:t.worktreePath,remoteUrl:t.remoteUrl}:null}async function ut(t,e,r){if(e===null||r===null)return null;if(e===r)return r;const o=await w(t,["cat-file","commit","HEAD"]),n=[];for(const i of o.split(`
2
- `)){if(i.length===0)break;i.startsWith("parent ")&&n.push(i.slice(7))}return n.length>=2&&n.includes(r)?r:null}async function dt(t,e,r,o){const n=await q(t,e,r,o),i=await q(t,e,r,o);if(n!==i)throw new Error("Git HEAD, index, untracked inventory, submodule state, or physical snapshot changed while collecting repository state");return n?.slice(0,24)??null}async function q(t,e,r,o){await X(t.repoRoot,r);const n=C.createHash("sha256");let i=!1;const a=await wt(t.repoRoot),[s,u]=await Promise.all([V(t,a),ft(t,r,a)]),m=Rt(s),f=bt(u),b=[...new Set([...m.keys(),...f.keys()])].sort(y);for(const d of b){const g=m.get(d)??[],E=f.get(d)??[],M=g.some(x=>x.mode==="160000")||E.some(x=>x.mode==="160000");if(W(d,o,e)&&(!M||!rt(d,o,e)))continue;const tt=g.length>0?g[0]:E[0],H=F(t.productDir,d),U=M?await ht(t,tt,H,e,o):null;(!gt(g,E)||!mt(g,H,U,a))&&(i=!0,yt(n,d,E,g,H,U,a))}const P=await w(t.repoRoot,["ls-files","--others","--exclude-per-directory=.gitignore","--",t.productPathspec]),h=xt(t,T(P,"Git ls-files").map(d=>$(d))).filter(d=>!W(d.productRelativePath,o,e));if(h.length>0){i=!0,n.update("untracked-state-v2\0");for(const d of h)l(n,"path",d.productRelativePath),L(n,F(t.productDir,d.productRelativePath),a)}const G=await V(t,a);if(!Et(s,G))throw new Error("Git index changed while collecting repository state");return await X(t.repoRoot,r),i?n.digest("hex"):null}async function V(t,e){const r=await w(t.repoRoot,["ls-files","--stage","--abbrev=64","--",t.productPathspec]);return pt(t,r,e)}function pt(t,e,r){const o=[];for(const n of T(e,"Git ls-files")){const i=n.indexOf(" ");if(i<0)throw new Error("Git ls-files returned malformed stage metadata");const a=n.slice(0,i).split(" "),s=k($(n.slice(i+1)));if(a.length!==3||!/^[0-7]{6}$/u.test(a[0])||!J(a[1],r)||!/^[0-3]$/u.test(a[2]))throw new Error("Git ls-files returned invalid stage metadata");const u=O(t,s);if(!u)throw new Error(`Git ls-files returned a path outside the product: ${s}`);o.push({mode:a[0],objectId:a[1],productRelativePath:u,rootRelativePath:s,stage:Number(a[2])})}return o.sort(Ht)}async function ft(t,e,r){if(e===null)return[];const o=await w(t.repoRoot,["ls-tree","-r","--full-tree",e,"--",t.productPathspec]);return vt(t,o,r)}async function ht(t,e,r,o,n){if(r.kind==="missing"||r.kind==="ancestor-missing")return{kind:"missing",node:r};if(r.kind!=="directory")return{kind:"non-directory",node:r};const i=St(t,e),a=await K(i);if(a===null||!It(a,i))throw new Error(`Gitlink is not initialized as a repository: ${e.productRelativePath}`);const s=Y(a,i),u=await R(s.repoRoot,["rev-parse","--verify","--quiet","HEAD"]),m=await q(s,o,u,kt(n,e.productRelativePath)),f=F(t.productDir,e.productRelativePath);if(f.kind!=="directory"||!ot(f.identity,r.identity))throw new Error(`Gitlink path changed while collecting repository state: ${e.productRelativePath}`);return{kind:"initialized",dirtyFingerprint:m,head:u,identity:f.identity}}function mt(t,e,r,o){if(t.length!==1||t[0]?.stage!==0)return!1;const n=t[0];return n.mode==="160000"?r?.kind==="initialized"&&r.head===n.objectId&&r.dirtyFingerprint===null:n.mode==="120000"?e.kind==="symlink"&&S(o,e.target)===n.objectId:n.mode==="100644"||n.mode==="100755"?e.kind==="file"&&Gt(e.mode,n.mode)===n.mode&&S(o,e.contents)===n.objectId:!1}function gt(t,e){return e.length===0&&t.length===0?!0:e.length!==1||t.length!==1||t[0]?.stage!==0?!1:e[0]?.mode===t[0]?.mode&&e[0]?.objectId===t[0]?.objectId}function yt(t,e,r,o,n,i,a){t.update("tracked-path-state-v2\0"),l(t,"path",e),r.length===0&&t.update("head-missing\0");for(const s of r)t.update("head-entry\0"),l(t,"mode",s.mode),l(t,"object",s.objectId);o.length===0&&t.update("index-missing\0");for(const s of o)t.update("index-entry\0"),l(t,"mode",s.mode),l(t,"object",s.objectId),l(t,"stage",s.stage.toString());i?Pt(t,i,a):L(t,n,a)}function vt(t,e,r){const o=[];for(const n of T(e,"Git ls-tree")){const i=n.indexOf(" "),a=n.slice(0,i).split(" ");if(i<0||a.length!==3||!/^[0-7]{6}$/u.test(a[0])||!/^(?:blob|commit)$/u.test(a[1])||!J(a[2],r))throw new Error("Git ls-tree returned invalid tree metadata");const s=k($(n.slice(i+1))),u=O(t,s);if(!u)throw new Error(`Git ls-tree returned a path outside the product: ${s}`);o.push({mode:a[0],objectId:a[2],productRelativePath:u,rootRelativePath:s})}return o.sort((n,i)=>y(n.productRelativePath,i.productRelativePath))}async function wt(t){const e=(await w(t,["rev-parse","--show-object-format"])).trim();if(e==="sha1"||e==="sha256")return e;throw new Error(`Unsupported Git object format: ${e||"empty"}`)}function Rt(t){const e=new Map;for(const r of t){const o=e.get(r.productRelativePath);o?o.push(r):e.set(r.productRelativePath,[r])}return e}function bt(t){const e=new Map;for(const r of t){const o=e.get(r.productRelativePath);o?o.push(r):e.set(r.productRelativePath,[r])}return e}function Pt(t,e,r){if(t.update("gitlink-worktree-state-v2\0"),l(t,"kind",e.kind),e.kind==="initialized"){e.head===null?t.update("head-missing\0"):l(t,"head",e.head),e.dirtyFingerprint===null?t.update("nested-clean\0"):l(t,"nested-dirty",e.dirtyFingerprint);return}L(t,e.node,r)}function L(t,e,r){switch(t.update("physical-node-v2\0"),l(t,"kind",e.kind),e.kind){case"file":l(t,"mode",process.platform==="win32"?"platform-mode-ignored":e.mode&73n?"100755":"100644"),l(t,"blob",S(r,e.contents)),I(t,e.contents);break;case"symlink":l(t,"blob",S(r,e.target)),I(t,e.target);break;case"directory":l(t,"mode",_(e.mode));break;case"ancestor-symlink":l(t,"obstacle",e.obstaclePath),I(t,e.target);break;case"ancestor-non-directory":l(t,"obstacle",e.obstaclePath),l(t,"mode",_(e.mode));break;case"ancestor-missing":l(t,"obstacle",e.obstaclePath);break;case"missing":break}}function Gt(t,e){return process.platform==="win32"?e:t&73n?"100755":"100644"}function S(t,e){return C.createHash(t).update(`blob ${e.byteLength.toString()}\0`).update(e).digest("hex")}function Et(t,e){return t.length===e.length&&t.every((r,o)=>{const n=e[o];return r.mode===n.mode&&r.objectId===n.objectId&&r.productRelativePath===n.productRelativePath&&r.rootRelativePath===n.rootRelativePath&&r.stage===n.stage})}function J(t,e){const r=e==="sha1"?40:64;return t.length===r&&/^[0-9a-f]+$/u.test(t)}function kt(t,e){return t?e?`${t}/${e}`:t:e}function St(t,e){const r=e.productRelativePath.split("/");if(r.some(i=>i.length===0||i==="."||i===".."))throw new Error(`Git returned an invalid product path: ${e.productRelativePath}`);const o=c.resolve(t.productDir,...r),n=c.relative(t.productDir,o);if(n===".."||n.startsWith(`..${c.sep}`)||c.isAbsolute(n))throw new Error(`Git returned a path outside the product: ${e.rootRelativePath}`);return o}function It(t,e){const r=p.realpathSync.native(c.resolve(t)),o=p.realpathSync.native(c.resolve(e));return process.platform==="win32"?r.toLocaleLowerCase("en-US")===o.toLocaleLowerCase("en-US"):r===o}function l(t,e,r){t.update(`${e}\0`),I(t,Buffer.from(r))}function I(t,e){t.update(`${e.byteLength.toString()}\0`),t.update(e),t.update("\0")}function Ht(t,e){return y(t.productRelativePath,e.productRelativePath)||t.stage-e.stage||y(t.mode,e.mode)||y(t.objectId,e.objectId)}function xt(t,e){const r=new Map;for(const o of e){const n=k(o);if(n===".."||n.startsWith("../")||c.isAbsolute(n)||process.platform==="win32"&&c.win32.isAbsolute(n))throw new Error(`Git returned a path outside its worktree: ${n}`);const i=O(t,n);i&&r.set(n,{productRelativePath:i,rootRelativePath:n})}return[...r.values()].sort((o,n)=>y(o.productRelativePath,n.productRelativePath))}function O(t,e){if(!e)return null;if(!t.productRootRelativePath)return e;const r=`${t.productRootRelativePath}/`;return(process.platform==="win32"?e.toLocaleLowerCase("en-US").startsWith(r.toLocaleLowerCase("en-US")):e.startsWith(r))?e.slice(r.length):null}function Ct(t){return`:(top,literal)${t}`}function $t(t){return k(t.split(c.sep).join("/"))}function Q(t,e){const r=C.createHash("sha256");return et(r,t,t,e),r.digest("hex")}async function w(t,e){const r=await A(t,e);return j(D(e),r).stdout}async function R(t,e){const r=await A(t,e);if(r.exitCode===0)return r.stdout.trim()||null;if(r.exitCode===1&&r.stdout.trim().length===0&&r.stderr.trim().length===0)return null;throw j(D(e),r),new Error("Unreachable Git command result")}function A(t,e){return nt(t,["--no-replace-objects","--no-pager","--no-optional-locks","-c","core.fsmonitor=false","-c","core.quotePath=true",...e],{repositoryConfigOnly:!0})}async function X(t,e){if(await R(t,["rev-parse","--verify","--quiet","HEAD"])!==e)throw new Error("Git HEAD changed while collecting repository state")}function T(t,e){if(t.length===0)return[];if(!t.endsWith(`
1
+ import C from"crypto";import p from"fs";import c from"path";import{appendFingerprintPathToHash as et,hasIncludedFingerprintDescendantAtPrefix as rt,normalizeFingerprintPolicy as _,shouldIgnoreCanonicalFingerprintPathAtPrefix as N,normalizeCanonicalRelativePath as k}from"./fingerprint-policy.js";import{decodeGitPathRecord as $}from"./git-path.js";import{runManagedGitCommand as nt,gitCommandLabel as D,REPOSITORY_COMMAND_GUARD_ARGS as ot}from"./managed-git.js";import{physicalNodeMode as W,readPhysicalNode as F,sameIdentityRecord as it,compareStrings as y}from"./physical-tree.js";import{normalizeManagedHostEnvironment as at,requireManagedHostCommandSuccess as j,resolveManagedHostExecutableIfAvailable as st}from"../process/index.js";import{readCiPullRequestHeadSha as ct,resolveCiGitReference as z}from"./ci-environment.js";import{parseGitRepository as lt}from"./remote.js";import{hasErrorCode as v}from"../kernel/error-serialization.js";export async function collectRepoState(t,e={}){const r=_(e.fingerprints),o=e.env??process.env,n=z(o);return await B(t,r,o,n)??ut(t,r,n)}export async function collectGitRepoState(t,e={}){const r=_(e.fingerprints),o=e.env??process.env;return B(t,r,o,z(o))}async function B(t,e,r,o){const n=ct(r),i=st("git",c.resolve(t),at(process.env));if(!i&&!Ot(t)||i&&jt(t))return null;const a=await Y(t);if(a===null)return null;const s=K(a,t),u=await R(s.repoRoot,["rev-parse","--verify","--quiet","HEAD"]),m=await dt(s.repoRoot,u,n),f=await R(s.repoRoot,["symbolic-ref","--quiet","--short","HEAD"]),b=await R(s.repoRoot,["config","--get","remote.origin.url"]),P=u!==null&&f===null,h=await pt(s,e,u,""),G=!!h,d=u||"unborn";return{kind:G?"dirty":"commit",repoRoot:s.repoRoot,worktreePath:s.repoRoot,branch:f??(P?o.branch:null),detached:P,commitSha:u,pullRequest:o.pullRequest,pullRequestHeadSha:m,remoteUrl:b,repository:lt(b),dirty:G,dirtyFingerprint:h,contentFingerprint:null,cacheKey:h?`dirty/${d}-${h}`:`commits/${d}`}}function ut(t,e,r){const o=p.realpathSync.native(c.resolve(t)),n=Q(o,e);if(Q(o,e)!==n)throw new Error("Physical repository snapshot changed while collecting repository state");const a=n.slice(0,24);return{kind:"nogit",repoRoot:null,worktreePath:o,branch:null,detached:!1,commitSha:null,pullRequest:r.pullRequest,pullRequestHeadSha:null,remoteUrl:null,repository:null,dirty:!1,dirtyFingerprint:null,contentFingerprint:a,cacheKey:`nogit/${a}`}}async function Y(t){const e=["rev-parse","--show-toplevel"],r=await L(t,e);if(r.exitCode===0){const o=r.stdout.trim();if(o.length>0)return o;throw new Error("Git rev-parse returned an empty repository root")}if(Ft(r))return null;throw j(D(e),r),new Error("Unreachable Git repository probe result")}function K(t,e){const r=p.realpathSync.native(c.resolve(t)),o=p.realpathSync.native(c.resolve(e)),n=Dt(c.relative(r,o));if(n===".."||n.startsWith("../")||c.isAbsolute(n)||process.platform==="win32"&&c.win32.isAbsolute(n))throw new Error(`Product directory must be inside its Git worktree: ${o}`);return{productDir:o,productPathspec:n?$t(n):".",productRootRelativePath:n,repoRoot:r}}export function summarizeRepoStateForMetadata(t){return t?{kind:t.kind,cacheKey:t.cacheKey,branch:t.branch,detached:t.detached,commitSha:t.commitSha,dirty:t.dirty,dirtyFingerprint:t.dirtyFingerprint,contentFingerprint:t.contentFingerprint,pullRequest:t.pullRequest,pullRequestHeadSha:t.pullRequestHeadSha,repoRoot:t.repoRoot,repository:t.repository,worktreePath:t.worktreePath,remoteUrl:t.remoteUrl}:null}async function dt(t,e,r){if(e===null||r===null)return null;if(e===r)return r;const o=await w(t,["cat-file","commit","HEAD"]),n=[];for(const i of o.split(`
2
+ `)){if(i.length===0)break;i.startsWith("parent ")&&n.push(i.slice(7))}return n.length>=2&&n.includes(r)?r:null}async function pt(t,e,r,o){const n=await O(t,e,r,o),i=await O(t,e,r,o);if(n!==i)throw new Error("Git HEAD, index, untracked inventory, submodule state, or physical snapshot changed while collecting repository state");return n?.slice(0,24)??null}async function O(t,e,r,o){await X(t.repoRoot,r);const n=C.createHash("sha256");let i=!1;const a=await Rt(t.repoRoot),[s,u]=await Promise.all([V(t,a),ht(t,r,a)]),m=bt(s),f=Pt(u),b=[...new Set([...m.keys(),...f.keys()])].sort(y);for(const d of b){const g=m.get(d)??[],E=f.get(d)??[],M=g.some(x=>x.mode==="160000")||E.some(x=>x.mode==="160000");if(N(d,o,e)&&(!M||!rt(d,o,e)))continue;const tt=g.length>0?g[0]:E[0],H=F(t.productDir,d),U=M?await mt(t,tt,H,e,o):null;(!yt(g,E)||!gt(g,H,U,a))&&(i=!0,vt(n,d,E,g,H,U,a))}const P=await w(t.repoRoot,["ls-files","--others","--exclude-per-directory=.gitignore","--",t.productPathspec]),h=Ct(t,T(P,"Git ls-files").map(d=>$(d))).filter(d=>!N(d.productRelativePath,o,e));if(h.length>0){i=!0,n.update("untracked-state-v2\0");for(const d of h)l(n,"path",d.productRelativePath),q(n,F(t.productDir,d.productRelativePath),a)}const G=await V(t,a);if(!kt(s,G))throw new Error("Git index changed while collecting repository state");return await X(t.repoRoot,r),i?n.digest("hex"):null}async function V(t,e){const r=await w(t.repoRoot,["ls-files","--stage","--abbrev=64","--",t.productPathspec]);return ft(t,r,e)}function ft(t,e,r){const o=[];for(const n of T(e,"Git ls-files")){const i=n.indexOf(" ");if(i<0)throw new Error("Git ls-files returned malformed stage metadata");const a=n.slice(0,i).split(" "),s=k($(n.slice(i+1)));if(a.length!==3||!/^[0-7]{6}$/u.test(a[0])||!J(a[1],r)||!/^[0-3]$/u.test(a[2]))throw new Error("Git ls-files returned invalid stage metadata");const u=A(t,s);if(!u)throw new Error(`Git ls-files returned a path outside the product: ${s}`);o.push({mode:a[0],objectId:a[1],productRelativePath:u,rootRelativePath:s,stage:Number(a[2])})}return o.sort(xt)}async function ht(t,e,r){if(e===null)return[];const o=await w(t.repoRoot,["ls-tree","-r","--full-tree",e,"--",t.productPathspec]);return wt(t,o,r)}async function mt(t,e,r,o,n){if(r.kind==="missing"||r.kind==="ancestor-missing")return{kind:"missing",node:r};if(r.kind!=="directory")return{kind:"non-directory",node:r};const i=It(t,e),a=await Y(i);if(a===null||!Ht(a,i))throw new Error(`Gitlink is not initialized as a repository: ${e.productRelativePath}`);const s=K(a,i),u=await R(s.repoRoot,["rev-parse","--verify","--quiet","HEAD"]),m=await O(s,o,u,St(n,e.productRelativePath)),f=F(t.productDir,e.productRelativePath);if(f.kind!=="directory"||!it(f.identity,r.identity))throw new Error(`Gitlink path changed while collecting repository state: ${e.productRelativePath}`);return{kind:"initialized",dirtyFingerprint:m,head:u,identity:f.identity}}function gt(t,e,r,o){if(t.length!==1||t[0]?.stage!==0)return!1;const n=t[0];return n.mode==="160000"?r?.kind==="initialized"&&r.head===n.objectId&&r.dirtyFingerprint===null:n.mode==="120000"?e.kind==="symlink"&&S(o,e.target)===n.objectId:n.mode==="100644"||n.mode==="100755"?e.kind==="file"&&Et(e.mode,n.mode)===n.mode&&S(o,e.contents)===n.objectId:!1}function yt(t,e){return e.length===0&&t.length===0?!0:e.length!==1||t.length!==1||t[0]?.stage!==0?!1:e[0]?.mode===t[0]?.mode&&e[0]?.objectId===t[0]?.objectId}function vt(t,e,r,o,n,i,a){t.update("tracked-path-state-v2\0"),l(t,"path",e),r.length===0&&t.update("head-missing\0");for(const s of r)t.update("head-entry\0"),l(t,"mode",s.mode),l(t,"object",s.objectId);o.length===0&&t.update("index-missing\0");for(const s of o)t.update("index-entry\0"),l(t,"mode",s.mode),l(t,"object",s.objectId),l(t,"stage",s.stage.toString());i?Gt(t,i,a):q(t,n,a)}function wt(t,e,r){const o=[];for(const n of T(e,"Git ls-tree")){const i=n.indexOf(" "),a=n.slice(0,i).split(" ");if(i<0||a.length!==3||!/^[0-7]{6}$/u.test(a[0])||!/^(?:blob|commit)$/u.test(a[1])||!J(a[2],r))throw new Error("Git ls-tree returned invalid tree metadata");const s=k($(n.slice(i+1))),u=A(t,s);if(!u)throw new Error(`Git ls-tree returned a path outside the product: ${s}`);o.push({mode:a[0],objectId:a[2],productRelativePath:u,rootRelativePath:s})}return o.sort((n,i)=>y(n.productRelativePath,i.productRelativePath))}async function Rt(t){const e=(await w(t,["rev-parse","--show-object-format"])).trim();if(e==="sha1"||e==="sha256")return e;throw new Error(`Unsupported Git object format: ${e||"empty"}`)}function bt(t){const e=new Map;for(const r of t){const o=e.get(r.productRelativePath);o?o.push(r):e.set(r.productRelativePath,[r])}return e}function Pt(t){const e=new Map;for(const r of t){const o=e.get(r.productRelativePath);o?o.push(r):e.set(r.productRelativePath,[r])}return e}function Gt(t,e,r){if(t.update("gitlink-worktree-state-v2\0"),l(t,"kind",e.kind),e.kind==="initialized"){e.head===null?t.update("head-missing\0"):l(t,"head",e.head),e.dirtyFingerprint===null?t.update("nested-clean\0"):l(t,"nested-dirty",e.dirtyFingerprint);return}q(t,e.node,r)}function q(t,e,r){switch(t.update("physical-node-v2\0"),l(t,"kind",e.kind),e.kind){case"file":l(t,"mode",process.platform==="win32"?"platform-mode-ignored":e.mode&73n?"100755":"100644"),l(t,"blob",S(r,e.contents)),I(t,e.contents);break;case"symlink":l(t,"blob",S(r,e.target)),I(t,e.target);break;case"directory":l(t,"mode",W(e.mode));break;case"ancestor-symlink":l(t,"obstacle",e.obstaclePath),I(t,e.target);break;case"ancestor-non-directory":l(t,"obstacle",e.obstaclePath),l(t,"mode",W(e.mode));break;case"ancestor-missing":l(t,"obstacle",e.obstaclePath);break;case"missing":break}}function Et(t,e){return process.platform==="win32"?e:t&73n?"100755":"100644"}function S(t,e){return C.createHash(t).update(`blob ${e.byteLength.toString()}\0`).update(e).digest("hex")}function kt(t,e){return t.length===e.length&&t.every((r,o)=>{const n=e[o];return r.mode===n.mode&&r.objectId===n.objectId&&r.productRelativePath===n.productRelativePath&&r.rootRelativePath===n.rootRelativePath&&r.stage===n.stage})}function J(t,e){const r=e==="sha1"?40:64;return t.length===r&&/^[0-9a-f]+$/u.test(t)}function St(t,e){return t?e?`${t}/${e}`:t:e}function It(t,e){const r=e.productRelativePath.split("/");if(r.some(i=>i.length===0||i==="."||i===".."))throw new Error(`Git returned an invalid product path: ${e.productRelativePath}`);const o=c.resolve(t.productDir,...r),n=c.relative(t.productDir,o);if(n===".."||n.startsWith(`..${c.sep}`)||c.isAbsolute(n))throw new Error(`Git returned a path outside the product: ${e.rootRelativePath}`);return o}function Ht(t,e){const r=p.realpathSync.native(c.resolve(t)),o=p.realpathSync.native(c.resolve(e));return process.platform==="win32"?r.toLocaleLowerCase("en-US")===o.toLocaleLowerCase("en-US"):r===o}function l(t,e,r){t.update(`${e}\0`),I(t,Buffer.from(r))}function I(t,e){t.update(`${e.byteLength.toString()}\0`),t.update(e),t.update("\0")}function xt(t,e){return y(t.productRelativePath,e.productRelativePath)||t.stage-e.stage||y(t.mode,e.mode)||y(t.objectId,e.objectId)}function Ct(t,e){const r=new Map;for(const o of e){const n=k(o);if(n===".."||n.startsWith("../")||c.isAbsolute(n)||process.platform==="win32"&&c.win32.isAbsolute(n))throw new Error(`Git returned a path outside its worktree: ${n}`);const i=A(t,n);i&&r.set(n,{productRelativePath:i,rootRelativePath:n})}return[...r.values()].sort((o,n)=>y(o.productRelativePath,n.productRelativePath))}function A(t,e){if(!e)return null;if(!t.productRootRelativePath)return e;const r=`${t.productRootRelativePath}/`;return(process.platform==="win32"?e.toLocaleLowerCase("en-US").startsWith(r.toLocaleLowerCase("en-US")):e.startsWith(r))?e.slice(r.length):null}function $t(t){return`:(top,literal)${t}`}function Dt(t){return k(t.split(c.sep).join("/"))}function Q(t,e){const r=C.createHash("sha256");return et(r,t,t,e),r.digest("hex")}async function w(t,e){const r=await L(t,e);return j(D(e),r).stdout}async function R(t,e){const r=await L(t,e);if(r.exitCode===0)return r.stdout.trim()||null;if(r.exitCode===1&&r.stdout.trim().length===0&&r.stderr.trim().length===0)return null;throw j(D(e),r),new Error("Unreachable Git command result")}function L(t,e){return nt(t,["--no-replace-objects","--no-pager",...ot,"-c","core.quotePath=true",...e],{repositoryConfigOnly:!0})}async function X(t,e){if(await R(t,["rev-parse","--verify","--quiet","HEAD"])!==e)throw new Error("Git HEAD changed while collecting repository state")}function T(t,e){if(t.length===0)return[];if(!t.endsWith(`
3
3
  `))throw new Error(`${e} returned an unterminated path record`);return t.slice(0,-1).split(`
4
- `)}function Dt(t){return`${t.stderr}
5
- ${t.stdout}`.toLowerCase().includes("not a git repository")}function Ft(t){const e=new Set(["GIT_CEILING_DIRECTORIES","GIT_DISCOVERY_ACROSS_FILESYSTEM"]);if(Object.keys(process.env).some(r=>e.has(process.platform==="win32"?r.toUpperCase():r)))return!1;try{let r=p.realpathSync.native(c.resolve(t));for(;;){if(!p.statSync(r).isDirectory())return!1;for(const n of[".git","HEAD"])try{return p.lstatSync(c.join(r,n)),!1}catch(i){if(!v(i,"ENOENT"))return!1}const o=c.dirname(r);if(o===r)return!0;r=o}}catch{return!1}}function jt(t){let e=p.realpathSync.native(c.resolve(t)),r=p.statSync(e).dev;const o=qt(),n=Ot(process.env.GIT_DISCOVERY_ACROSS_FILESYSTEM);let i=c.dirname(e);for(;i!==e;){if(o.has(e))return!1;if(Z(c.join(e,".git")))return!0;const a=p.statSync(i).dev;if(!n&&a!==r)return!1;e=i,r=a,i=c.dirname(e)}return o.has(e)?!1:Z(c.join(e,".git"))}function qt(){const t=process.env.GIT_CEILING_DIRECTORIES;return t?new Set(t.split(c.delimiter).filter(Boolean).map(e=>Lt(e))):new Set}function Lt(t){const e=c.resolve(t);try{return p.realpathSync.native(e)}catch(r){if(v(r,"ENOENT")||v(r,"ENOTDIR"))return e;throw r}}function Ot(t){return/^(?:1|on|true|yes)$/iu.test(t||"")}function Z(t){try{return p.lstatSync(t),!0}catch(e){if(v(e,"ENOENT")||v(e,"ENOTDIR"))return!1;throw e}}
4
+ `)}function Ft(t){return`${t.stderr}
5
+ ${t.stdout}`.toLowerCase().includes("not a git repository")}function jt(t){const e=new Set(["GIT_CEILING_DIRECTORIES","GIT_DISCOVERY_ACROSS_FILESYSTEM"]);if(Object.keys(process.env).some(r=>e.has(process.platform==="win32"?r.toUpperCase():r)))return!1;try{let r=p.realpathSync.native(c.resolve(t));for(;;){if(!p.statSync(r).isDirectory())return!1;for(const n of[".git","HEAD"])try{return p.lstatSync(c.join(r,n)),!1}catch(i){if(!v(i,"ENOENT"))return!1}const o=c.dirname(r);if(o===r)return!0;r=o}}catch{return!1}}function Ot(t){let e=p.realpathSync.native(c.resolve(t)),r=p.statSync(e).dev;const o=qt(),n=Lt(process.env.GIT_DISCOVERY_ACROSS_FILESYSTEM);let i=c.dirname(e);for(;i!==e;){if(o.has(e))return!1;if(Z(c.join(e,".git")))return!0;const a=p.statSync(i).dev;if(!n&&a!==r)return!1;e=i,r=a,i=c.dirname(e)}return o.has(e)?!1:Z(c.join(e,".git"))}function qt(){const t=process.env.GIT_CEILING_DIRECTORIES;return t?new Set(t.split(c.delimiter).filter(Boolean).map(e=>At(e))):new Set}function At(t){const e=c.resolve(t);try{return p.realpathSync.native(e)}catch(r){if(v(r,"ENOENT")||v(r,"ENOTDIR"))return e;throw r}}function Lt(t){return/^(?:1|on|true|yes)$/iu.test(t||"")}function Z(t){try{return p.lstatSync(t),!0}catch(e){if(v(e,"ENOENT")||v(e,"ENOTDIR"))return!1;throw e}}
@@ -1 +1 @@
1
- export const CONFIGURED_STEP_TESTKIT_MODULE_SPECIFIERS=["@gfdelarue/testkit","@gfdelarue/testkit/config","@gfdelarue/testkit/config/build-assets","@gfdelarue/testkit/config/database-steps","@gfdelarue/testkit/config/next-runtime-tsconfig","@gfdelarue/testkit/drizzle","@gfdelarue/testkit/env","@gfdelarue/testkit/tooling/vitest"];const n=new Set(["@gfdelarue/testkit/environment","@gfdelarue/testkit/node-vitest/contract","@gfdelarue/testkit/node-vitest/data","@gfdelarue/testkit/playwright"]);export const CONFIGURED_STEP_TESTKIT_MODULE_DIST_PATHS={"@gfdelarue/testkit":["index"],"@gfdelarue/testkit/config":["config","authoring","index"],"@gfdelarue/testkit/config/build-assets":["config","runtime","build-assets"],"@gfdelarue/testkit/config/database-steps":["database","steps"],"@gfdelarue/testkit/config/next-runtime-tsconfig":["config","runtime","next-runtime-tsconfig"],"@gfdelarue/testkit/drizzle":["database","drizzle","index"],"@gfdelarue/testkit/env":["config","runtime","env","index"],"@gfdelarue/testkit/tooling/vitest":["toolchains","vitest","index"]};export function isTestkitPackageSpecifier(t){return t==="@gfdelarue/testkit"||t.startsWith("@gfdelarue/testkit/")}export function requireConfiguredStepTestkitModuleSpecifier(t){if(n.has(t))throw new Error(`Configured lifecycle modules run in Node and may not import engine authoring module ${JSON.stringify(t)}`);const e=CONFIGURED_STEP_TESTKIT_MODULE_SPECIFIERS.find(i=>i===t);if(!e)throw new Error(`Unsupported configured Testkit module specifier ${JSON.stringify(t)}`);return e}
1
+ export const CONFIGURED_STEP_TESTKIT_MODULE_SPECIFIERS=["@gfdelarue/testkit","@gfdelarue/testkit/config","@gfdelarue/testkit/config/build-assets","@gfdelarue/testkit/config/database-steps","@gfdelarue/testkit/config/next-runtime-tsconfig","@gfdelarue/testkit/drizzle","@gfdelarue/testkit/env","@gfdelarue/testkit/tooling/vitest"];const n=new Set(["@gfdelarue/testkit/environment","@gfdelarue/testkit/node-vitest/data","@gfdelarue/testkit/playwright"]);export const CONFIGURED_STEP_TESTKIT_MODULE_DIST_PATHS={"@gfdelarue/testkit":["index"],"@gfdelarue/testkit/config":["config","authoring","index"],"@gfdelarue/testkit/config/build-assets":["config","runtime","build-assets"],"@gfdelarue/testkit/config/database-steps":["database","steps"],"@gfdelarue/testkit/config/next-runtime-tsconfig":["config","runtime","next-runtime-tsconfig"],"@gfdelarue/testkit/drizzle":["database","drizzle","index"],"@gfdelarue/testkit/env":["config","runtime","env","index"],"@gfdelarue/testkit/tooling/vitest":["toolchains","vitest","index"]};export function isTestkitPackageSpecifier(t){return t==="@gfdelarue/testkit"||t.startsWith("@gfdelarue/testkit/")}export function requireConfiguredStepTestkitModuleSpecifier(t){if(n.has(t))throw new Error(`Configured lifecycle modules run in Node and may not import engine authoring module ${JSON.stringify(t)}`);const e=CONFIGURED_STEP_TESTKIT_MODULE_SPECIFIERS.find(i=>i===t);if(!e)throw new Error(`Unsupported configured Testkit module specifier ${JSON.stringify(t)}`);return e}
@@ -1 +1 @@
1
- import v from"path";import{joinPathList as f,packageBinPath as E,shellQuote as w,stringifyUnknown as l}from"../process/host/platform.js";export function buildExecutionEnv(r,e={},t=process.env,i={}){return o(r,null,e,t,i)}export function buildTemplateExecutionEnv(r,e={},t=process.env,i={}){return o(r,null,e,t,{...i,omitRuntimeDatabaseBindings:!0})}export function buildTaskExecutionEnv(r,e,t={},i=process.env,s={}){return o(r,e,t,i,s)}export function resolveExecutionEnvValues(r,e,t=process.env){return o(r,null,{},t,{includeTestkitControls:!1},new Set(e))}function o(r,e,t,i,s={},n=null){const a=c(s.inheritProcessEnv===!1?{}:i,n,!1);delete a.DATABASE_URL;const u=buildTemplateContext(r,e),p=s.omitRuntimeDatabaseBindings===!0,m=c(r.testkit.serviceEnv,n,p),b=c(r.testkit.local?.env,n,p);return{...a,...d(m,u),...d(b,u),...d(c(t,n,!1),u),...s.includeTestkitControls===!1?{}:{TESTKIT_ACTIVE:"1",TESTKIT_MODE:s.mode||r.testkit.mode||"test",...r.runtimeId?{TESTKIT_RUNTIME_ID:String(r.runtimeId)}:{},...e?.leaseId?{TESTKIT_LEASE_ID:String(e.leaseId)}:{},...e?.leaseDir?{TESTKIT_LEASE_DIR:e.leaseDir}:{}}}}export function buildTemplateContext(r,e){const t=r.testkit.templateContext||{};return{...t,runtimeId:r.runtimeId||t.runtimeId||null,runtimeLabel:r.runtimeLabel||t.runtimeLabel||null,productDir:r.productDir||t.productDir||null,serviceName:r.name,serviceStateDir:r.stateDir||t.serviceStateDir||null,prepareDir:r.testkit.prepareDir||t.prepareDir||null,leaseId:e?.leaseId||null,leaseDir:e?.leaseDir||null}}export function serviceDatabaseBinding(r,e=r.name){return r.testkit.templateContext?.databaseBindingsByService?.get(e)??null}export function withDatabaseBindings(r,e){return{...r,testkit:{...r.testkit,templateContext:{...r.testkit.templateContext,databaseBindingsByService:e}}}}export function finalizeString(r,e){return resolveTemplateString(r,e)}export function finalizeSerializable(r,e){return typeof r=="string"?finalizeString(r,e):Array.isArray(r)?r.map(t=>finalizeSerializable(t,e)):r===null||typeof r!="object"?r:Object.fromEntries(Object.entries(r).map(([t,i])=>[t,finalizeSerializable(i,e)]))}export function resolveTemplateString(r,e){return typeof r!="string"?r:r.replace(/\{([a-zA-Z]+)(?::([^}]+))?\}/g,(t,i,s)=>{switch(i){case"runtime":case"runtimeId":return String(e.runtimeId);case"service":return e.serviceName;case"stateDir":return String(e.serviceStateDir);case"productDir":return e.productDir||"";case"bin":{const n=v.resolve(e.productDir||process.cwd(),e.serviceCwd||".");return w(E(n,s))}case"pathList":{const n=l(s||"").split(",").map(a=>a.trim()).filter(Boolean).map(a=>v.resolve(e.productDir||process.cwd(),a));return f(n)}case"prepareDir":return e.prepareDir||"";case"lease":return e.leaseId?String(e.leaseId):"";case"leaseDir":return e.leaseDir||"";case"port":{const n=s||e.serviceName,a=e.portMap?.get(n);if(!a)throw new Error(`Unknown port placeholder for service "${n}"`);return String(a)}case"baseUrl":{const n=s||e.serviceName,a=e.baseUrlByService?.get(n);if(!a)throw new Error(`Unknown baseUrl placeholder for service "${n}"`);return a}case"readyUrl":{const n=s||e.serviceName,a=e.readyUrlByService?.get(n);if(!a)throw new Error(`Unknown readyUrl placeholder for service "${n}"`);return a}case"publicHost":return e.publicHost||"127.0.0.1";case"publicBaseUrl":{const n=s||e.serviceName,a=e.publicBaseUrlByService?.get(n)||e.baseUrlByService?.get(n);if(!a)throw new Error(`Unknown publicBaseUrl placeholder for service "${n}"`);return a}case"privateBaseUrl":{const n=s||e.serviceName,a=e.privateBaseUrlByService?.get(n)||e.baseUrlByService?.get(n);if(!a)throw new Error(`Unknown privateBaseUrl placeholder for service "${n}"`);return a}case"serviceContextUrl":{const n=s||e.serviceName,a=e.serviceContextUrlByService?.get(n)||e.baseUrlByService?.get(n);if(!a)throw new Error(`Unknown serviceContextUrl placeholder for service "${n}"`);return a}case"dbUrl":case"dbHost":case"dbPort":case"dbName":case"dbUser":case"dbPassword":case"dbSsl":{const n=s||e.serviceName;return U(i,t,n,e)}default:throw new Error(`Unsupported template token "{${i}${s?`:${s}`:""}}"`)}})}function d(r,e){return Object.fromEntries(Object.entries(r||{}).map(([t,i])=>[t,typeof i=="string"?S(t,i,e):i]))}function S(r,e,t){try{return finalizeString(e,t)}catch(i){const s=i instanceof Error?i.message:l(i);throw new Error(`Service "${t.serviceName}" env ${r}: ${s}`,{cause:i})}}function c(r,e,t){return Object.fromEntries(Object.entries(r||{}).filter(([i,s])=>e&&!e.has(i)?!1:!(t&&typeof s=="string"&&findDatabasePlaceholders(s).length>0)))}export function findDatabasePlaceholders(r){return[...r.matchAll(/\{db(?:Url|Host|Port|Name|User|Password|Ssl)(?::([^}]+))?\}/g)].map(e=>({placeholder:e[0],serviceName:e.at(1)??null}))}function U(r,e,t,i){const s=i.databaseBindingsByService?.get(t);if(!s)throw new Error(`Cannot resolve ${e}: service "${t}" has no database binding in this context`);switch(r){case"dbUrl":return s.url;case"dbHost":return s.host;case"dbPort":return String(s.port);case"dbName":return s.database;case"dbUser":return s.user;case"dbPassword":return s.password;case"dbSsl":return D(s)}}function D(r){const e=r.sslMode,t=l(e||"").trim();return t&&!B(t)?t:h(r.sslMode)?"1":"0"}function h(r){const e=l(r||"").trim().toLowerCase();return!!(e&&!["0","false","no","off","disable","disabled"].includes(e))}function B(r){return["0","1","true","false","yes","no","on","off","disable","disabled","require","verify-ca","verify-full"].includes(l(r||"").trim().toLowerCase())}
1
+ import m from"path";import{joinPathList as E,packageBinPath as w,shellQuote as S,stringifyUnknown as l}from"../process/host/platform.js";const U=new Set(["GH_TOKEN","GITEA_TOKEN","GITHUB_TOKEN"]);export function buildExecutionEnv(r,e={},t=process.env,i={}){return o(r,null,e,t,i)}export function buildTemplateExecutionEnv(r,e={},t=process.env,i={}){return o(r,null,e,t,{...i,omitRuntimeDatabaseBindings:!0})}export function buildTaskExecutionEnv(r,e,t={},i=process.env,s={}){return o(r,e,t,i,s)}export function resolveExecutionEnvValues(r,e,t=process.env){return o(r,null,{},t,{includeTestkitControls:!1},new Set(e))}function o(r,e,t,i,s={},n=null){const a=Object.fromEntries(Object.entries(c(s.inheritProcessEnv===!1?{}:i,n,!1)).filter(([v])=>v!=="DATABASE_URL"&&!U.has(v.toUpperCase()))),u=buildTemplateContext(r,e),p=s.omitRuntimeDatabaseBindings===!0,b=c(r.testkit.serviceEnv,n,p),f=c(r.testkit.local?.env,n,p);return{...a,...d(b,u),...d(f,u),...d(c(t,n,!1),u),...s.includeTestkitControls===!1?{}:{TESTKIT_ACTIVE:"1",TESTKIT_MODE:s.mode||r.testkit.mode||"test",...r.runtimeId?{TESTKIT_RUNTIME_ID:String(r.runtimeId)}:{},...e?.leaseId?{TESTKIT_LEASE_ID:String(e.leaseId)}:{},...e?.leaseDir?{TESTKIT_LEASE_DIR:e.leaseDir}:{}}}}export function buildTemplateContext(r,e){const t=r.testkit.templateContext||{};return{...t,runtimeId:r.runtimeId||t.runtimeId||null,runtimeLabel:r.runtimeLabel||t.runtimeLabel||null,productDir:r.productDir||t.productDir||null,serviceName:r.name,serviceStateDir:r.stateDir||t.serviceStateDir||null,prepareDir:r.testkit.prepareDir||t.prepareDir||null,leaseId:e?.leaseId||null,leaseDir:e?.leaseDir||null}}export function serviceDatabaseBinding(r,e=r.name){return r.testkit.templateContext?.databaseBindingsByService?.get(e)??null}export function withDatabaseBindings(r,e){return{...r,testkit:{...r.testkit,templateContext:{...r.testkit.templateContext,databaseBindingsByService:e}}}}export function finalizeString(r,e){return resolveTemplateString(r,e)}export function finalizeSerializable(r,e){return typeof r=="string"?finalizeString(r,e):Array.isArray(r)?r.map(t=>finalizeSerializable(t,e)):r===null||typeof r!="object"?r:Object.fromEntries(Object.entries(r).map(([t,i])=>[t,finalizeSerializable(i,e)]))}export function resolveTemplateString(r,e){return typeof r!="string"?r:r.replace(/\{([a-zA-Z]+)(?::([^}]+))?\}/g,(t,i,s)=>{switch(i){case"runtime":case"runtimeId":return String(e.runtimeId);case"service":return e.serviceName;case"stateDir":return String(e.serviceStateDir);case"productDir":return e.productDir||"";case"bin":{const n=m.resolve(e.productDir||process.cwd(),e.serviceCwd||".");return S(w(n,s))}case"pathList":{const n=l(s||"").split(",").map(a=>a.trim()).filter(Boolean).map(a=>m.resolve(e.productDir||process.cwd(),a));return E(n)}case"prepareDir":return e.prepareDir||"";case"lease":return e.leaseId?String(e.leaseId):"";case"leaseDir":return e.leaseDir||"";case"port":{const n=s||e.serviceName,a=e.portMap?.get(n);if(!a)throw new Error(`Unknown port placeholder for service "${n}"`);return String(a)}case"baseUrl":{const n=s||e.serviceName,a=e.baseUrlByService?.get(n);if(!a)throw new Error(`Unknown baseUrl placeholder for service "${n}"`);return a}case"readyUrl":{const n=s||e.serviceName,a=e.readyUrlByService?.get(n);if(!a)throw new Error(`Unknown readyUrl placeholder for service "${n}"`);return a}case"publicHost":return e.publicHost||"127.0.0.1";case"publicBaseUrl":{const n=s||e.serviceName,a=e.publicBaseUrlByService?.get(n)||e.baseUrlByService?.get(n);if(!a)throw new Error(`Unknown publicBaseUrl placeholder for service "${n}"`);return a}case"privateBaseUrl":{const n=s||e.serviceName,a=e.privateBaseUrlByService?.get(n)||e.baseUrlByService?.get(n);if(!a)throw new Error(`Unknown privateBaseUrl placeholder for service "${n}"`);return a}case"serviceContextUrl":{const n=s||e.serviceName,a=e.serviceContextUrlByService?.get(n)||e.baseUrlByService?.get(n);if(!a)throw new Error(`Unknown serviceContextUrl placeholder for service "${n}"`);return a}case"dbUrl":case"dbHost":case"dbPort":case"dbName":case"dbUser":case"dbPassword":case"dbSsl":{const n=s||e.serviceName;return h(i,t,n,e)}default:throw new Error(`Unsupported template token "{${i}${s?`:${s}`:""}}"`)}})}function d(r,e){return Object.fromEntries(Object.entries(r||{}).map(([t,i])=>[t,typeof i=="string"?D(t,i,e):i]))}function D(r,e,t){try{return finalizeString(e,t)}catch(i){const s=i instanceof Error?i.message:l(i);throw new Error(`Service "${t.serviceName}" env ${r}: ${s}`,{cause:i})}}function c(r,e,t){return Object.fromEntries(Object.entries(r||{}).filter(([i,s])=>e&&!e.has(i)?!1:!(t&&typeof s=="string"&&findDatabasePlaceholders(s).length>0)))}export function findDatabasePlaceholders(r){return[...r.matchAll(/\{db(?:Url|Host|Port|Name|User|Password|Ssl)(?::([^}]+))?\}/g)].map(e=>({placeholder:e[0],serviceName:e.at(1)??null}))}function h(r,e,t,i){const s=i.databaseBindingsByService?.get(t);if(!s)throw new Error(`Cannot resolve ${e}: service "${t}" has no database binding in this context`);switch(r){case"dbUrl":return s.url;case"dbHost":return s.host;case"dbPort":return String(s.port);case"dbName":return s.database;case"dbUser":return s.user;case"dbPassword":return s.password;case"dbSsl":return T(s)}}function T(r){const e=r.sslMode,t=l(e||"").trim();return t&&!I(t)?t:B(r.sslMode)?"1":"0"}function B(r){const e=l(r||"").trim().toLowerCase();return!!(e&&!["0","false","no","off","disable","disabled"].includes(e))}function I(r){return["0","1","true","false","yes","no","on","off","disable","disabled","require","verify-ca","verify-full"].includes(l(r||"").trim().toLowerCase())}
@@ -1,4 +1,4 @@
1
- import _ from"crypto";import h from"fs";import m from"path";import{prepareCommandForSpawn as I,resolveCommandForSpawn as A}from"../process/host/platform.js";import{normalizeContainedRelativePath as V,resolveContainedPath as k}from"../process/files/contained-path.js";import{deriveSemverMinimum as D,isExactSemver as L,normalizeSemver as f,satisfiesSemver as g}from"./semver.js";import{managedHostProcessInfrastructureError as M,runManagedHostProcess as H}from"../process/index.js";import{managedNodeRelease as j}from"./managed-node-releases.js";import{createSharedToolchainOperation as P,waitForSharedToolchainOperation as R}from"./shared-operation.js";import{TOOLCHAIN_METADATA_TIMEOUT_MS as O}from"./timeouts.js";import{isUnknownRecord as N,normalizeOptionalString as d}from"../kernel/unknown-values.js";import{readJsonFileOrNull as F}from"../process/files/json-file.js";const W=".",z="auto",q="require-host",U=600*1e3,B=['"use strict";','const { spawn } = require("node:child_process");',"const [command, ...args] = process.argv.slice(1);",'if (typeof command !== "string" || command.length === 0) {',' process.stderr.write("Missing Windows toolchain command\\n");'," process.exitCode = 126;","} else {"," let spawnFailed = false;"," const child = spawn(command, args, {"," shell: false,",' stdio: "inherit",'," windowsHide: true,"," windowsVerbatimArguments: true,"," });",' child.once("error", (error) => {'," spawnFailed = true;"," process.stderr.write(`${error.stack ?? String(error)}\\n`);"," process.exitCode = 126;"," });",' child.once("close", (code, signal) => {'," if (spawnFailed) return;"," if (signal !== null) {"," process.stderr.write(`Windows toolchain command exited on signal ${signal}\\n`);"," process.exitCode = 1;"," return;"," }"," process.exitCode = code ?? 1;"," });","}"].join(`
2
- `),v=new WeakMap,y=new WeakSet;export function normalizeToolchainRegistry(n){if(n==null)return{};if(!N(n))throw new Error("testkit.config.ts toolchains must be an object");const e={};for(const[o,r]of Object.entries(n))e[o]=x(r,`testkit.config.ts toolchains.${o}`);return e}export function normalizeRuntimeToolchain(n,e,o={}){if(n==null)return null;if(typeof n=="string"){const t=n.trim();if(!t)throw new Error(`${e} must reference a non-empty toolchain name`);const i=o[t];if(!i)throw new Error(`${e} references unknown toolchain "${t}"`);return{...i,refName:t,sourceLabel:`${e} -> ${t}`}}return{...x(n,e),refName:null,sourceLabel:e}}export async function resolveConfiguredToolchain(n,e={}){const o=n?.testkit?.runtime?.toolchain;if(!n||!o)return null;let r=v.get(n);if(r===void 0){r=P(i=>J(n,o,{...e,signal:i})),v.set(n,r);const t=r;r.promise.catch(()=>{v.get(n)===t&&v.delete(n)})}return R(r,e.signal)}export function announceResolvedToolchain(n,e,o=null){return!e||y.has(n)||(y.add(n),o?.toolchainResolved&&o.toolchainResolved({serviceName:n.name,runtimeLabel:n.runtimeLabel||n.name},e.summary)),Promise.resolve()}export function applyToolchainEnv(n,e,o=process.env){if(!e)return{...n};const r={...n,PATH:ee(e.binDir,n.PATH||o.PATH||""),TESTKIT_TOOLCHAIN_KIND:"node",TESTKIT_TOOLCHAIN_NODE_VERSION:e.nodeVersion,TESTKIT_TOOLCHAIN_NODE_SOURCE:e.nodeSource,TESTKIT_TOOLCHAIN_INSTALL_MODE:e.install};return e.npmVersion&&(r.TESTKIT_TOOLCHAIN_NPM_VERSION=e.npmVersion,r.TESTKIT_TOOLCHAIN_NPM_SOURCE=e.npmSource),r}function x(n,e){if(!N(n))throw new Error(`${e} must be an object`);const o=V(d(n.cwd)||W,`${e}.cwd`),r=d(n.detect)||z,t=d(n.install)||q;if(r!=="auto"&&r!=="off")throw new Error(`${e}.detect must be "auto" or "off"`);if(t!=="require-host"&&t!=="download")throw new Error(`${e}.install must be "require-host" or "download"`);return{cwd:o,detect:r,install:t,node:d(n.node),npm:d(n.npm)}}async function J(n,e,o){const r=n.productDir,t=k(r,e.cwd,`${e.sourceLabel}.cwd`),i=K(t),s=e.node||(e.detect==="auto"?i.nodeSpec:null),a=e.node?"toolchain.node":e.detect==="auto"?i.nodeSource??"none":"none",l=e.npm||(e.detect==="auto"?i.npmSpec:null),c=e.npm?"toolchain.npm":e.detect==="auto"?i.npmSource??"none":"none";if(!s&&!l)throw new Error(`Service "${n.name}" enables runtime.toolchain but no Node/npm version could be resolved in ${e.cwd}`);return e.install==="require-host"?G({config:n,cwd:t,nodeSpec:s,nodeSource:a,npmSpec:l,npmSource:c,toolchain:e,options:o}):Q({config:n,cwd:t,nodeSpec:s,nodeSource:a,npmSpec:l,npmSource:c,toolchain:e,options:o})}function K(n){const e=m.join(n,"package.json"),o=F(e),r=Z(m.join(n,".tool-versions")),t=b(m.join(n,".nvmrc")),i=b(m.join(n,".node-version")),s=d(p(o,"volta","node")),a=d(p(o,"engines","node")),l=d(r.nodejs),c=s||t||i||l||a;let u=null;s?u="package.json#volta.node":t?u=".nvmrc":i?u=".node-version":l?u=".tool-versions#nodejs":a&&(u="package.json#engines.node");const E=d(p(o,"volta","npm")),$=d(p(o,"engines","npm")),T=Y(p(o,"packageManager")),C=E||(T?.name==="npm"?T.version:null)||$;let w=null;return E?w="package.json#volta.npm":T?.name==="npm"?w="package.json#packageManager":$&&(w="package.json#engines.npm"),{nodeSpec:c,nodeSource:u,npmSpec:C,npmSource:w}}async function G({config:n,cwd:e,nodeSpec:o,nodeSource:r,npmSpec:t,npmSource:i,toolchain:s,options:a}){const l=f(a.hostNodeVersion||process.versions.node);if(o&&!g(l,o))throw new Error(`Service "${n.name}" requires Node ${o} (${r}), but host Node is ${l}`);let c=null;if(t&&(c=f(a.hostNpmVersion||await X({command:"npm",args:["--version"],cwd:e,env:process.env,commandExecutor:a.commandExecutor,signal:a.signal})),!g(c,t)))throw new Error(`Service "${n.name}" requires npm ${t} (${i}), but host npm is ${c}`);return{install:s.install,cwd:s.cwd,nodeVersion:l,nodeSource:r,nodeSpec:o,npmVersion:c,npmSource:i,npmSpec:t,nodeExecutable:process.execPath,binDir:m.dirname(process.execPath),toolchainDir:m.dirname(process.execPath),fingerprint:S({install:s.install,nodeVersion:l,npmVersion:c,nodeSource:r,npmSource:i}),summary:`${s.install} node=${l}${c?` npm=${c}`:""} (${r})`}}async function Q({config:n,nodeSpec:e,nodeSource:o,npmSpec:r,npmSource:t,toolchain:i,options:s}){if(!e)throw new Error(`Service "${n.name}" download toolchain mode requires a resolvable Node version`);const a=L(e)?f(e):D(e);if(!a)throw new Error(`Service "${n.name}" cannot deterministically provision Node from "${e}". Provide an exact version in runtime.toolchain.node, .nvmrc, .node-version, .tool-versions, or package.json#volta.node.`);const l=await provisionNodeToolchain({productDir:n.productDir,version:a,fetchImpl:s.fetchImpl||fetchToolchain,commandExecutor:s.commandExecutor||executeToolchainCommand,signal:s.signal});if(!g(a,e))throw new Error(`Service "${n.name}" resolved Node ${a}, which does not satisfy ${e}`);const c=l.npmVersion?f(l.npmVersion):null;if(r&&(!c||!g(c,r)))throw new Error(`Service "${n.name}" requires npm ${r} (${t}), but Node ${a} bundles npm ${c||"unknown"}`);return{install:i.install,cwd:i.cwd,nodeVersion:a,nodeSource:o,nodeSpec:e,npmVersion:c,npmSource:t,npmSpec:r,nodeExecutable:l.nodeExecutable,binDir:l.binDir,toolchainDir:l.toolchainDir,fingerprint:S({install:i.install,nodeVersion:a,npmVersion:c,nodeSource:o,npmSource:t}),summary:`download node=${a}${c?` npm=${c}`:""} (${o})`}}export async function provisionNodeToolchain({productDir:n,version:e,fetchImpl:o=fetchToolchain,commandExecutor:r=executeToolchainCommand,signal:t}){const i=f(e);if(e!==i)throw new Error(`Downloaded Node toolchain version must use canonical x.y.z form; received ${JSON.stringify(e)}`);const s=j(i),{provisionNodeToolchainWithRelease:a}=await import("./node-provisioning.js");return a({commandExecutor:r,fetchImpl:o,productDir:n,release:s,signal:t})}function S(n){return _.createHash("sha256").update(JSON.stringify({kind:"node",...n})).digest("hex")}async function X({command:n,args:e,cwd:o,env:r,commandExecutor:t=executeToolchainCommand,signal:i}){const s=await t(n,e,{cwd:o,env:r,signal:i,stdio:"pipe",timeoutMs:O,windowsHide:!0});return s.stdout==null?"":typeof s.stdout=="string"?s.stdout.trim():Buffer.from(s.stdout).toString().trim()}function Y(n){const e=d(n);if(!e)return null;const o=e.lastIndexOf("@");return o<=0||o===e.length-1?null:{name:e.slice(0,o),version:e.slice(o+1)}}function b(n){if(!h.existsSync(n))return null;const o=h.readFileSync(n,"utf8").split(/\r?\n/).find(r=>r.trim().length>0);return o?o.trim():null}function Z(n){if(!h.existsSync(n))return{};const e={};for(const o of h.readFileSync(n,"utf8").split(/\r?\n/)){const r=o.trim();if(!r||r.startsWith("#"))continue;const[t,i]=r.split(/\s+/,2);t&&i&&(e[t]=i.trim())}return e}function ee(n,e){const o=d(n);if(!o)return e;const r=(e||"").split(m.delimiter).filter(Boolean).filter(t=>t!==o);return[o,...r].join(m.delimiter)}export async function fetchToolchain(n,e={}){const o=AbortSignal.timeout(U),r=await fetch(n,{signal:e.signal===void 0?o:AbortSignal.any([e.signal,o])});return{arrayBuffer:()=>r.arrayBuffer(),body:r.body===null?null:ne(r.body),ok:r.ok,status:r.status}}async function*ne(n){const e=n.getReader();let o=!1;try{for(;;){const r=await e.read();if(r.done){o=!0;return}yield r.value}}finally{o||await e.cancel(),e.releaseLock()}}export function prepareToolchainCommandForManagedHost(n,e,o={}){const r={env:o.env||process.env,platform:o.platform},t=A(n,r),i=I(t,e,r);return i.windowsVerbatimArguments?(oe(t,e),{executable:o.hostNodeExecutable||process.execPath,args:["--input-type=commonjs","--eval",B,"--",i.command,...i.args]}):{executable:i.command,args:i.args}}function oe(n,e){if(/[%!"\0\r\n]/u.test(n))throw new Error(`Windows batch toolchain commands cannot safely preserve command path ${JSON.stringify(n)}`);const o=e.find(r=>/[%!"\0\r\n]/u.test(r)||r.endsWith("\\"));if(o!==void 0)throw new Error(`Windows batch toolchain commands cannot safely preserve argument ${JSON.stringify(o)}`)}export async function executeToolchainCommand(n,e,o={}){const r=re(o.timeoutMs),t=m.resolve(o.cwd||process.cwd()),i=prepareToolchainCommandForManagedHost(n,e,{env:o.env||process.env}),s=await H({args:i.args,cwd:t,displayLabel:`Toolchain command ${n}`,env:o.env||process.env,executable:i.executable,signal:o.signal,timeoutMs:r}),a=M(`Toolchain command ${n}`,s);if(a)throw a;if(s.exitCode!==0)throw new Error(`Toolchain command ${n} exited with code ${String(s.exitCode)}${te(s.stdout,s.stderr)}`);return{stdout:s.stdout}}function re(n){const e=n??O;if(!Number.isSafeInteger(e)||e<=0)throw new Error("Toolchain command timeout must be a positive safe integer in milliseconds");return e}function te(n,e){const o=[n,e].map(r=>r.trim()).filter(Boolean).join(`
1
+ import _ from"crypto";import h from"fs";import m from"path";import{prepareCommandForSpawn as I,resolveCommandForSpawn as A}from"../process/host/platform.js";import{normalizeContainedRelativePath as k,resolveContainedPath as V}from"../process/files/contained-path.js";import{deriveSemverMinimum as D,isExactSemver as L,normalizeSemver as f,satisfiesSemver as g}from"./semver.js";import{managedHostProcessInfrastructureError as M,runManagedHostProcess as j}from"../process/index.js";import{managedNodeRelease as H}from"./managed-node-releases.js";import{createSharedToolchainOperation as P,waitForSharedToolchainOperation as R}from"./shared-operation.js";import{TOOLCHAIN_METADATA_TIMEOUT_MS as O}from"./timeouts.js";import{rejectUnknownKeys as F}from"../kernel/known-keys.js";import{isUnknownRecord as N,normalizeOptionalString as l}from"../kernel/unknown-values.js";import{readJsonFileOrNull as W}from"../process/files/json-file.js";const z=".",q="auto",U="require-host",B=600*1e3,K=['"use strict";','const { spawn } = require("node:child_process");',"const [command, ...args] = process.argv.slice(1);",'if (typeof command !== "string" || command.length === 0) {',' process.stderr.write("Missing Windows toolchain command\\n");'," process.exitCode = 126;","} else {"," let spawnFailed = false;"," const child = spawn(command, args, {"," shell: false,",' stdio: "inherit",'," windowsHide: true,"," windowsVerbatimArguments: true,"," });",' child.once("error", (error) => {'," spawnFailed = true;"," process.stderr.write(`${error.stack ?? String(error)}\\n`);"," process.exitCode = 126;"," });",' child.once("close", (code, signal) => {'," if (spawnFailed) return;"," if (signal !== null) {"," process.stderr.write(`Windows toolchain command exited on signal ${signal}\\n`);"," process.exitCode = 1;"," return;"," }"," process.exitCode = code ?? 1;"," });","}"].join(`
2
+ `),v=new WeakMap,y=new WeakSet;export function normalizeToolchainRegistry(n){if(n==null)return{};if(!N(n))throw new Error("testkit.config.ts toolchains must be an object");const e={};for(const[o,r]of Object.entries(n))e[o]=x(r,`testkit.config.ts toolchains.${o}`);return e}export function normalizeRuntimeToolchain(n,e,o={}){if(n==null)return null;if(typeof n=="string"){const t=n.trim();if(!t)throw new Error(`${e} must reference a non-empty toolchain name`);const i=o[t];if(!i)throw new Error(`${e} references unknown toolchain "${t}"`);return{...i,refName:t,sourceLabel:`${e} -> ${t}`}}return{...x(n,e),refName:null,sourceLabel:e}}export async function resolveConfiguredToolchain(n,e={}){const o=n?.testkit?.runtime?.toolchain;if(!n||!o)return null;let r=v.get(n);if(r===void 0){r=P(i=>J(n,o,{...e,signal:i})),v.set(n,r);const t=r;r.promise.catch(()=>{v.get(n)===t&&v.delete(n)})}return R(r,e.signal)}export function announceResolvedToolchain(n,e,o=null){return!e||y.has(n)||(y.add(n),o?.toolchainResolved&&o.toolchainResolved({serviceName:n.name,runtimeLabel:n.runtimeLabel||n.name},e.summary)),Promise.resolve()}export function applyToolchainEnv(n,e,o=process.env){if(!e)return{...n};const r={...n,PATH:ne(e.binDir,n.PATH||o.PATH||""),TESTKIT_TOOLCHAIN_KIND:"node",TESTKIT_TOOLCHAIN_NODE_VERSION:e.nodeVersion,TESTKIT_TOOLCHAIN_NODE_SOURCE:e.nodeSource,TESTKIT_TOOLCHAIN_INSTALL_MODE:e.install};return e.npmVersion&&(r.TESTKIT_TOOLCHAIN_NPM_VERSION=e.npmVersion,r.TESTKIT_TOOLCHAIN_NPM_SOURCE=e.npmSource),r}function x(n,e){if(!N(n))throw new Error(`${e} must be an object`);F(n,["cwd","detect","install","node","npm"],e);const o=k(l(n.cwd)||z,`${e}.cwd`),r=l(n.detect)||q,t=l(n.install)||U;if(r!=="auto"&&r!=="off")throw new Error(`${e}.detect must be "auto" or "off"`);if(t!=="require-host"&&t!=="download")throw new Error(`${e}.install must be "require-host" or "download"`);return{cwd:o,detect:r,install:t,node:l(n.node),npm:l(n.npm)}}async function J(n,e,o){const r=n.productDir,t=V(r,e.cwd,`${e.sourceLabel}.cwd`),i=G(t),s=e.node||(e.detect==="auto"?i.nodeSpec:null),a=e.node?"toolchain.node":e.detect==="auto"?i.nodeSource??"none":"none",d=e.npm||(e.detect==="auto"?i.npmSpec:null),c=e.npm?"toolchain.npm":e.detect==="auto"?i.npmSource??"none":"none";if(!s&&!d)throw new Error(`Service "${n.name}" enables runtime.toolchain but no Node/npm version could be resolved in ${e.cwd}`);return e.install==="require-host"?Q({config:n,cwd:t,nodeSpec:s,nodeSource:a,npmSpec:d,npmSource:c,toolchain:e,options:o}):X({config:n,cwd:t,nodeSpec:s,nodeSource:a,npmSpec:d,npmSource:c,toolchain:e,options:o})}function G(n){const e=m.join(n,"package.json"),o=W(e),r=ee(m.join(n,".tool-versions")),t=b(m.join(n,".nvmrc")),i=b(m.join(n,".node-version")),s=l(p(o,"volta","node")),a=l(p(o,"engines","node")),d=l(r.nodejs),c=s||t||i||d||a;let u=null;s?u="package.json#volta.node":t?u=".nvmrc":i?u=".node-version":d?u=".tool-versions#nodejs":a&&(u="package.json#engines.node");const E=l(p(o,"volta","npm")),$=l(p(o,"engines","npm")),T=Z(p(o,"packageManager")),C=E||(T?.name==="npm"?T.version:null)||$;let w=null;return E?w="package.json#volta.npm":T?.name==="npm"?w="package.json#packageManager":$&&(w="package.json#engines.npm"),{nodeSpec:c,nodeSource:u,npmSpec:C,npmSource:w}}async function Q({config:n,cwd:e,nodeSpec:o,nodeSource:r,npmSpec:t,npmSource:i,toolchain:s,options:a}){const d=f(a.hostNodeVersion||process.versions.node);if(o&&!g(d,o))throw new Error(`Service "${n.name}" requires Node ${o} (${r}), but host Node is ${d}`);let c=null;if(t&&(c=f(a.hostNpmVersion||await Y({command:"npm",args:["--version"],cwd:e,env:process.env,commandExecutor:a.commandExecutor,signal:a.signal})),!g(c,t)))throw new Error(`Service "${n.name}" requires npm ${t} (${i}), but host npm is ${c}`);return{install:s.install,cwd:s.cwd,nodeVersion:d,nodeSource:r,nodeSpec:o,npmVersion:c,npmSource:i,npmSpec:t,nodeExecutable:process.execPath,binDir:m.dirname(process.execPath),toolchainDir:m.dirname(process.execPath),fingerprint:S({install:s.install,nodeVersion:d,npmVersion:c,nodeSource:r,npmSource:i}),summary:`${s.install} node=${d}${c?` npm=${c}`:""} (${r})`}}async function X({config:n,nodeSpec:e,nodeSource:o,npmSpec:r,npmSource:t,toolchain:i,options:s}){if(!e)throw new Error(`Service "${n.name}" download toolchain mode requires a resolvable Node version`);const a=L(e)?f(e):D(e);if(!a)throw new Error(`Service "${n.name}" cannot deterministically provision Node from "${e}". Provide an exact version in runtime.toolchain.node, .nvmrc, .node-version, .tool-versions, or package.json#volta.node.`);const d=await provisionNodeToolchain({productDir:n.productDir,version:a,fetchImpl:s.fetchImpl||fetchToolchain,commandExecutor:s.commandExecutor||executeToolchainCommand,signal:s.signal});if(!g(a,e))throw new Error(`Service "${n.name}" resolved Node ${a}, which does not satisfy ${e}`);const c=d.npmVersion?f(d.npmVersion):null;if(r&&(!c||!g(c,r)))throw new Error(`Service "${n.name}" requires npm ${r} (${t}), but Node ${a} bundles npm ${c||"unknown"}`);return{install:i.install,cwd:i.cwd,nodeVersion:a,nodeSource:o,nodeSpec:e,npmVersion:c,npmSource:t,npmSpec:r,nodeExecutable:d.nodeExecutable,binDir:d.binDir,toolchainDir:d.toolchainDir,fingerprint:S({install:i.install,nodeVersion:a,npmVersion:c,nodeSource:o,npmSource:t}),summary:`download node=${a}${c?` npm=${c}`:""} (${o})`}}export async function provisionNodeToolchain({productDir:n,version:e,fetchImpl:o=fetchToolchain,commandExecutor:r=executeToolchainCommand,signal:t}){const i=f(e);if(e!==i)throw new Error(`Downloaded Node toolchain version must use canonical x.y.z form; received ${JSON.stringify(e)}`);const s=H(i),{provisionNodeToolchainWithRelease:a}=await import("./node-provisioning.js");return a({commandExecutor:r,fetchImpl:o,productDir:n,release:s,signal:t})}function S(n){return _.createHash("sha256").update(JSON.stringify({kind:"node",...n})).digest("hex")}async function Y({command:n,args:e,cwd:o,env:r,commandExecutor:t=executeToolchainCommand,signal:i}){const s=await t(n,e,{cwd:o,env:r,signal:i,stdio:"pipe",timeoutMs:O,windowsHide:!0});return s.stdout==null?"":typeof s.stdout=="string"?s.stdout.trim():Buffer.from(s.stdout).toString().trim()}function Z(n){const e=l(n);if(!e)return null;const o=e.lastIndexOf("@");return o<=0||o===e.length-1?null:{name:e.slice(0,o),version:e.slice(o+1)}}function b(n){if(!h.existsSync(n))return null;const o=h.readFileSync(n,"utf8").split(/\r?\n/).find(r=>r.trim().length>0);return o?o.trim():null}function ee(n){if(!h.existsSync(n))return{};const e={};for(const o of h.readFileSync(n,"utf8").split(/\r?\n/)){const r=o.trim();if(!r||r.startsWith("#"))continue;const[t,i]=r.split(/\s+/,2);t&&i&&(e[t]=i.trim())}return e}function ne(n,e){const o=l(n);if(!o)return e;const r=(e||"").split(m.delimiter).filter(Boolean).filter(t=>t!==o);return[o,...r].join(m.delimiter)}export async function fetchToolchain(n,e={}){const o=AbortSignal.timeout(B),r=await fetch(n,{signal:e.signal===void 0?o:AbortSignal.any([e.signal,o])});return{arrayBuffer:()=>r.arrayBuffer(),body:r.body===null?null:oe(r.body),ok:r.ok,status:r.status}}async function*oe(n){const e=n.getReader();let o=!1;try{for(;;){const r=await e.read();if(r.done){o=!0;return}yield r.value}}finally{o||await e.cancel(),e.releaseLock()}}export function prepareToolchainCommandForManagedHost(n,e,o={}){const r={env:o.env||process.env,platform:o.platform},t=A(n,r),i=I(t,e,r);return i.windowsVerbatimArguments?(re(t,e),{executable:o.hostNodeExecutable||process.execPath,args:["--input-type=commonjs","--eval",K,"--",i.command,...i.args]}):{executable:i.command,args:i.args}}function re(n,e){if(/[%!"\0\r\n]/u.test(n))throw new Error(`Windows batch toolchain commands cannot safely preserve command path ${JSON.stringify(n)}`);const o=e.find(r=>/[%!"\0\r\n]/u.test(r)||r.endsWith("\\"));if(o!==void 0)throw new Error(`Windows batch toolchain commands cannot safely preserve argument ${JSON.stringify(o)}`)}export async function executeToolchainCommand(n,e,o={}){const r=te(o.timeoutMs),t=m.resolve(o.cwd||process.cwd()),i=prepareToolchainCommandForManagedHost(n,e,{env:o.env||process.env}),s=await j({args:i.args,cwd:t,displayLabel:`Toolchain command ${n}`,env:o.env||process.env,executable:i.executable,signal:o.signal,timeoutMs:r}),a=M(`Toolchain command ${n}`,s);if(a)throw a;if(s.exitCode!==0)throw new Error(`Toolchain command ${n} exited with code ${String(s.exitCode)}${ie(s.stdout,s.stderr)}`);return{stdout:s.stdout}}function te(n){const e=n??O;if(!Number.isSafeInteger(e)||e<=0)throw new Error("Toolchain command timeout must be a positive safe integer in milliseconds");return e}function ie(n,e){const o=[n,e].map(r=>r.trim()).filter(Boolean).join(`
3
3
  `);return o?`
4
4
  ${o.slice(-4e3)}`:""}function p(n,...e){let o=n;for(const r of e){if(!N(o))return;o=o[r]}return o}
@@ -1,2 +1,2 @@
1
- import V from"node:crypto";import u from"node:fs";import d from"node:path";import{TarArchiveInspector as z,ZipArchiveExtractor as M}from"./archive.js";import{withProcessLock as B}from"../process/locks.js";import{createContainedTemporaryDirectory as L,existingRealInstallDirectory as $,NODE_TOOLCHAIN_CONTENT_DIRECTORY as P,NODE_TOOLCHAIN_MARKER_FILENAME as R,nodeToolchainCacheKey as j,prepareNodeToolchainCache as H,readRealManifestFile as p,removeContainedInstallDirectory as F,requireContainedExecutable as v,requireRealContentDirectory as U}from"./node-cache.js";import{InstalledNodeTreeMismatchError as Y,validateNodeArchiveEntries as q,verifyInstalledNodeTree as g}from"./node-integrity.js";import{createSharedToolchainOperation as J,throwIfSignalAborted as m,waitForPromise as f,waitForSharedToolchainOperation as K}from"./shared-operation.js";import{TOOLCHAIN_METADATA_TIMEOUT_MS as W}from"./timeouts.js";import{isUnknownRecord as G}from"../kernel/unknown-values.js";const w=2,E=300*1e3,Z=Object.freeze([100,250,500,1e3,2e3,3e3,3e3]),y=new Map;export async function provisionNodeToolchainWithRelease({archiveTimeoutMs:e=E,commandExecutor:r,fetchImpl:t,productDir:n,release:o,signal:i,zipExtractor:a}){m(i);const s=H(n,o),l=j(s);let c=y.get(l);if(c===void 0){c=J(k=>Q({archiveTimeoutMs:e,commandExecutor:r,fetchImpl:t,paths:s,productDir:n,release:o,signal:k,zipExtractor:a})),y.set(l,c);const h=c;c.promise.then(()=>{I(l,h)},()=>{I(l,h)})}return K(c,i)}export async function extractTrustedNodeArchive({archivePath:e,commandExecutor:r,destination:t,release:n,signal:o,timeoutMs:i=E,zipExtractor:a=new M}){if(se(i),u.existsSync(t))throw new Error(`Node archive destination already exists: ${t}`);u.mkdirSync(t,{mode:448});const s=AbortSignal.timeout(i),l=o===void 0?s:AbortSignal.any([o,s]),c=n.archive.format==="zip"?a:new z;try{const h=await c.inspect(e,n.archive.format,{maxEntries:n.archive.entryCount,maxUnpackedSizeBytes:n.archive.unpackedSizeBytes,signal:l});q(h,n),n.archive.format==="zip"?await a.extract(e,t,n.archive.format,{maxEntries:n.archive.entryCount,maxUnpackedSizeBytes:n.archive.unpackedSizeBytes,signal:l}):(await r("tar",["-xzf",e,"-C",t],{cwd:d.dirname(t),env:process.env,signal:l,timeoutMs:i}),m(l))}catch(h){throw u.rmSync(t,{recursive:!0,force:!0}),h}}async function Q(e){let r=null;const t=await b(e.paths,e.release,e.commandExecutor,e.signal,n=>{r=n});return t!==null?t:B(e.paths.lockPath,async()=>{if(m(e.signal),r===null||!T(e.paths,r)){const n=await b(e.paths,e.release,e.commandExecutor,e.signal);if(n!==null)return n}return F(e.paths),X(e)},{signal:e.signal,timeoutMs:null})}async function X(e){const r=L(e.paths.rootDirectory,`.tmp-${e.release.version}`),t=e.release.archive.format==="zip"?".zip":".tar.gz",n=d.join(r,`node${t}`),o=d.join(r,"extracted"),i=d.join(r,"publish"),a=d.join(i,P);try{const s=await f(e.fetchImpl(e.release.archive.url,{signal:e.signal}),e.signal);if(!s.ok)throw new Error(`Failed to download ${e.release.archive.url}: HTTP ${String(s.status)}`);await re(s,n,e.release,e.signal),await extractTrustedNodeArchive({archivePath:n,commandExecutor:e.commandExecutor,destination:o,release:e.release,signal:e.signal,timeoutMs:e.archiveTimeoutMs,zipExtractor:e.zipExtractor});const l=d.join(o,e.release.archive.rootDirectory);ce(o,l),await g(l,e.release.installed,e.signal),u.mkdirSync(i,{mode:448}),u.renameSync(l,a),u.rmSync(o,{recursive:!0,force:!0}),u.rmSync(n,{force:!0});const c=x(a,e.release),h=await D(a,c,e.release,e.commandExecutor,e.signal);return ie(d.join(i,R),ne(e.release,h.npmVersion)),await renameNodeToolchainPublishDirectory(i,e.paths.installDirectory,e.signal),{binDir:S(e.paths.contentDirectory,e.release),nodeExecutable:d.join(e.paths.contentDirectory,c.nodeRelativePath),npmVersion:h.npmVersion,toolchainDir:e.paths.installDirectory}}finally{u.rmSync(r,{recursive:!0,force:!0})}}export async function renameNodeToolchainPublishDirectory(e,r,t){for(let n=0;;n+=1){m(t);try{await u.promises.rename(e,r);return}catch(o){const i=nodeToolchainPublishRetryDelay(o,process.platform,n);if(i===null||ee(r))throw o;await f(new Promise(a=>setTimeout(a,i)),t)}}}function ee(e){try{return u.lstatSync(e).isDirectory()&&u.readdirSync(e).length>0}catch{return!1}}export function nodeToolchainPublishRetryDelay(e,r,t){if(r!=="win32"||!(e instanceof Error))return null;const n=e.code;return n!=="EACCES"&&n!=="EBUSY"&&n!=="EPERM"?null:Z[t]??null}async function b(e,r,t,n,o){if(!$(e))return null;const i=p(e);if(i===null)return null;const a=oe(i);if(a===null||!ae(a,r))return null;U(e);const s=o?N(e,i):null;try{await g(e.contentDirectory,r.installed,n)}catch(h){return m(n),h instanceof Y&&s!==null&&T(e,s)&&o?.(s),null}const l=x(e.contentDirectory,r),c=await D(e.contentDirectory,l,r,t,n);return c.npmVersion!==a.npmVersion?null:{binDir:S(e.contentDirectory,r),nodeExecutable:l.nodeExecutable,npmVersion:c.npmVersion,toolchainDir:e.installDirectory}}function N(e,r){try{const t=u.lstatSync(e.installDirectory,{bigint:!0});return!t.isDirectory()||t.isSymbolicLink()||t.ino===0n||t.birthtimeNs===0n?null:{birthtimeNs:t.birthtimeNs,dev:t.dev,ino:t.ino,manifestSource:r}}catch{return null}}function T(e,r){try{const t=N(e,r.manifestSource);return t!==null&&t.birthtimeNs===r.birthtimeNs&&t.dev===r.dev&&t.ino===r.ino&&p(e)===r.manifestSource}catch{return!1}}async function re(e,r,t,n){const o=[];let i=0;if(e.body!==void 0&&e.body!==null){const l=e.body[Symbol.asyncIterator]();for(;;){const c=await f(l.next(),n);if(c.done)break;if(!(c.value instanceof Uint8Array))throw new Error("Node toolchain download yielded a non-binary chunk");const h=Buffer.from(c.value.buffer,c.value.byteOffset,c.value.byteLength);if(i+=h.byteLength,i>t.archive.sizeBytes)throw await l.return?.(),new Error(`Node toolchain archive exceeds its trusted ${t.archive.sizeBytes.toString()} byte size`);o.push(h)}}else{const l=await f(e.arrayBuffer(),n),c=Buffer.from(l);i=c.byteLength,i<=t.archive.sizeBytes&&o.push(c)}if(i!==t.archive.sizeBytes)throw new Error(`Node toolchain archive size mismatch: expected ${t.archive.sizeBytes.toString()}, received ${i.toString()}`);const a=Buffer.concat(o,i),s=V.createHash("sha256").update(a).digest("hex");if(s!==t.archive.sha256)throw new Error(`Node toolchain archive digest mismatch: expected ${t.archive.sha256}, received ${s}`);m(n),u.writeFileSync(r,a,{flag:"wx",mode:384})}function x(e,r){const t=r.target.platform==="win32"?"node.exe":"bin/node",n=r.target.platform==="win32"?"npm.cmd":"bin/npm";return{nodeExecutable:v(e,t,{allowSymbolicLink:!1}),nodeRelativePath:t,npmCli:r.target.platform==="win32"?v(e,"node_modules/npm/bin/npm-cli.js",{allowSymbolicLink:!1}):null,npmExecutable:v(e,n,{allowSymbolicLink:r.target.platform!=="win32"})}}async function D(e,r,t,n,o){const i=S(e,t),a=await O(r.nodeExecutable,e,i,n,o);if(a!==t.version)throw new Error(`Downloaded Node toolchain version mismatch: expected ${t.version}, received ${a}`);return{npmVersion:await O(r.npmCli===null?r.npmExecutable:r.nodeExecutable,e,i,n,o,r.npmCli===null?[]:[r.npmCli])}}async function O(e,r,t,n,o,i=[]){const a=nodeToolchainVersionInvocation(r,t,process.env,i),s=await n(e,a.args,{...a.options,signal:o});return te(typeof s.stdout=="string"?s.stdout:Buffer.from(s.stdout??"").toString())}export function nodeToolchainVersionInvocation(e,r,t,n=[]){const o=(t.PATH??"").split(d.delimiter).filter(i=>i.length>0&&i!==r);return{args:[...n,"--version"],options:{cwd:e,env:{...t,PATH:[r,...o].join(d.delimiter)},stdio:"pipe",timeoutMs:W,windowsHide:!0}}}function te(e){const r=e.trim().replace(/^v/u,"");if(!/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/u.test(r))throw new Error(`Toolchain command returned an invalid version: ${JSON.stringify(e)}`);return r}function S(e,r){return r.target.platform==="win32"?e:d.join(e,"bin")}function ne(e,r){return{arch:e.target.arch,archiveSha256:e.archive.sha256,installedSha256:e.installed.sha256,npmVersion:r,platform:e.target.platform,schemaVersion:w,version:e.version}}function ie(e,r){u.writeFileSync(e,`${JSON.stringify(r,null,2)}
2
- `,{encoding:"utf8",flag:"wx",mode:384})}function oe(e){let r;try{r=JSON.parse(e)}catch{return null}if(!G(r))return null;const t=["arch","archiveSha256","installedSha256","npmVersion","platform","schemaVersion","version"],n=Object.keys(r).filter(o=>o!=="kind").sort();return n.length!==t.length||n.some((o,i)=>o!==t[i])||r.schemaVersion!==w||!A(r.arch)||!C(r.archiveSha256)||!C(r.installedSha256)||!_(r.npmVersion)||!A(r.platform)||!_(r.version)?null:{arch:r.arch,archiveSha256:r.archiveSha256,installedSha256:r.installedSha256,npmVersion:r.npmVersion,platform:r.platform,schemaVersion:w,version:r.version}}function ae(e,r){return e.version===r.version&&e.platform===r.target.platform&&e.arch===r.target.arch&&e.archiveSha256===r.archive.sha256&&e.installedSha256===r.installed.sha256}function ce(e,r){const t=u.readdirSync(e);if(t.length!==1||t[0]!==d.basename(r)||!u.lstatSync(r).isDirectory()||u.lstatSync(r).isSymbolicLink())throw new Error(`Expected Node archive to contain only ${d.basename(r)}`)}function I(e,r){y.get(e)===r&&y.delete(e)}function se(e){if(!Number.isSafeInteger(e)||e<=0)throw new Error("Node archive timeout must be a positive safe integer")}function A(e){return typeof e=="string"&&e.length>0}function C(e){return typeof e=="string"&&/^[a-f0-9]{64}$/u.test(e)}function _(e){return typeof e=="string"&&/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/u.test(e)}
1
+ import V from"node:crypto";import u from"node:fs";import d from"node:path";import{TarArchiveInspector as z,ZipArchiveExtractor as M}from"./archive.js";import{withProcessLock as L,withProcessLockProgress as P}from"../process/locks.js";import{createContainedTemporaryDirectory as $,existingRealInstallDirectory as B,NODE_TOOLCHAIN_CONTENT_DIRECTORY as R,NODE_TOOLCHAIN_MARKER_FILENAME as j,nodeToolchainCacheKey as H,prepareNodeToolchainCache as F,readRealManifestFile as p,removeContainedInstallDirectory as U,requireContainedExecutable as v,requireRealContentDirectory as Y}from"./node-cache.js";import{InstalledNodeTreeMismatchError as q,validateNodeArchiveEntries as J,verifyInstalledNodeTree as g}from"./node-integrity.js";import{createSharedToolchainOperation as K,throwIfSignalAborted as m,waitForPromise as f,waitForSharedToolchainOperation as W}from"./shared-operation.js";import{TOOLCHAIN_METADATA_TIMEOUT_MS as G}from"./timeouts.js";import{isUnknownRecord as Z}from"../kernel/unknown-values.js";const w=2,E=300*1e3,Q=Object.freeze([100,250,500,1e3,2e3,3e3,3e3]),y=new Map;export async function provisionNodeToolchainWithRelease({archiveTimeoutMs:e=E,commandExecutor:r,fetchImpl:t,productDir:n,release:o,signal:i,zipExtractor:a}){m(i);const s=F(n,o),l=H(s);let c=y.get(l);if(c===void 0){c=K(k=>X({archiveTimeoutMs:e,commandExecutor:r,fetchImpl:t,paths:s,productDir:n,release:o,signal:k,zipExtractor:a})),y.set(l,c);const h=c;c.promise.then(()=>{I(l,h)},()=>{I(l,h)})}return W(c,i)}export async function extractTrustedNodeArchive({archivePath:e,commandExecutor:r,destination:t,release:n,signal:o,timeoutMs:i=E,zipExtractor:a=new M}){if(le(i),u.existsSync(t))throw new Error(`Node archive destination already exists: ${t}`);u.mkdirSync(t,{mode:448});const s=AbortSignal.timeout(i),l=o===void 0?s:AbortSignal.any([o,s]),c=n.archive.format==="zip"?a:new z;try{const h=await c.inspect(e,n.archive.format,{maxEntries:n.archive.entryCount,maxUnpackedSizeBytes:n.archive.unpackedSizeBytes,signal:l});J(h,n),n.archive.format==="zip"?await a.extract(e,t,n.archive.format,{maxEntries:n.archive.entryCount,maxUnpackedSizeBytes:n.archive.unpackedSizeBytes,signal:l}):(await r("tar",["-xzf",e,"-C",t],{cwd:d.dirname(t),env:process.env,signal:l,timeoutMs:i}),m(l))}catch(h){throw u.rmSync(t,{recursive:!0,force:!0}),h}}async function X(e){let r=null;const t=await b(e.paths,e.release,e.commandExecutor,e.signal,n=>{r=n});return t!==null?t:L(e.paths.lockPath,()=>P(`installing Node ${e.release.version}`,async()=>{if(m(e.signal),r===null||!T(e.paths,r)){const n=await b(e.paths,e.release,e.commandExecutor,e.signal);if(n!==null)return n}return U(e.paths),ee(e)}),{signal:e.signal,timeoutMs:null})}async function ee(e){const r=$(e.paths.rootDirectory,`.tmp-${e.release.version}`),t=e.release.archive.format==="zip"?".zip":".tar.gz",n=d.join(r,`node${t}`),o=d.join(r,"extracted"),i=d.join(r,"publish"),a=d.join(i,R);try{const s=await f(e.fetchImpl(e.release.archive.url,{signal:e.signal}),e.signal);if(!s.ok)throw new Error(`Failed to download ${e.release.archive.url}: HTTP ${String(s.status)}`);await te(s,n,e.release,e.signal),await extractTrustedNodeArchive({archivePath:n,commandExecutor:e.commandExecutor,destination:o,release:e.release,signal:e.signal,timeoutMs:e.archiveTimeoutMs,zipExtractor:e.zipExtractor});const l=d.join(o,e.release.archive.rootDirectory);se(o,l),await g(l,e.release.installed,e.signal),u.mkdirSync(i,{mode:448}),u.renameSync(l,a),u.rmSync(o,{recursive:!0,force:!0}),u.rmSync(n,{force:!0});const c=x(a,e.release),h=await D(a,c,e.release,e.commandExecutor,e.signal);return oe(d.join(i,j),ie(e.release,h.npmVersion)),await renameNodeToolchainPublishDirectory(i,e.paths.installDirectory,e.signal),{binDir:S(e.paths.contentDirectory,e.release),nodeExecutable:d.join(e.paths.contentDirectory,c.nodeRelativePath),npmVersion:h.npmVersion,toolchainDir:e.paths.installDirectory}}finally{u.rmSync(r,{recursive:!0,force:!0})}}export async function renameNodeToolchainPublishDirectory(e,r,t){for(let n=0;;n+=1){m(t);try{await u.promises.rename(e,r);return}catch(o){const i=nodeToolchainPublishRetryDelay(o,process.platform,n);if(i===null||re(r))throw o;await f(new Promise(a=>setTimeout(a,i)),t)}}}function re(e){try{return u.lstatSync(e).isDirectory()&&u.readdirSync(e).length>0}catch{return!1}}export function nodeToolchainPublishRetryDelay(e,r,t){if(r!=="win32"||!(e instanceof Error))return null;const n=e.code;return n!=="EACCES"&&n!=="EBUSY"&&n!=="EPERM"?null:Q[t]??null}async function b(e,r,t,n,o){if(!B(e))return null;const i=p(e);if(i===null)return null;const a=ae(i);if(a===null||!ce(a,r))return null;Y(e);const s=o?N(e,i):null;try{await g(e.contentDirectory,r.installed,n)}catch(h){return m(n),h instanceof q&&s!==null&&T(e,s)&&o?.(s),null}const l=x(e.contentDirectory,r),c=await D(e.contentDirectory,l,r,t,n);return c.npmVersion!==a.npmVersion?null:{binDir:S(e.contentDirectory,r),nodeExecutable:l.nodeExecutable,npmVersion:c.npmVersion,toolchainDir:e.installDirectory}}function N(e,r){try{const t=u.lstatSync(e.installDirectory,{bigint:!0});return!t.isDirectory()||t.isSymbolicLink()||t.ino===0n||t.birthtimeNs===0n?null:{birthtimeNs:t.birthtimeNs,dev:t.dev,ino:t.ino,manifestSource:r}}catch{return null}}function T(e,r){try{const t=N(e,r.manifestSource);return t!==null&&t.birthtimeNs===r.birthtimeNs&&t.dev===r.dev&&t.ino===r.ino&&p(e)===r.manifestSource}catch{return!1}}async function te(e,r,t,n){const o=[];let i=0;if(e.body!==void 0&&e.body!==null){const l=e.body[Symbol.asyncIterator]();for(;;){const c=await f(l.next(),n);if(c.done)break;if(!(c.value instanceof Uint8Array))throw new Error("Node toolchain download yielded a non-binary chunk");const h=Buffer.from(c.value.buffer,c.value.byteOffset,c.value.byteLength);if(i+=h.byteLength,i>t.archive.sizeBytes)throw await l.return?.(),new Error(`Node toolchain archive exceeds its trusted ${t.archive.sizeBytes.toString()} byte size`);o.push(h)}}else{const l=await f(e.arrayBuffer(),n),c=Buffer.from(l);i=c.byteLength,i<=t.archive.sizeBytes&&o.push(c)}if(i!==t.archive.sizeBytes)throw new Error(`Node toolchain archive size mismatch: expected ${t.archive.sizeBytes.toString()}, received ${i.toString()}`);const a=Buffer.concat(o,i),s=V.createHash("sha256").update(a).digest("hex");if(s!==t.archive.sha256)throw new Error(`Node toolchain archive digest mismatch: expected ${t.archive.sha256}, received ${s}`);m(n),u.writeFileSync(r,a,{flag:"wx",mode:384})}function x(e,r){const t=r.target.platform==="win32"?"node.exe":"bin/node",n=r.target.platform==="win32"?"npm.cmd":"bin/npm";return{nodeExecutable:v(e,t,{allowSymbolicLink:!1}),nodeRelativePath:t,npmCli:r.target.platform==="win32"?v(e,"node_modules/npm/bin/npm-cli.js",{allowSymbolicLink:!1}):null,npmExecutable:v(e,n,{allowSymbolicLink:r.target.platform!=="win32"})}}async function D(e,r,t,n,o){const i=S(e,t),a=await O(r.nodeExecutable,e,i,n,o);if(a!==t.version)throw new Error(`Downloaded Node toolchain version mismatch: expected ${t.version}, received ${a}`);return{npmVersion:await O(r.npmCli===null?r.npmExecutable:r.nodeExecutable,e,i,n,o,r.npmCli===null?[]:[r.npmCli])}}async function O(e,r,t,n,o,i=[]){const a=nodeToolchainVersionInvocation(r,t,process.env,i),s=await n(e,a.args,{...a.options,signal:o});return ne(typeof s.stdout=="string"?s.stdout:Buffer.from(s.stdout??"").toString())}export function nodeToolchainVersionInvocation(e,r,t,n=[]){const o=(t.PATH??"").split(d.delimiter).filter(i=>i.length>0&&i!==r);return{args:[...n,"--version"],options:{cwd:e,env:{...t,PATH:[r,...o].join(d.delimiter)},stdio:"pipe",timeoutMs:G,windowsHide:!0}}}function ne(e){const r=e.trim().replace(/^v/u,"");if(!/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/u.test(r))throw new Error(`Toolchain command returned an invalid version: ${JSON.stringify(e)}`);return r}function S(e,r){return r.target.platform==="win32"?e:d.join(e,"bin")}function ie(e,r){return{arch:e.target.arch,archiveSha256:e.archive.sha256,installedSha256:e.installed.sha256,npmVersion:r,platform:e.target.platform,schemaVersion:w,version:e.version}}function oe(e,r){u.writeFileSync(e,`${JSON.stringify(r,null,2)}
2
+ `,{encoding:"utf8",flag:"wx",mode:384})}function ae(e){let r;try{r=JSON.parse(e)}catch{return null}if(!Z(r))return null;const t=["arch","archiveSha256","installedSha256","npmVersion","platform","schemaVersion","version"],n=Object.keys(r).filter(o=>o!=="kind").sort();return n.length!==t.length||n.some((o,i)=>o!==t[i])||r.schemaVersion!==w||!A(r.arch)||!C(r.archiveSha256)||!C(r.installedSha256)||!_(r.npmVersion)||!A(r.platform)||!_(r.version)?null:{arch:r.arch,archiveSha256:r.archiveSha256,installedSha256:r.installedSha256,npmVersion:r.npmVersion,platform:r.platform,schemaVersion:w,version:r.version}}function ce(e,r){return e.version===r.version&&e.platform===r.target.platform&&e.arch===r.target.arch&&e.archiveSha256===r.archive.sha256&&e.installedSha256===r.installed.sha256}function se(e,r){const t=u.readdirSync(e);if(t.length!==1||t[0]!==d.basename(r)||!u.lstatSync(r).isDirectory()||u.lstatSync(r).isSymbolicLink())throw new Error(`Expected Node archive to contain only ${d.basename(r)}`)}function I(e,r){y.get(e)===r&&y.delete(e)}function le(e){if(!Number.isSafeInteger(e)||e<=0)throw new Error("Node archive timeout must be a positive safe integer")}function A(e){return typeof e=="string"&&e.length>0}function C(e){return typeof e=="string"&&/^[a-f0-9]{64}$/u.test(e)}function _(e){return typeof e=="string"&&/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/u.test(e)}
@@ -1 +1 @@
1
- import{isUnknownRecord as o}from"../../kernel/unknown-values.js";const s=["**/*.contract.testkit.ts","**/*.data.testkit.ts","**/*.ui.testkit.ts"];export function defineConfig(t={},r={}){const e=Reflect.get(t,"test"),n={...o(e)?e:{}},i=new Set([...c(n.exclude,"config.test.exclude"),...r.exclude??[],...s]);return{...t,test:{...n,exclude:[...i]}}}export function testkitExcludes(){return[...s]}function c(t,r){if(t===void 0)return[];if(!Array.isArray(t)||!t.every(e=>typeof e=="string"))throw new TypeError(`${r} must be an array of strings`);return t}
1
+ import{isUnknownRecord as i}from"../../kernel/unknown-values.js";const o=["**/*.ui.testkit.ts"];export function defineConfig(e={},r={}){const t=Reflect.get(e,"test"),n={...i(t)?t:{}},s=new Set([...c(n.exclude,"config.test.exclude"),...r.exclude??[],...o]);return{...e,test:{...n,exclude:[...s]}}}export function testkitExcludes(){return[...o]}function c(e,r){if(e===void 0)return[];if(!Array.isArray(e)||!e.every(t=>typeof t=="string"))throw new TypeError(`${r} must be an array of strings`);return e}
@@ -1 +1 @@
1
- import{createTrackerHttp as b,TRACKER_PAGE_LIMIT as y,TrackerError as f}from"./http.js";import{commentRequest as T,commitStatusRequest as g,getRequest as k,TRACKER_USER_AGENT as h,trackerRepository as w,trackerRoutes as I}from"./requests.js";import{decodeGiteaVersion as R,decodeTrackerComment as G,decodeTrackerComments as S,decodeTrackerCommitStatusContext as C,decodeTrackerIssue as A,decodeTrackerIssues as E,decodeTrackerLabel as L,decodeTrackerLabels as q,requireIssueLabels as x}from"./responses.js";const $="50";export function giteaHeaders(a){return{Accept:"application/json",Authorization:`token ${a}`,"User-Agent":h}}export function giteaRequests(a){const o=I(a),r=(s,i,m={})=>k(s,{...m,limit:$,page:String(i)});return{routes:o,createIssue:(s,i)=>({body:{body:s.body,labels:i,title:s.title},method:"POST",url:o.issues}),createLabel:s=>({body:{color:`#${s.color}`,description:s.description,name:s.name},method:"POST",url:o.labels}),listComments:(s,i)=>r(o.comments(s),i),listLabels:s=>r(o.labels,s),listOpenIssues:(s,i)=>r(o.issues,i,{labels:s,state:"open",type:"issues"})}}export async function readGiteaVersion(a){const r=await b({headers:{Accept:"application/json","User-Agent":h},label:"Gitea version probe",secrets:[]}).exchange({method:"GET",url:`${a}/api/v1/version`});return r.ok?R(r.body):null}function O(a){const o=a.get("x-total-count");return o!==null&&/^\d+$/u.test(o)?Number(o):Number.POSITIVE_INFINITY}export function createGiteaTracker(a){const o=w(a),r=giteaRequests(o),s=b({headers:giteaHeaders(a.token),label:"Gitea",secrets:[a.token]});async function i(t,c,e){const n=new Map;for(let u=1;u<=y;u++){const l=await s.send(t(u)),p=n.size;for(const d of c(l.body,l.source))n.has(e(d))||n.set(e(d),d);if(n.size===p||n.size>=O(l.headers))return[...n.values()]}throw new f("rejected",`Gitea returned more than ${String(y)} pages`)}const m=()=>i(r.listLabels,q,t=>t.id);return{target:o,async createIssue(t){const c=t.labels.length===0?[]:await m(),e=t.labels.map(l=>{const p=c.find(d=>d.name===l);if(p===void 0)throw new f("rejected",`Gitea repository ${o.owner}/${o.name} has no label named ${JSON.stringify(l)}`);return p.id}),n=await s.send(r.createIssue(t,e)),{issue:u}=A(n.body,n.source);return x(u,t.labels,"Gitea",o)},async ensureLabels(t){if(t.length===0)return;const c=new Set((await m()).map(e=>e.name));for(const e of t){if(c.has(e.name))continue;const n=await s.send(r.createLabel(e));L(n.body,n.source),c.add(e.name)}},async listOpenIssues(t){return(await i(e=>r.listOpenIssues(t,e),E,e=>e.issue.number)).filter(e=>!e.pullRequest&&e.issue.labels.includes(t)).map(e=>e.issue)},async setCommitStatus(t){const c=await s.send(g(r.routes,t));C(c.body,c.source)},async upsertPullRequestComment(t){const e=(await i(u=>r.listComments(t.pullRequest,u),S,u=>u.id)).find(u=>u.body.startsWith(t.marker)),n=await s.send(T(r.routes,t,e));return{created:e===void 0,url:G(n.body,n.source).url}}}}
1
+ import{createTrackerHttp as f,TRACKER_PAGE_LIMIT as h,TrackerError as g}from"./http.js";import{commentRequest as I,commitStatusRequest as T,getRequest as k,TRACKER_USER_AGENT as w,trackerRepository as L,trackerRoutes as E}from"./requests.js";import{decodeGiteaVersion as R,decodeTrackerComment as S,decodeTrackerComments as G,decodeTrackerCommitStatus as A,decodeTrackerIssue as C,decodeTrackerIssues as $,decodeTrackerLabel as x,decodeTrackerLabels as q,missingIssueLabels as O,unlabelledIssueError as P}from"./responses.js";import{closeDuplicateIssue as N,closeIssue as _,commenterLogin as j}from"./ownership.js";const v="50";export function giteaHeaders(u){return{Accept:"application/json",Authorization:`token ${u}`,"User-Agent":w}}export function giteaRequests(u){const r=E(u),n=(e,l,d={})=>k(e,{...d,limit:v,page:String(l)});return{routes:r,createIssue:(e,l)=>({body:{body:e.body,labels:l,title:e.title},method:"POST",url:r.issues}),createLabel:e=>({body:{color:`#${e.color}`,description:e.description,name:e.name},method:"POST",url:r.labels}),deleteLabel:e=>({method:"DELETE",url:`${r.labels}/${String(e)}`}),listComments:(e,l)=>n(r.comments(e),l),listLabels:e=>n(r.labels,e),listOpenIssues:(e,l)=>n(r.issues,l,{...e===null?{}:{labels:e},state:"open",type:"issues"})}}export async function readGiteaVersion(u){const n=await f({headers:{Accept:"application/json","User-Agent":w},label:"Gitea version probe",secrets:[]}).exchange({method:"GET",url:`${u}/api/v1/version`});return n.ok?R(n.body):null}function z(u){const r=u.get("x-total-count");return r!==null&&/^\d+$/u.test(r)?Number(r):Number.POSITIVE_INFINITY}export function createGiteaTracker(u){const r=L(u),n=giteaRequests(r),e=f({headers:giteaHeaders(u.token),label:"Gitea",secrets:[u.token]});async function l(s,i,c){const o=new Map;for(let t=1;t<=h;t++){const a=await e.send(s(t)),p=o.size;for(const m of i(a.body,a.source))o.has(c(m))||o.set(c(m),m);if(o.size===p||o.size>=z(a.headers))return[...o.values()]}throw new g("rejected",`Gitea returned more than ${String(h)} pages`)}const d=()=>l(n.listLabels,q,s=>s.id);return{target:r,closeDuplicateIssue(s,i){return N(e,n.routes,s,i,"gitea")},async createIssue(s){const i=s.labels.length===0?[]:await d(),c=s.labels.map(m=>{const b=y(i,m);if(b===void 0)throw new g("rejected",`Gitea repository ${r.owner}/${r.name} has no label named ${JSON.stringify(m)}`);return b.id}),o=await e.send(n.createIssue(s,c)),{issue:t}=C(o.body,o.source),a=O(t,s.labels);if(a.length===0)return t;const p=await _(e,n.routes,t,"gitea");throw P("Gitea",t,a,r,p)},async ensureLabels(s){if(s.length===0)return;const i=new Set((await d()).map(t=>t.name)),c=[];for(const t of s){if(i.has(t.name))continue;const a=await e.send(n.createLabel(t));c.push(x(a.body,a.source)),i.add(t.name)}if(c.length===0)return;const o=await d();for(const t of c){if(y(o,t.name)?.id===t.id)continue;const a=await e.exchange(n.deleteLabel(t.id));if(!a.ok&&a.status!==404)throw e.failure(a)}},async listOpenIssues(s){const i=o=>l(t=>n.listOpenIssues(o,t),$,t=>t.issue.number);let c=await i(s);return(await d()).filter(o=>o.name===s).length>1&&(c=await i(null)),c.filter(o=>!o.pullRequest&&o.issue.labels.includes(s)).map(o=>o.issue)},async setCommitStatus(s){const i=await e.send(T(n.routes,s));return{creator:A(i.body,i.source).creator}},async upsertPullRequestComment(s){const i=await l(a=>n.listComments(s.pullRequest,a),G,a=>a.id),c=s.author??await j(e,r.apiUrl),o=c===null?void 0:i.find(a=>a.body.startsWith(s.marker)&&a.author===c),t=await e.send(I(n.routes,s,o));return{created:o===void 0,url:S(t.body,t.source).url}}}}function y(u,r){return u.filter(n=>n.name===r).sort((n,e)=>n.id-e.id)[0]}
@@ -1 +1 @@
1
- import{createTrackerHttp as g,nextPageLink as w,TRACKER_PAGE_LIMIT as m,TrackerError as b}from"./http.js";import{commentRequest as f,commitStatusRequest as L,getRequest as d,TRACKER_USER_AGENT as h,trackerRepository as k,trackerRoutes as y}from"./requests.js";import{decodeTrackerComment as R,decodeTrackerComments as T,decodeTrackerCommitStatusContext as I,decodeTrackerIssue as C,decodeTrackerIssues as x,decodeTrackerLabel as A,decodeTrackerLabels as E,isDuplicateLabelRejection as P,requireIssueLabels as q}from"./responses.js";const p="100",G=100;export function githubHeaders(u){return{Accept:"application/vnd.github+json",Authorization:`Bearer ${u}`,"User-Agent":h,"X-GitHub-Api-Version":"2022-11-28"}}export function githubRequests(u){const i=y(u);return{routes:i,createIssue:e=>({body:{body:e.body,labels:e.labels,title:e.title},method:"POST",url:i.issues}),createLabel:e=>({body:{color:e.color,description:Array.from(e.description).slice(0,G).join(""),name:e.name},method:"POST",url:i.labels}),listComments:e=>d(i.comments(e),{per_page:p}),listLabels:()=>d(i.labels,{per_page:p}),listOpenIssues:e=>d(i.issues,{labels:e,per_page:p,state:"open"}),nextPage(e){const a=w(e);if(a===null)return null;if(!URL.canParse(a)||new URL(a).origin!==new URL(u.apiUrl).origin)throw new b("rejected",`GitHub returned a next page outside ${u.apiUrl}`);return{method:"GET",url:a}}}}export function createGithubTracker(u){const i=k(u),e=githubRequests(i),a=g({headers:githubHeaders(u.token),label:"GitHub",secrets:[u.token]});async function c(t,o){const s=[];let r=t;for(let n=0;r!==null;n++){if(n===m)throw new b("rejected",`GitHub returned more than ${String(m)} pages`);const l=await a.send(r);s.push(...o(l.body,l.source)),r=e.nextPage(l.headers.get("link"))}return s}return{target:i,async createIssue(t){const o=await a.send(e.createIssue(t)),{issue:s}=C(o.body,o.source);return q(s,t.labels,"GitHub",i)},async ensureLabels(t){if(t.length===0)return;const o=new Set((await c(e.listLabels(),E)).map(s=>s.name.toLowerCase()));for(const s of t){const r=s.name.toLowerCase();if(o.has(r))continue;const n=await a.exchange(e.createLabel(s));if(n.ok)A(n.body,n.source);else if(n.status!==422||!P(n.body))throw a.failure(n);o.add(r)}},async listOpenIssues(t){const o=t.toLowerCase(),s=new Map;for(const r of await c(e.listOpenIssues(t),x))r.pullRequest||!r.issue.labels.some(n=>n.toLowerCase()===o)||s.set(r.issue.number,r.issue);return[...s.values()]},async setCommitStatus(t){const o=await a.send(L(e.routes,t));I(o.body,o.source)},async upsertPullRequestComment(t){const s=(await c(e.listComments(t.pullRequest),T)).find(n=>n.body.startsWith(t.marker)),r=await a.send(f(e.routes,t,s));return{created:s===void 0,url:R(r.body,r.source).url}}}}
1
+ import{createTrackerHttp as g,nextPageLink as h,TRACKER_PAGE_LIMIT as m,TrackerError as b}from"./http.js";import{commentRequest as w,commitStatusRequest as f,getRequest as d,TRACKER_USER_AGENT as L,trackerRepository as I,trackerRoutes as k}from"./requests.js";import{decodeTrackerComment as y,decodeTrackerComments as R,decodeTrackerCommitStatus as T,decodeTrackerIssue as C,decodeTrackerIssues as E,decodeTrackerLabel as x,decodeTrackerLabels as A,isDuplicateLabelRejection as P,missingIssueLabels as G,unlabelledIssueError as S}from"./responses.js";import{closeDuplicateIssue as _,closeIssue as q,commenterLogin as U}from"./ownership.js";const p="100",H=100;export function githubHeaders(i){return{Accept:"application/vnd.github+json",Authorization:`Bearer ${i}`,"User-Agent":L,"X-GitHub-Api-Version":"2022-11-28"}}export function githubRequests(i){const u=k(i);return{routes:u,createIssue:e=>({body:{body:e.body,labels:e.labels,title:e.title},method:"POST",url:u.issues}),createLabel:e=>({body:{color:e.color,description:Array.from(e.description).slice(0,H).join(""),name:e.name},method:"POST",url:u.labels}),listComments:e=>d(u.comments(e),{per_page:p}),listLabels:()=>d(u.labels,{per_page:p}),listOpenIssues:e=>d(u.issues,{labels:e,per_page:p,state:"open"}),nextPage(e){const a=h(e);if(a===null)return null;if(!URL.canParse(a)||new URL(a).origin!==new URL(i.apiUrl).origin)throw new b("rejected",`GitHub returned a next page outside ${i.apiUrl}`);return{method:"GET",url:a}}}}export function createGithubTracker(i){const u=I(i),e=githubRequests(u),a=g({headers:githubHeaders(i.token),label:"GitHub",secrets:[i.token]});async function l(s,o){const t=[];let r=s;for(let n=0;r!==null;n++){if(n===m)throw new b("rejected",`GitHub returned more than ${String(m)} pages`);const c=await a.send(r);t.push(...o(c.body,c.source)),r=e.nextPage(c.headers.get("link"))}return t}return{target:u,closeDuplicateIssue(s,o){return _(a,e.routes,s,o,"github")},async createIssue(s){const o=await a.send(e.createIssue(s)),{issue:t}=C(o.body,o.source),r=G(t,s.labels);if(r.length===0)return t;const n=await q(a,e.routes,t,"github");throw S("GitHub",t,r,u,n)},async ensureLabels(s){if(s.length===0)return;const o=new Set((await l(e.listLabels(),A)).map(t=>t.name.toLowerCase()));for(const t of s){const r=t.name.toLowerCase();if(o.has(r))continue;const n=await a.exchange(e.createLabel(t));if(n.ok)x(n.body,n.source);else if(n.status!==422||!P(n.body))throw a.failure(n);o.add(r)}},async listOpenIssues(s){const o=s.toLowerCase(),t=new Map;for(const r of await l(e.listOpenIssues(s),E))r.pullRequest||!r.issue.labels.some(n=>n.toLowerCase()===o)||t.set(r.issue.number,r.issue);return[...t.values()]},async setCommitStatus(s){const o=await a.send(f(e.routes,s));return{creator:T(o.body,o.source).creator}},async upsertPullRequestComment(s){const o=await l(e.listComments(s.pullRequest),R),t=s.author??await U(a,u.apiUrl),r=t===null?void 0:o.find(c=>c.body.startsWith(s.marker)&&c.author===t),n=await a.send(w(e.routes,s,r));return{created:r===void 0,url:y(n.body,n.source).url}}}}
@@ -1 +1 @@
1
- import{isUnknownRecord as g}from"../kernel/unknown-values.js";import{redactBugText as m}from"./redaction.js";const l=3e4,d=300,f=Symbol("not JSON");export const TRACKER_PAGE_LIMIT=1e3;export class TrackerError extends Error{kind;status;constructor(t,s,o=null){super(s),this.name="TrackerError",this.kind=t,this.status=o}}export function createTrackerHttp(e){const t=r=>scrubFailureDetail(r,e.secrets);async function s(r){const n=`${e.label} ${r.method} ${T(r.url)}`;let i,c;try{i=await fetch(r.url,{body:r.body===void 0?void 0:JSON.stringify(r.body),headers:r.body===void 0?e.headers:{...e.headers,"Content-Type":"application/json"},method:r.method,signal:AbortSignal.timeout(l)}),c=await i.text()}catch(h){throw new TrackerError("unreachable",`${n} did not complete: ${t(y(h))}`)}const u=p(c);return{body:u===f?c:u,headers:i.headers,json:u!==f,ok:i.ok,source:n,status:i.status}}function o(r){const n=t(w(r.body));return new TrackerError($(r.status),`${r.source} returned ${String(r.status)}${n.length>0?`: ${n}`:""}`,r.status)}async function a(r){const n=await s(r);if(!n.ok)throw o(n);if(!n.json)throw new TrackerError("rejected",`${n.source} returned a response that is not JSON`,n.status);return n}return{exchange:s,failure:o,send:a}}export function nextPageLink(e){if(e===null)return null;for(const t of e.matchAll(/<([^>]*)>([^,]*)/gu)){const s=/;\s*rel\s*=\s*(?:"([^"]*)"|([^;\s]*))/iu.exec(t[2]);if((s?.[1]??s?.[2]??"").toLowerCase().split(/\s+/u).includes("next"))return t[1]}return null}export function scrubFailureDetail(e,t){let s=e;for(const a of t)a.length>0&&(s=s.split(a).join("<redacted>"));const o=m(s.replace(/\s+/gu," ").trim());return o.length>d?`${o.slice(0,d)}...`:o}function p(e){if(e.trim().length===0)return null;try{return JSON.parse(e)}catch{return f}}function $(e){return e===401?"unauthorized":e===403?"forbidden":e===404?"not-found":"rejected"}function w(e){if(typeof e=="string")return e;if(!g(e))return"";const t=typeof e.message=="string"?e.message:"";return Array.isArray(e.errors)?`${t} ${JSON.stringify(e.errors)}`:t}function y(e){if(!(e instanceof Error))return"the request failed";if(e.name==="TimeoutError")return`no answer within ${String(l/1e3)} seconds`;const t=e.cause;if(t instanceof Error){if(t.message.length>0)return t.message;if("code"in t&&typeof t.code=="string")return t.code}return e.message}function T(e){const t=new URL(e);return`${t.origin}${t.pathname}`}
1
+ import{isUnknownRecord as h}from"../kernel/unknown-values.js";import{recordWork as g}from"../kernel/work-metrics.js";import{redactBugText as y}from"./redaction.js";const f=3e4,d=300,l=Symbol("not JSON");export const TRACKER_PAGE_LIMIT=1e3;export class TrackerError extends Error{kind;retryAfterSeconds;status;constructor(e,n,s=null,i=null){super(n),this.name="TrackerError",this.kind=e,this.retryAfterSeconds=i,this.status=s}}export function createTrackerHttp(t){const e=r=>scrubFailureDetail(r,t.secrets);async function n(r){g(`tracker.requests.${r.method.toLowerCase()}`);const o=`${t.label} ${r.method} ${A(r.url)}`;let a,u;try{a=await fetch(r.url,{body:r.body===void 0?void 0:JSON.stringify(r.body),headers:r.body===void 0?t.headers:{...t.headers,"Content-Type":"application/json"},method:r.method,signal:AbortSignal.timeout(f)}),u=await a.text()}catch(m){throw new TrackerError("unreachable",`${o} did not complete: ${e(x(m))}`)}const c=p(u);return{body:c===l?u:c,headers:a.headers,json:c!==l,ok:a.ok,source:o,status:a.status}}function s(r){const o=e(w(r.body)),a=`${r.source} returned ${String(r.status)}${o.length>0?`: ${o}`:""}`,u=S(r,o);return u===null?new TrackerError($(r.status),a,r.status):new TrackerError("rate-limited",a,r.status,u.retryAfterSeconds)}async function i(r){const o=await n(r);if(!o.ok)throw s(o);if(!o.json)throw new TrackerError("rejected",`${o.source} returned a response that is not JSON`,o.status);return o}return{exchange:n,failure:s,send:i}}export function nextPageLink(t){if(t===null)return null;for(const e of t.matchAll(/<([^>]*)>([^,]*)/gu)){const n=/;\s*rel\s*=\s*(?:"([^"]*)"|([^;\s]*))/iu.exec(e[2]);if((n?.[1]??n?.[2]??"").toLowerCase().split(/\s+/u).includes("next"))return e[1]}return null}export function scrubFailureDetail(t,e){let n=t;for(const i of e)i.length>0&&(n=n.split(i).join("<redacted>"));const s=y(n.replace(/\s+/gu," ").trim());return s.length>d?`${s.slice(0,d)}...`:s}function p(t){if(t.trim().length===0)return null;try{return JSON.parse(t)}catch{return l}}function S({headers:t,status:e},n){if(e!==403&&e!==429)return null;const s=t.get("retry-after")?.trim()??"";if(/^\d+$/u.test(s))return{retryAfterSeconds:Number(s)};const i=Number(t.get("x-ratelimit-reset")?.trim()||Number.NaN);return t.get("x-ratelimit-remaining")==="0"?{retryAfterSeconds:Number.isSafeInteger(i)&&i>=0?Math.max(0,i-Math.floor(Date.now()/1e3)):null}:e===429||/rate limit/iu.test(n)?{retryAfterSeconds:null}:null}function $(t){return t===401?"unauthorized":t===403?"forbidden":t===404?"not-found":"rejected"}function w(t){if(typeof t=="string")return t;if(!h(t))return"";const e=typeof t.message=="string"?t.message:"";return Array.isArray(t.errors)?`${e} ${JSON.stringify(t.errors)}`:e}function x(t){if(!(t instanceof Error))return"the request failed";if(t.name==="TimeoutError")return`no answer within ${String(f/1e3)} seconds`;const e=t.cause;if(e instanceof Error){if(e.message.length>0)return e.message;if("code"in e&&typeof e.code=="string")return e.code}return t.message}function A(t){const e=new URL(t);return`${e.origin}${e.pathname}`}
@@ -0,0 +1 @@
1
+ import{decodeTrackerUserLogin as c}from"./responses.js";export async function commenterLogin(t,n){const e=await t.exchange({method:"GET",url:`${n}/user`});if(e.ok)return c(e.body,e.source);if([401,403,404].includes(e.status))return null;throw t.failure(e)}export async function closeIssue(t,n,e,o){const s=o==="github"?{state:"closed",state_reason:"not_planned"}:{state:"closed"};try{return(await t.exchange({body:s,method:"PATCH",url:`${n.issues}/${String(e.number)}`})).ok}catch{return!1}}export async function closeDuplicateIssue(t,n,e,o,s){if(!await closeIssue(t,n,e,s))return!1;const r=`Testkit filed this issue from two checkouts at once. #${String(o.number)} covers the same test, so Testkit closed this one.`;return await t.exchange({body:{body:r},method:"POST",url:n.comments(e.number)}).catch(()=>{}),!0}
@@ -1 +1 @@
1
- import{REDACTED as n,redactSensitiveText as c}from"../kernel/redaction.js";export{redactSensitiveText as redactBugText}from"../kernel/redaction.js";const i=/<redacted>(?!@)["']?/u;export function redactRunResultText(e){const t=e.replace(/\s+/gu," ").trim(),r=i.exec(t);return c(r===null?t:t.slice(0,r.index+r[0].length))}export function holdsRedaction(e){return e.includes(n)}
1
+ import{REDACTED as o,redactSensitiveText as c,SENSITIVE_KEY_PATTERN as i}from"../kernel/redaction.js";export{redactSensitiveText as redactBugText}from"../kernel/redaction.js";const l=/<redacted>(?!@)["']?/u;export function redactRunResultText(e){const r=e.replace(/\s+/gu," ").trim(),t=l.exec(r);return c(t===null?r:r.slice(0,t.index+t[0].length))}export function publishableUrl(e){if(e===null||!URL.canParse(e))return null;const r=new URL(e);if(r.username!==""||r.password!=="")return null;const t=[...r.searchParams,...new URLSearchParams(r.hash.slice(1))];return t.some(([n,s])=>i.test(n)&&s.trim()!=="")?null:[e,u(e),...t.map(([n,s])=>`${n}=${s}`)].every(n=>n!==null&&c(n)===n)?e:null}function u(e){try{return decodeURIComponent(e)}catch{return null}}export function holdsRedaction(e){return e.includes(o)}