@gfdelarue/testkit 0.4.3 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (276) hide show
  1. package/README.md +109 -22
  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/categories.js +1 -1
  5. package/dist/app/bugs/draft.js +3 -3
  6. package/dist/app/bugs/facts.d.ts +3 -2
  7. package/dist/app/bugs/facts.js +2 -2
  8. package/dist/app/bugs/guide.js +1 -1
  9. package/dist/app/bugs/run-reading.js +1 -1
  10. package/dist/app/db-schema/prepare.js +1 -1
  11. package/dist/app/destroy/index.js +1 -1
  12. package/dist/app/doctor/index.js +1 -1
  13. package/dist/app/environments/config-driven.d.ts +5 -0
  14. package/dist/app/environments/config-driven.js +1 -0
  15. package/dist/app/environments/routing.d.ts +6 -0
  16. package/dist/app/environments/routing.js +1 -0
  17. package/dist/app/environments/supplied.d.ts +3 -0
  18. package/dist/app/environments/supplied.js +1 -0
  19. package/dist/app/file-selection.js +1 -1
  20. package/dist/app/lint/native.js +1 -1
  21. package/dist/app/lint/options.d.ts +0 -1
  22. package/dist/app/lint/options.js +1 -1
  23. package/dist/app/lint/shared.d.ts +0 -2
  24. package/dist/app/lint/shared.js +1 -1
  25. package/dist/app/package-inspection.js +1 -1
  26. package/dist/app/reports/files.d.ts +13 -0
  27. package/dist/app/reports/files.js +1 -0
  28. package/dist/app/reports/prepare.d.ts +20 -0
  29. package/dist/app/reports/prepare.js +2 -0
  30. package/dist/app/reports/publish.d.ts +21 -0
  31. package/dist/app/reports/publish.js +1 -0
  32. package/dist/app/run/engine-evidence-persistence.js +1 -1
  33. package/dist/app/run/engine-execution-cleanup.js +1 -1
  34. package/dist/app/run/engine-execution-failures.js +1 -1
  35. package/dist/app/run/engine-execution.d.ts +10 -8
  36. package/dist/app/run/engine-execution.js +1 -1
  37. package/dist/app/run/engine-orchestration.d.ts +15 -34
  38. package/dist/app/run/engine-orchestration.js +1 -1
  39. package/dist/app/run/environment.d.ts +33 -0
  40. package/dist/app/run/environment.js +1 -0
  41. package/dist/app/run/failure-details.js +1 -1
  42. package/dist/app/run/live-run.d.ts +3 -1
  43. package/dist/app/run/live-run.js +1 -1
  44. package/dist/app/run/orchestrator.d.ts +8 -1
  45. package/dist/app/run/orchestrator.js +1 -1
  46. package/dist/app/run/planning.d.ts +1 -2
  47. package/dist/app/run/planning.js +1 -1
  48. package/dist/app/run/results.d.ts +1 -1
  49. package/dist/app/run/results.js +1 -1
  50. package/dist/app/run/run-finalization.d.ts +6 -2
  51. package/dist/app/run/run-finalization.js +1 -1
  52. package/dist/app/run/runtime-fixtures.js +1 -1
  53. package/dist/app/run/runtime-task-types.d.ts +1 -1
  54. package/dist/app/run/scheduler/index.js +1 -1
  55. package/dist/app/run/scheduler/locks.d.ts +6 -0
  56. package/dist/app/run/scheduler/locks.js +1 -0
  57. package/dist/app/run/selection.d.ts +0 -1
  58. package/dist/app/run/selection.js +1 -1
  59. package/dist/app/run/setup/runtime-node-setup-protocol.js +1 -1
  60. package/dist/app/run/setup/runtime-node-setup-worker.js +1 -1
  61. package/dist/app/run/task-leases.d.ts +15 -0
  62. package/dist/app/run/task-leases.js +1 -0
  63. package/dist/app/run/worker-loop.d.ts +13 -4
  64. package/dist/app/run/worker-loop.js +1 -1
  65. package/dist/app/tracker/index.d.ts +6 -1
  66. package/dist/app/tracker/index.js +1 -1
  67. package/dist/app/typecheck/index.js +4 -4
  68. package/dist/cli/command-catalog.d.ts +19 -0
  69. package/dist/cli/command-catalog.js +1 -1
  70. package/dist/cli/command-registry.js +1 -1
  71. package/dist/cli/commands/report/prepare.d.ts +12 -0
  72. package/dist/cli/commands/report/prepare.js +5 -0
  73. package/dist/cli/commands/report/publish.d.ts +13 -0
  74. package/dist/cli/commands/report/publish.js +2 -0
  75. package/dist/cli/json-errors.js +1 -1
  76. package/dist/cli/operations/run/operation.d.ts +3 -1
  77. package/dist/cli/operations/run/operation.js +2 -2
  78. package/dist/cli/renderers/doctor/text.js +1 -1
  79. package/dist/cli/renderers/run/formatting.js +1 -1
  80. package/dist/config/authoring/index.d.ts +4 -13
  81. package/dist/config/authoring/index.js +1 -1
  82. package/dist/config/authoring/preset-env.js +1 -1
  83. package/dist/config/loader/config-loader.js +1 -1
  84. package/dist/config/loader/database.js +1 -1
  85. package/dist/config/loader/decode-execution.d.ts +0 -2
  86. package/dist/config/loader/decode-execution.js +1 -1
  87. package/dist/config/loader/discover-tests.js +1 -1
  88. package/dist/config/loader/discovery.d.ts +0 -1
  89. package/dist/config/loader/discovery.js +2 -2
  90. package/dist/config/loader/env.js +1 -1
  91. package/dist/config/loader/execution-model.d.ts +0 -1
  92. package/dist/config/loader/execution-model.js +1 -1
  93. package/dist/config/loader/file-metadata.d.ts +1 -1
  94. package/dist/config/loader/file-metadata.js +1 -1
  95. package/dist/config/loader/groups.d.ts +33 -0
  96. package/dist/config/loader/groups.js +1 -0
  97. package/dist/config/loader/load.d.ts +0 -2
  98. package/dist/config/loader/load.js +1 -1
  99. package/dist/config/loader/runtime-isolation.js +1 -1
  100. package/dist/config/loader/runtime.d.ts +0 -9
  101. package/dist/config/loader/runtime.js +1 -1
  102. package/dist/config/loader/skip-config.js +1 -1
  103. package/dist/config/loader/validation.d.ts +1 -2
  104. package/dist/config/loader/validation.js +1 -1
  105. package/dist/config/loader/values.d.ts +0 -1
  106. package/dist/config/loader/values.js +1 -1
  107. package/dist/database/activity-gate.js +1 -1
  108. package/dist/database/admin.js +2 -2
  109. package/dist/database/lifecycle-cleanup.d.ts +1 -0
  110. package/dist/database/lifecycle-cleanup.js +1 -1
  111. package/dist/database/local-postgres.js +1 -1
  112. package/dist/database/naming.js +1 -1
  113. package/dist/database/schema-operations.d.ts +1 -1
  114. package/dist/database/schema-operations.js +1 -1
  115. package/dist/database/schema-source.js +3 -3
  116. package/dist/database/source-url.d.ts +0 -1
  117. package/dist/database/source-url.js +2 -2
  118. package/dist/database/state.js +1 -1
  119. package/dist/database/steps.js +3 -3
  120. package/dist/domain/file-timeout.d.ts +0 -3
  121. package/dist/domain/file-timeout.js +1 -1
  122. package/dist/domain/operation-display.js +1 -1
  123. package/dist/domain/test-outcomes.d.ts +0 -1
  124. package/dist/domain/test-outcomes.js +1 -1
  125. package/dist/engines/adapters/evidence-identity.js +1 -1
  126. package/dist/engines/adapters/execution.d.ts +0 -1
  127. package/dist/engines/adapters/execution.js +1 -1
  128. package/dist/engines/api/capabilities.d.ts +0 -10
  129. package/dist/engines/api/capabilities.js +1 -1
  130. package/dist/engines/api/execution-scope.js +1 -1
  131. package/dist/engines/api/k6-runtime-input.js +1 -1
  132. package/dist/engines/bundle-cache.js +1 -1
  133. package/dist/engines/contracts/fixture-manifest.js +1 -1
  134. package/dist/engines/definitions.d.ts +1 -0
  135. package/dist/engines/definitions.js +1 -1
  136. package/dist/engines/installations/playwright-resolution-child.js +1 -1
  137. package/dist/engines/installations/playwright.js +1 -1
  138. package/dist/engines/k6/adapter/evidence.js +4 -4
  139. package/dist/engines/k6/adapter/executor.js +1 -1
  140. package/dist/engines/k6/adapter/native-summary.js +1 -1
  141. package/dist/engines/k6/runtime/checks.js +1 -1
  142. package/dist/engines/k6/runtime/dal-fixtures.js +1 -1
  143. package/dist/engines/k6/runtime/dal-suite.js +1 -1
  144. package/dist/engines/k6/runtime/http-suite-runtime.js +1 -1
  145. package/dist/engines/k6/runtime/http.js +1 -1
  146. package/dist/engines/k6/runtime/shared/db-normalization.js +1 -1
  147. package/dist/engines/k6/runtime/shared/fixture-engine.js +2 -2
  148. package/dist/engines/k6/runtime/shared/http-parsing.js +3 -3
  149. package/dist/engines/managed-module-resolution-policy-input.d.ts +0 -1
  150. package/dist/engines/managed-module-resolution-policy-input.js +1 -1
  151. package/dist/engines/managed-module-resolution-policy.js +1 -1
  152. package/dist/engines/node-vitest/adapter/adapter.js +1 -1
  153. package/dist/engines/node-vitest/adapter/evidence.js +2 -2
  154. package/dist/engines/node-vitest/adapter/workspace.js +22 -21
  155. package/dist/engines/node-vitest/runtime/contract/index.js +1 -1
  156. package/dist/engines/node-vitest/runtime/data/http.js +1 -1
  157. package/dist/engines/node-vitest/runtime/data/index.d.ts +2 -2
  158. package/dist/engines/node-vitest/runtime/data/index.js +1 -1
  159. package/dist/engines/node-vitest/runtime/data/report.js +5 -5
  160. package/dist/engines/node-vitest/runtime/data/scenario.d.ts +0 -1
  161. package/dist/engines/node-vitest/runtime/data/scenario.js +2 -2
  162. package/dist/engines/node-vitest/runtime/data/types.d.ts +1 -29
  163. package/dist/engines/node-vitest/runtime/runtime-input.js +1 -1
  164. package/dist/engines/playwright/adapter/adapter.js +1 -1
  165. package/dist/engines/playwright/adapter/config.d.ts +1 -1
  166. package/dist/engines/playwright/adapter/config.js +4 -4
  167. package/dist/engines/playwright/adapter/evidence-validation.d.ts +0 -3
  168. package/dist/engines/playwright/adapter/evidence-validation.js +1 -1
  169. package/dist/engines/playwright/adapter/evidence.js +1 -1
  170. package/dist/engines/playwright/runtime/config-file.js +1 -1
  171. package/dist/engines/playwright/runtime/config.js +1 -1
  172. package/dist/engines/playwright/runtime/runtime-input.d.ts +2 -2
  173. package/dist/engines/playwright/runtime/runtime-input.js +16 -12
  174. package/dist/environment/lifecycle-artifacts.js +1 -1
  175. package/dist/environment/lifecycle-controller.d.ts +1 -11
  176. package/dist/environment/lifecycle-controller.js +1 -1
  177. package/dist/environment/lifecycle.d.ts +14 -11
  178. package/dist/environment/lifecycle.js +1 -1
  179. package/dist/environment/planning-types.d.ts +0 -2
  180. package/dist/environment/ports.d.ts +10 -0
  181. package/dist/environment/ports.js +1 -0
  182. package/dist/environment/ready-environment.d.ts +2 -5
  183. package/dist/environment/ready-environment.js +1 -1
  184. package/dist/environment/run-environment.d.ts +1 -0
  185. package/dist/environment/run-environment.js +1 -1
  186. package/dist/environment/runtime-contexts.d.ts +11 -7
  187. package/dist/environment/runtime-contexts.js +1 -1
  188. package/dist/environment/runtime-manager.d.ts +1 -9
  189. package/dist/environment/runtime-manager.js +1 -1
  190. package/dist/environment/runtime-planning.js +1 -1
  191. package/dist/environment/runtime-preparation.js +1 -1
  192. package/dist/environment/runtime-service.d.ts +0 -6
  193. package/dist/environment/services.d.ts +11 -3
  194. package/dist/environment/services.js +3 -1
  195. package/dist/environment/state.d.ts +1 -4
  196. package/dist/environment/state.js +1 -1
  197. package/dist/environment/template.d.ts +6 -22
  198. package/dist/environment/template.js +1 -1
  199. package/dist/evidence/artifact-config.js +1 -1
  200. package/dist/evidence/artifact-validation.d.ts +1 -2
  201. package/dist/evidence/artifact-validation.js +1 -1
  202. package/dist/evidence/artifacts.js +2 -2
  203. package/dist/evidence/bundles.js +1 -1
  204. package/dist/evidence/environment-record.d.ts +22 -0
  205. package/dist/evidence/environment-record.js +1 -0
  206. package/dist/evidence/index.d.ts +1 -1
  207. package/dist/evidence/logs.js +2 -2
  208. package/dist/evidence/operation-timings.js +1 -1
  209. package/dist/evidence/read-fields.d.ts +0 -1
  210. package/dist/evidence/read-fields.js +1 -1
  211. package/dist/evidence/read-run.js +1 -1
  212. package/dist/evidence/reading.js +1 -1
  213. package/dist/evidence/reporting-types.d.ts +19 -0
  214. package/dist/evidence/reporting.d.ts +2 -2
  215. package/dist/evidence/reporting.js +1 -1
  216. package/dist/evidence/storage.d.ts +0 -2
  217. package/dist/evidence/storage.js +2 -2
  218. package/dist/evidence/timing.js +1 -1
  219. package/dist/kernel/error-serialization.d.ts +2 -0
  220. package/dist/kernel/error-serialization.js +1 -1
  221. package/dist/kernel/unknown-values.d.ts +5 -0
  222. package/dist/kernel/unknown-values.js +1 -1
  223. package/dist/process/environment.js +1 -1
  224. package/dist/process/execa-bundle.json +3 -3
  225. package/dist/process/files/json-file.d.ts +0 -1
  226. package/dist/process/files/json-file.js +1 -1
  227. package/dist/process/files/paths.d.ts +2 -0
  228. package/dist/process/files/paths.js +1 -1
  229. package/dist/process/host/metadata.js +1 -1
  230. package/dist/process/host/platform.js +1 -1
  231. package/dist/process/host-process.js +1 -1
  232. package/dist/process/lifecycle-fence.d.ts +1 -2
  233. package/dist/process/lifecycle-fence.js +1 -1
  234. package/dist/process/locks.js +2 -2
  235. package/dist/process/process-group.js +1 -1
  236. package/dist/process/process-identity.js +1 -1
  237. package/dist/process/process.js +30 -30
  238. package/dist/process/readiness.d.ts +7 -1
  239. package/dist/process/readiness.js +1 -1
  240. package/dist/process/recovery.js +1 -1
  241. package/dist/process/runs/manifest.js +1 -1
  242. package/dist/process/runs/ownership.js +1 -1
  243. package/dist/process/runs/product-state.js +1 -1
  244. package/dist/process/runs/stop.js +1 -1
  245. package/dist/process/stoppable-settlement.d.ts +26 -0
  246. package/dist/process/stoppable-settlement.js +1 -0
  247. package/dist/process/url-redaction.d.ts +1 -0
  248. package/dist/process/url-redaction.js +1 -0
  249. package/dist/process/windows-native.d.ts +6 -0
  250. package/dist/process/windows-native.js +1 -1
  251. package/dist/repo/ci-environment.js +1 -1
  252. package/dist/repo/fingerprint-policy.d.ts +0 -2
  253. package/dist/repo/fingerprint-policy.js +1 -1
  254. package/dist/repo/physical-tree.js +1 -1
  255. package/dist/repo/publish-directory.d.ts +20 -0
  256. package/dist/repo/publish-directory.js +3 -0
  257. package/dist/repo/state.js +4 -4
  258. package/dist/steps/build-config.d.ts +0 -1
  259. package/dist/steps/build-config.js +1 -1
  260. package/dist/steps/configured-steps.js +1 -1
  261. package/dist/steps/environment.d.ts +0 -2
  262. package/dist/steps/environment.js +1 -1
  263. package/dist/steps/index.d.ts +1 -1
  264. package/dist/steps/run.js +2 -2
  265. package/dist/toolchains/archive.js +1 -1
  266. package/dist/toolchains/index.js +3 -3
  267. package/dist/toolchains/node-provisioning.js +2 -2
  268. package/dist/toolchains/vitest/index.js +1 -1
  269. package/docs/bug-records.md +54 -52
  270. package/docs/engine-architecture.md +39 -11
  271. package/package.json +1 -1
  272. package/testkit-config-schema.md +43 -5
  273. package/dist/app/run/playwright-configuration.d.ts +0 -2
  274. package/dist/app/run/playwright-configuration.js +0 -1
  275. package/dist/environment/readiness.d.ts +0 -12
  276. package/dist/environment/readiness.js +0 -1
@@ -17,8 +17,14 @@ export interface WaitForReadyOptions {
17
17
  }
18
18
  export declare const DEFAULT_READY_TIMEOUT_MS = 120000;
19
19
  export declare function waitForReady({ name, url, timeoutMs, process, signal, }: Readonly<WaitForReadyOptions>): Promise<void>;
20
+ export declare class ServiceReadinessTimeoutError extends Error {
21
+ constructor(message: string);
22
+ }
23
+ export declare class ServiceExitedBeforeReadyError extends Error {
24
+ readonly outcome: string;
25
+ constructor(message: string, outcome: string, options?: ErrorOptions);
26
+ }
20
27
  export declare function serviceCompletionError(name: string, result: Readonly<ReadyProcessCompletion>, phase?: "before becoming ready" | "unexpectedly"): Error;
21
- export declare function isPortInUse({ host, port }: Readonly<RuntimeSocket>): Promise<boolean>;
22
28
  export interface EndpointReadinessSuccessfulAttempt {
23
29
  durationMs: number;
24
30
  error?: undefined;
@@ -1 +1 @@
1
- import p from"net";import{setTimeout as y}from"node:timers/promises";export const DEFAULT_READY_TIMEOUT_MS=12e4;export async function waitForReady({name:e,url:t,timeoutMs:r,process:c,signal:o}){const s=Date.now(),a=Promise.resolve(c.completion).then(f=>({kind:"completion",result:f})),n=E(o);let i=10,d;try{for(;Date.now()-s<r;){const f=Math.max(1,r-(Date.now()-s));d?.abort(),d=new AbortController;const u=y(Math.min(i,f),void 0,{signal:d.signal}).then(()=>({kind:"interval"}));i=Math.min(i*2,1e3);const m=new AbortController,b=fetch(t,{signal:m.signal}).then(w=>({kind:"endpoint",ready:w.ok}),()=>({kind:"endpoint",ready:!1}));let l=await Promise.race([a,...n?[n.promise]:[],b,u]).finally(()=>{m.abort()});if(l.kind==="endpoint"){if(l.ready)return;l=await Promise.race([a,...n?[n.promise]:[],u])}if(l.kind==="completion")throw serviceCompletionError(e,l.result);if(l.kind==="abort")throw l.reason}}finally{d?.abort(),n?.dispose()}throw new Error(`Timed out waiting for "${e}" to become ready at ${t}`)}export function serviceCompletionError(e,t,r="before becoming ready"){const c=r==="unexpectedly"?"Local service":"Service",o=t.error||t.terminationError||null,s=t.exitCode!==null?`exit code ${t.exitCode.toString()}`:t.exitSignal?`signal ${t.exitSignal}`:"no exit status";return new Error(`${c} "${e}" exited ${r} with ${s}${o?`: ${o.message}`:""}`,o?{cause:o}:void 0)}function E(e){if(!e)return null;let t=()=>{};const r=new Promise(c=>{t=()=>{c({kind:"abort",reason:e.reason||new Error("Service startup aborted")})},e.aborted?t():e.addEventListener("abort",t,{once:!0})});return{dispose(){e.removeEventListener("abort",t)},promise:r}}export async function isPortInUse({host:e,port:t}){return new Promise((r,c)=>{const o=new p.Socket;let s=!1;const a=(n,i=null)=>{if(!s){if(s=!0,o.destroy(),i){c(i);return}r(n)}};o.setTimeout(1e3),o.once("connect",()=>{a(!0)}),o.once("timeout",()=>{a(!1)}),o.once("error",n=>{const i=h(n,"code");if(typeof i=="string"&&["ECONNREFUSED","EHOSTUNREACH","ENOTFOUND"].includes(i)){a(!1);return}a(!1,n)}),o.connect(t,e)})}function h(e,t){if(!((typeof e!="object"||e===null)&&typeof e!="function"))return Reflect.get(e,t)}export function connectEndpoint(e,t,r){return new Promise(c=>{const o=p.createConnection({host:e.host,port:e.port});let s=!1;const a=Date.now(),n=d=>{s||(s=!0,r?.removeEventListener("abort",i),o.destroy(),c({host:e.host,port:e.port,durationMs:Math.max(0,Date.now()-a),...d}))};function i(){n({ok:!1,error:"connection cancelled"})}o.setTimeout(t),o.once("connect",()=>{n({ok:!0})}),o.once("timeout",()=>{n({ok:!1,error:`connect timed out after ${t.toString()}ms`})}),o.once("error",d=>{n({ok:!1,error:d.message})}),r?.addEventListener("abort",i,{once:!0}),r?.aborted&&i()})}
1
+ import w from"net";import{setTimeout as x}from"node:timers/promises";import{redactUrlSecrets as E}from"./url-redaction.js";export const DEFAULT_READY_TIMEOUT_MS=12e4;export async function waitForReady({name:t,url:e,timeoutMs:o,process:i,signal:n}){const s=Date.now(),c=Promise.resolve(i.completion).then(l=>({kind:"completion",result:l})),r=h(n);let d=10,a;try{for(;Date.now()-s<o;){const l=Math.max(1,o-(Date.now()-s));a?.abort(),a=new AbortController;const f=x(Math.min(d,l),void 0,{signal:a.signal}).then(()=>({kind:"interval"}));d=Math.min(d*2,1e3);const u=new AbortController,p=fetch(e,{signal:u.signal}).then(b=>({kind:"endpoint",ready:b.ok}),()=>({kind:"endpoint",ready:!1}));let m=await Promise.race([c,...r?[r.promise]:[],p,f]).finally(()=>{u.abort()});if(m.kind==="endpoint"){if(m.ready)return;m=await Promise.race([c,...r?[r.promise]:[],f])}if(m.kind==="completion")throw serviceCompletionError(t,m.result);if(m.kind==="abort")throw m.reason}}finally{a?.abort(),r?.dispose()}throw new ServiceReadinessTimeoutError(`Timed out waiting for "${t}" to become ready at ${E(e)}`)}export class ServiceReadinessTimeoutError extends Error{constructor(e){super(e),this.name="ServiceReadinessTimeoutError"}}export class ServiceExitedBeforeReadyError extends Error{outcome;constructor(e,o,i){super(e,i),this.outcome=o,this.name="ServiceExitedBeforeReadyError"}}export function serviceCompletionError(t,e,o="before becoming ready"){const i=o==="unexpectedly"?"Local service":"Service",n=e.error||e.terminationError||null,s=`${e.exitCode!==null?`exit code ${e.exitCode.toString()}`:e.exitSignal?`signal ${e.exitSignal}`:"no exit status"}${n?`: ${n.message}`:""}`,c=`${i} "${t}" exited ${o} with ${s}`,r=n?{cause:n}:void 0;return o==="before becoming ready"?new ServiceExitedBeforeReadyError(c,s,r):new Error(c,r)}function h(t){if(!t)return null;let e=()=>{};const o=new Promise(i=>{e=()=>{i({kind:"abort",reason:t.reason||new Error("Service startup aborted")})},t.aborted?e():t.addEventListener("abort",e,{once:!0})});return{dispose(){t.removeEventListener("abort",e)},promise:o}}export function connectEndpoint(t,e,o){return new Promise(i=>{const n=w.createConnection({host:t.host,port:t.port});let s=!1;const c=Date.now(),r=a=>{s||(s=!0,o?.removeEventListener("abort",d),n.destroy(),i({host:t.host,port:t.port,durationMs:Math.max(0,Date.now()-c),...a}))};function d(){r({ok:!1,error:"connection cancelled"})}n.setTimeout(e),n.once("connect",()=>{r({ok:!0})}),n.once("timeout",()=>{r({ok:!1,error:`connect timed out after ${e.toString()}ms`})}),n.once("error",a=>{r({ok:!1,error:a.message})}),o?.addEventListener("abort",d,{once:!0}),o?.aborted&&d()})}
@@ -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";const u=d(c);export async function terminateAttributedProcessLineage(e){if(i(e.pid),t(e.displayLabel),s(e.pid,e.expectedCreationIdentity),process.platform==="win32"){await g(e.pid,e.displayLabel);return}w(e.pid,e.signal)}export async function terminateOwnedPosixProcessGroup(e){if(process.platform==="win32")throw new Error("POSIX process-group recovery is unavailable on Windows");if(i(e.leaderPid),i(e.processGroupId),t(e.displayLabel),e.leaderPid!==e.processGroupId)throw new Error("Owned POSIX process-group leader pid must equal its process-group id");if(!Number.isFinite(e.graceMs)||!Number.isInteger(e.graceMs)||e.graceMs<0)throw new Error("Owned POSIX process-group grace must be non-negative");if(!m(e.processGroupId))return;s(e.leaderPid,e.leaderCreationIdentity);const r=await f(e.processGroupId,{graceMs:e.graceMs});if(r!==null)throw new Error(`${e.displayLabel}: ${r.message}`,{cause:r})}function i(e){if(!Number.isSafeInteger(e)||e<=0)throw new Error("Process pid must be a positive safe integer")}function t(e){if(e.trim().length===0)throw new Error("Process display label must be non-empty")}function s(e,r){if(r!==void 0&&!l(e,r))throw new Error(`Process creation identity no longer matches pid ${e.toString()}; refusing unsafe cleanup`)}function w(e,r){try{process.kill(e,r)}catch(o){if(!a(o,"ESRCH"))throw o}}async function g(e,r){const o=p.join(process.env.SystemRoot||"C:\\Windows","System32","taskkill.exe");try{await u(o,["/pid",e.toString(),"/t","/f"],{timeout:1e4,windowsHide:!0})}catch(n){if(a(n,"ESRCH"))return;throw new Error(`Could not terminate ${r} process tree`,{cause:n})}}function a(e,r){return typeof e=="object"&&e!==null&&Reflect.get(e,"code")===r}
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 +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 O,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";export{g as RUN_MAINTENANCE_STOP_REQUEST_SCHEMA_VERSION,h as RUN_MANIFEST_SCHEMA_VERSION};const U=l.join(".testkit","_runs"),j=".maintenance-stop",T=128,C=/^[A-Za-z0-9][A-Za-z0-9._-]*$/u,k=/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u,L=/^(?:CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(?:\.|$)/iu;export class RunManifestCollisionError extends Error{code="TESTKIT_RUN_MANIFEST_COLLISION";manifestPath;constructor(n,e){super(`Testkit run manifest already exists at ${n}`,e),this.name="RunManifestCollisionError",this.manifestPath=n}}export class RunManifestInventoryError extends Error{code="TESTKIT_RUN_MANIFEST_INVENTORY_FAILED";manifestPath;constructor(n,e,r){super(`Cannot inventory Testkit run manifest ${n}: ${e}`,r),this.name="RunManifestInventoryError",this.manifestPath=n}}export function runManifestPath(t,n){const e=requireSafeRunId(n,"Testkit run id");return l.join(m(t),`${e}.json`)}export function writeRunManifest(t,n){const e=runManifestPath(t,n.runId);i.mkdirSync(m(t),{recursive:!0}),S(e,n)}export function prepareRunShutdownDeadlines(t,n){return{...t,services:t.services.map(e=>e.shutdownTimeoutMs===void 0?e:{...e,shutdownDeadlineAt:Math.min(e.shutdownDeadlineAt??Number.MAX_VALUE,Date.parse(n)+e.shutdownTimeoutMs)})}}export function persistRunShutdownDeadlines(t,n){S(runManifestPath(t,n.runId),n)}export function createRunManifest(t,n){const e=runManifestPath(t,n.runId),r=m(t);i.mkdirSync(r,{recursive:!0});const s=l.join(r,`.${l.basename(e)}.${process.pid.toString()}.${F()}.tmp`);i.writeFileSync(s,O(n,`run manifest ${n.runId}`),{flag:"wx"});try{process.platform==="win32"?D(s,e):i.linkSync(s,e)}catch(o){throw x(o,"EEXIST")?new RunManifestCollisionError(e,{cause:o}):o}finally{i.rmSync(s,{force:!0})}}export function writeRunMaintenanceStopRequest(t,n){const e=R(t,n.runId);i.mkdirSync(m(t),{recursive:!0}),S(e,n)}export function readRunMaintenanceStopRequest(t,n){const e=requireSafeRunId(n,"Testkit run id"),r=R(t,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(t,n){i.rmSync(R(t,n),{force:!0})}export function removeRunManifestFile(t,n){const e=runManifestPath(t,n),r=m(t);i.rmSync(e,{force:!0}),removeRunMaintenanceStopRequest(t,n),i.existsSync(r)&&i.readdirSync(r).length===0&&i.rmSync(r,{recursive:!0,force:!0})}export function loadRunManifest(t,n){const e=runManifestPath(t,n);I("runs.manifestLookups");let r;try{r=i.lstatSync(e)}catch(s){if(x(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 M(e)}export function listRunManifests(t){const n=m(t);if(!i.existsSync(n))return[];const e=i.readdirSync(n,{withFileTypes:!0});return I("runs.manifestDirectoriesRead"),I("runs.manifestEntriesVisited",e.length),e.filter(r=>r.name.endsWith(".json")).map(r=>{const s=l.join(n,r.name);if(!r.isFile())throw new RunManifestInventoryError(s,"manifest path is not a regular file");return M(s)}).sort((r,s)=>(r.startedAt||"").localeCompare(s.startedAt||""))}export function requireSafeRunId(t,n){const e=N(t);if(e!==null)return e;throw new Error(`${n} must be 1-${T.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(t){if(!p(t)||t.schemaVersion!==h)return null;const n=N(t.runId);if(n===null)return null;const e=t.controller,r=t.finishedAt,s=t.heartbeatAt,o=_(t.incarnationId),u=t.interruptReason,a=t.pid,d=t.processIdentity,c=t.session,f=t.startedAt,y=t.status,w=V(t.services),E=J(t.runtimeDirs);return typeof a!="number"||!Number.isSafeInteger(a)||a<0||o===null||w===null||E===null||a===0&&d!==null||a>0&&(typeof d!="string"||d.length===0)?null:{incarnationId:o,runId:n,runtimeDirs:E,schemaVersion:h,services:w,pid:a,processIdentity:typeof d=="string"?d:null,...G(e)?{controller:e}:{},...typeof r=="string"?{finishedAt:r}:{},...A(s)?{heartbeatAt:s}:{},...A(u)?{interruptReason:u}:{},...X(c)?{session:c}:{},...typeof f=="string"?{startedAt:f}:{},...typeof y=="string"?{status:y}:{}}}function R(t,n){const e=requireSafeRunId(n,"Testkit run id");return l.join(m(t),`${e}${j}`)}function z(t){if(!p(t)||t.schemaVersion!==g)return null;const n=N(t.runId),e=_(t.targetIncarnationId);return n===null||e===null||!P(t.coordinatorPid)||typeof t.coordinatorProcessIdentity!="string"||t.coordinatorProcessIdentity.length===0||typeof t.reason!="string"||t.reason.length===0||typeof t.requestedAt!="string"||!Number.isFinite(Date.parse(t.requestedAt))||!P(t.targetPid)||typeof t.targetProcessIdentity!="string"||t.targetProcessIdentity.length===0?null:{coordinatorPid:t.coordinatorPid,coordinatorProcessIdentity:t.coordinatorProcessIdentity,reason:t.reason,requestedAt:t.requestedAt,runId:n,schemaVersion:g,targetIncarnationId:e,targetPid:t.targetPid,targetProcessIdentity:t.targetProcessIdentity}}function M(t){let n;try{if($()){const u=i.readFileSync(t);I("runs.manifestFilesRead"),I("runs.manifestBytesRead",u.byteLength),n=u.toString("utf8")}else n=i.readFileSync(t,"utf8")}catch(u){throw new RunManifestInventoryError(t,"file could not be read",{cause:u})}let e;try{e=JSON.parse(n)}catch(u){throw new RunManifestInventoryError(t,"file is not valid JSON",{cause:u})}const r=normalizeRunManifest(e);if(r!==null){const u=l.basename(t,".json");if(r.runId!==u)throw new RunManifestInventoryError(t,"embedded runId does not match the manifest filename");return r}const s=p(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(t,o)}function N(t){return typeof t=="string"&&t.length<=T&&C.test(t)&&!L.test(t)?t:null}function _(t){return typeof t=="string"&&k.test(t)?t:null}function V(t){if(!Array.isArray(t))return null;const n=[];for(const e of t){const r=q(e);if(r===null)return null;n.push(r)}return n}function q(t){if(!p(t))return null;const n=t.command,e=t.cwd,r=t.pid,s=t.processIdentity,o=t.processGroupId,u=t.runtimeLabel,a=t.serviceName,d=t.startedAt,c=t.shutdownTimeoutMs,f=t.shutdownDeadlineAt;if(c!==void 0&&(typeof c!="number"||!Number.isFinite(c)||!Number.isInteger(c)||c<=0)||f!=null&&(c===void 0||typeof f!="number"||!Number.isFinite(f)||f<0))return null;const y=W(t.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},...f===void 0?{}:{shutdownDeadlineAt:f},ports:y,pid:r,processGroupId:o,processIdentity:s,...A(n)?{command:n}:{},...typeof e=="string"?{cwd:e}:{},...typeof u=="string"?{runtimeLabel:u}:{},...typeof a=="string"?{serviceName:a}:{},...typeof d=="string"?{startedAt:d}:{}}}function W(t){if(!Array.isArray(t))return null;const n=[];for(const e of t){if(!p(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;n.push({...e,host:r,port:s})}return n}function J(t){return!Array.isArray(t)||!t.every(n=>typeof n=="string")?null:t}function P(t){return typeof t=="number"&&Number.isSafeInteger(t)&&t>0}function m(t){return l.join(t,U)}function p(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}function X(t){return t===null||p(t)}function G(t){return t===null?!0:p(t)?Object.values(t).every(n=>n===null||typeof n=="string"):!1}function A(t){return t===null||typeof t=="string"}function x(t,n){return(typeof t=="object"||typeof t=="function")&&t!==null&&Reflect.get(t,"code")===n}const b=String;export function formatRunSummary(t){const n=normalizeRunManifest(t);if(!n)return b(t);const e=[...new Set(n.services.map(r=>r.runtimeLabel).filter(r=>!!r))];return`${b(n.runId)} pid=${b(n.pid)}${e.length>0?` runtimes=${e.join(",")}`:""}`}
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 +1 @@
1
- import c 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";export function hasActiveRunManifest(e,n=p){return hasActiveRunManifestWithTerminalAuthority(e,n)}export function hasActiveRunManifestWithTerminalAuthority(e,n,i){const u=l.join(e,".testkit","_runs"),a=I(u);let f=null;if(i!==void 0){const o=`${i.runId}.json`,r=a.filter(s=>s.fileName===o&&y(s.value,i));if(r.length!==1)return!0;f=r[0]}for(const o of a){if(o===f)continue;if(t(o.value,"schemaVersion")!==d)return!0;const r=t(o.value,"pid"),s=t(o.value,"processIdentity");if(!(r===0&&s===null)&&(typeof r!="number"||!Number.isSafeInteger(r)||r<=0||typeof s!="string"||s.length===0||n(r,s)!=="different"))return!0}return!1}function y(e,n){const i=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&&i===n.status&&m(i)&&Array.isArray(u)&&u.length===0}function I(e){return c.existsSync(e)?c.readdirSync(e,{withFileTypes:!0}).filter(n=>n.name.endsWith(".json")).map(n=>({fileName:n.name,value:n.isFile()?h(l.join(e,n.name)):null})):[]}function h(e){try{return JSON.parse(c.readFileSync(e,"utf8"))}catch{return null}}function t(e,n){return typeof e!="object"&&typeof e!="function"||e===null?void 0:Reflect.get(e,n)}
1
+ import a 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";export function hasActiveRunManifest(e,n=p){return hasActiveRunManifestWithTerminalAuthority(e,n)}export function hasActiveRunManifestWithTerminalAuthority(e,n,s){const u=l.join(e,".testkit","_runs"),c=h(u);let f=null;if(s!==void 0){const o=`${s.runId}.json`,r=c.filter(i=>i.fileName===o&&I(i.value,s));if(r.length!==1)return!0;f=r[0]}for(const o of c){if(o===f)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 I(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 h(e){return a.existsSync(e)?a.readdirSync(e,{withFileTypes:!0}).filter(n=>n.name.endsWith(".json")).map(n=>({fileName:n.name,value:n.isFile()?v(l.join(e,n.name)):null})):[]}function v(e){try{return JSON.parse(a.readFileSync(e,"utf8"))}catch{return null}}
@@ -1 +1 @@
1
- import l from"node:fs";import e from"node:path";export function resolveContainedTestkitStateDirectory(t,...r){for(const n of r)h(n);const o=l.realpathSync.native(e.resolve(t)),s=l.lstatSync(o);if(!s.isDirectory()||s.isSymbolicLink())throw new Error(`Testkit product path is not a directory: ${t}`);let i=o;for(const n of[".testkit",...r]){i=e.join(i,n);let a;try{a=l.lstatSync(i)}catch(c){if(d(c,"ENOENT"))break;throw c}if(a.isSymbolicLink())throw u(i,"symlink");if(!a.isDirectory())throw u(i,"non-directory node")}return e.join(o,".testkit",...r)}export function requireContainedTestkitStateDirectory(t,r,o="Testkit state directory"){const s=e.resolve(t),i=l.realpathSync.native(s),n=e.resolve(r),a=f(s,n)??f(i,n);if(a===null)throw new Error(`${o} must resolve within the Testkit product`);const c=a.split(e.sep);if(c.shift()!==".testkit")throw new Error(`${o} must resolve below the .testkit state root`);return resolveContainedTestkitStateDirectory(t,...c)}function f(t,r){const o=e.relative(t,r);return o===".."||o.startsWith(`..${e.sep}`)||e.isAbsolute(o)?null:o}function h(t){if(!t||t==="."||t===".."||e.basename(t)!==t||e.posix.basename(t)!==t||e.win32.basename(t)!==t)throw new Error(`Unsafe Testkit state path segment: ${t}`)}function u(t,r){return new Error(`Refusing unsafe Testkit state ${r} at ${t}`)}function d(t,r){return typeof t=="object"&&t!==null&&"code"in t&&Reflect.get(t,"code")===r}
1
+ import l from"node:fs";import r from"node:path";import{hasErrorCode as h}from"../../kernel/error-serialization.js";export function resolveContainedTestkitStateDirectory(t,...o){for(const n of o)k(n);const e=l.realpathSync.native(r.resolve(t)),s=l.lstatSync(e);if(!s.isDirectory()||s.isSymbolicLink())throw new Error(`Testkit product path is not a directory: ${t}`);let i=e;for(const n of[".testkit",...o]){i=r.join(i,n);let a;try{a=l.lstatSync(i)}catch(c){if(h(c,"ENOENT"))break;throw c}if(a.isSymbolicLink())throw u(i,"symlink");if(!a.isDirectory())throw u(i,"non-directory node")}return r.join(e,".testkit",...o)}export function requireContainedTestkitStateDirectory(t,o,e="Testkit state directory"){const s=r.resolve(t),i=l.realpathSync.native(s),n=r.resolve(o),a=f(s,n)??f(i,n);if(a===null)throw new Error(`${e} must resolve within the Testkit product`);const c=a.split(r.sep);if(c.shift()!==".testkit")throw new Error(`${e} must resolve below the .testkit state root`);return resolveContainedTestkitStateDirectory(t,...c)}function f(t,o){const e=r.relative(t,o);return e===".."||e.startsWith(`..${r.sep}`)||r.isAbsolute(e)?null:e}function k(t){if(!t||t==="."||t===".."||r.basename(t)!==t||r.posix.basename(t)!==t||r.win32.basename(t)!==t)throw new Error(`Unsafe Testkit state path segment: ${t}`)}function u(t,o){return new Error(`Refusing unsafe Testkit state ${o} at ${t}`)}
@@ -1 +1 @@
1
- import{isTerminalRunStatus as h}from"./schema.js";import{observeProcessCreationIdentity as c}from"../index.js";import{withProductLifecycleFence as a}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 S}from"../shutdown.js";const l=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 M(n,"EPERM")}}export async function waitForPreparedRunStop(t){if(t.controllerActive&&t.targetProcessIdentity!==null){const i=await D(t,t.targetProcessIdentity);let r=i,s=i;if(!r&&!s&&(await f(t.pid,"SIGKILL",t.targetProcessIdentity),r=await p(t.pid,l,t.targetProcessIdentity),s=!r&&await b(t)),!r&&!s)throw new Error(`Timed out stopping Testkit run process ${t.pid.toString()}`)}const n=new Set,e=await a(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 r=runManifestServiceKey(i),s=o.get(r);o.set(r,s?.shutdownDeadlineAt==null?i:{...i,shutdownTimeoutMs:s.shutdownTimeoutMs,shutdownDeadlineAt:Math.min(s.shutdownDeadlineAt,i.shutdownDeadlineAt??Number.MAX_VALUE)})}return await drainRunServices([...o.values()],n),n}async function D(t,n){const e=new Map,o=s=>{for(const u of s){const w=runManifestServiceKey(u),m=u.shutdownTimeoutMs??t.graceMs,g=e.get(w)??I(m+S);g.begin((u.shutdownDeadlineAt??Date.parse(t.requestedAt)+m)+S),e.set(w,g)}return Math.max(0,...s.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 r=null;for(;;){if(c(t.pid,n)==="different")return!0;const s=await a(t.productDir,()=>d(t.productDir).find(u=>u.runId===t.runId));if(!s||!sameRunManifestIncarnation(s,t.manifest)||h(s.status))return!0;if(Math.max(i.remainingMs(),o(s.services))<=0){if(r===null){try{await f(t.pid,"SIGTERM",n)}catch{}r=performance.now()}if(performance.now()-r>=E)return!1}await y(100)}}async function b(t){return a(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?l: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=c(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 p(n,l,e))throw new Error(`Timed out stopping application service process ${n.toString()}`)}export function requestProcessStop(t,n,e){const o=c(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(!M(i,"ESRCH"))throw i}}async function p(t,n,e){const o=()=>e===void 0?isPidRunning(t):c(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}function y(t){return new Promise(n=>setTimeout(n,t))}function M(t,n){return(typeof t=="object"||typeof t=="function")&&t!==null&&Reflect.get(t,"code")===n}
1
+ import{isTerminalRunStatus as h}from"./schema.js";import{observeProcessCreationIdentity as c}from"../index.js";import{withProductLifecycleFence as a}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";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 M(t.pid,w,t.targetProcessIdentity),r=!s&&await P(t)),!s&&!r)throw new Error(`Timed out stopping Testkit run process ${t.pid.toString()}`)}const n=new Set,e=await a(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,g=e.get(m)??I(l+p);g.begin((u.shutdownDeadlineAt??Date.parse(t.requestedAt)+l)+p),e.set(m,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(c(t.pid,n)==="different")return!0;const r=await a(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 y(100)}}async function P(t){return a(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 b(e),n.add(runManifestServiceKey(e))}async function b(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=c(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 M(n,w,e))throw new Error(`Timed out stopping application service process ${n.toString()}`)}export function requestProcessStop(t,n,e){const o=c(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 M(t,n,e){const o=()=>e===void 0?isPidRunning(t):c(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}function y(t){return new Promise(n=>setTimeout(n,t))}
@@ -0,0 +1,26 @@
1
+ export type CommandStopReason = "cancelled" | "timedOut";
2
+ export interface CommandStop {
3
+ readonly reason: CommandStopReason;
4
+ /** Resolves with the termination failure, or null once the process tree has exited. */
5
+ readonly termination: Promise<Error | null>;
6
+ }
7
+ export type StoppableSettlement<T> = {
8
+ readonly abandoned: false;
9
+ readonly result: T;
10
+ readonly stop: CommandStop | null;
11
+ readonly terminationError: Error | null;
12
+ } | {
13
+ readonly abandoned: true;
14
+ readonly stop: CommandStop;
15
+ readonly terminationError: Error;
16
+ };
17
+ /**
18
+ * After a stop, a command's settlement is awaited only until its termination
19
+ * fails, or for `drainMs` after its tree has exited. A process outside the tree
20
+ * can hold the command's output open indefinitely.
21
+ */
22
+ export declare function settleStoppable<T>(settlement: PromiseLike<T>, drainMs: number): {
23
+ completion: Promise<StoppableSettlement<T>>;
24
+ readonly stop: CommandStop | null;
25
+ stopWith(next: CommandStop): void;
26
+ };
@@ -0,0 +1 @@
1
+ export function settleStoppable(s,o){let n=null,i=!1,a,r=()=>{};const l=new Promise(e=>{r=e}),u=Promise.resolve(s).then(async e=>{i=!0,clearTimeout(a);const t=n;return{abandoned:!1,result:e,stop:t,terminationError:t===null?null:await t.termination}});return{completion:Promise.race([u,l]),get stop(){return n},stopWith(e){n===null&&(n=e,e.termination.then(t=>{if(!i){if(t!==null){r({abandoned:!0,stop:e,terminationError:t});return}a=setTimeout(()=>{r({abandoned:!0,stop:e,terminationError:new Error(`The command's output stayed open ${o.toString()} ms after its process tree exited`)})},o)}}))}}}
@@ -0,0 +1 @@
1
+ export declare function redactUrlSecrets(rawUrl: string): string;
@@ -0,0 +1 @@
1
+ export function redactUrlSecrets(r){let e;try{e=new URL(r)}catch{return"<redacted>"}if(!e.username&&!e.password&&!e.search&&!e.hash)return r;const a=e.username||e.password?"<redacted>@":"",t=[...new Set(e.searchParams.keys())].map(n=>`${encodeURIComponent(n)}=<redacted>`).join("&");return`${e.protocol}//${a}${e.host}${e.pathname}${t?`?${t}`:""}${e.hash?"#<redacted>":""}`}
@@ -2,5 +2,11 @@ import fs from "node:fs";
2
2
  export declare function windowsOwnerExecutable(): string;
3
3
  /** Atomically publish a complete file without replacing an existing name. */
4
4
  export declare function publishWindowsFile(source: string, destination: string): void;
5
+ /**
6
+ * Terminate a process created no later than `createdNotAfterMs` and every
7
+ * descendant it started, then wait until each one has exited. Resolves
8
+ * `"absent"` without terminating anything when no such process exists.
9
+ */
10
+ export declare function terminateWindowsProcessTree(pid: number, createdNotAfterMs: number, timeoutMs: number): Promise<"absent" | "terminated">;
5
11
  /** A rename can change ctime; require stable file identity, size, and mtime. */
6
12
  export declare function sameFileAfterRename(before: Readonly<Pick<fs.BigIntStats, "dev" | "ino" | "size" | "mtimeNs">>, after: Readonly<Pick<fs.BigIntStats, "dev" | "ino" | "size" | "mtimeNs">>): boolean;
@@ -1 +1 @@
1
- import{execFileSync as l}from"node:child_process";import o 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(e,t){const s=o.lstatSync(e,{bigint:!0});try{l(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 n={bigint:!0,throwIfNoEntry:!1},a=o.lstatSync(e,n),c=o.lstatSync(t,n);if(!a&&s.isFile()&&c?.isFile()&&sameFileAfterRename(s,c))return}catch{}throw r}}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 u,execFileSync as d}from"node:child_process";import a from"node:fs";import o 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 o.resolve(o.dirname(p(import.meta.url)),"../../bin",`testkit-windows-owner-${process.arch}.exe`)}export function publishWindowsFile(e,t){const r=a.lstatSync(e,{bigint:!0});try{d(windowsOwnerExecutable(),["publish-file",o.toNamespacedPath(o.resolve(e)),o.toNamespacedPath(o.resolve(t))],{encoding:"utf8",maxBuffer:16*1024,stdio:["ignore","pipe","pipe"],timeout:5e3,windowsHide:!0})}catch(i){if(typeof i=="object"&&i!==null&&Reflect.get(i,"status")===17)throw Object.assign(new Error("File already exists",{cause:i}),{code:"EEXIST"});try{const s={bigint:!0,throwIfNoEntry:!1},c=a.lstatSync(e,s),n=a.lstatSync(t,s);if(!c&&r.isFile()&&n?.isFile()&&sameFileAfterRename(r,n))return}catch{}throw i}}const f=11644473600000n;export function terminateWindowsProcessTree(e,t,r){const i=(BigInt(Math.floor(t))+f)*10000n;return new Promise((s,c)=>{u(windowsOwnerExecutable(),["terminate-tree",e.toString(),i.toString(),Math.ceil(r).toString()],{encoding:"utf8",maxBuffer:16*1024,timeout:r+5e3,windowsHide:!0},(n,w,l)=>{if(n===null)s("terminated");else if(n.code===3)s("absent");else{const m=l.trim()||`Could not terminate Windows process tree ${e.toString()}`;c(new Error(m,{cause:n}))}})})}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 +1 @@
1
- import f from"fs";export function readCiPullRequestHeadSha(n){if(o(u(n,"GITHUB_REF"))===null)return null;const e=u(n,"GITHUB_EVENT_PATH");if(e===null)return null;try{const t=JSON.parse(f.readFileSync(e,"utf8"));if(!l(t))return null;const r=t.pull_request;if(!l(r))return null;const i=r.head;return l(i)?normalizePullRequestHeadSha(i.sha):null}catch{return null}}export function normalizePullRequestHeadSha(n){return typeof n!="string"||!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/iu.test(n)?null:n.toLowerCase()}export function resolveCiGitReference(n){const e=u(n,"GITHUB_REF"),t=c(n,["GITHUB_HEAD_REF","GITHUB_REF_NAME","GITHUB_REF","CI_COMMIT_REF_NAME"]);return{branch:s(t),pullRequest:o(e)}}function s(n){if(n===null)return null;for(const e of["refs/heads/","refs/tags/"])if(n.startsWith(e))return n.slice(e.length)||null;return n}function o(n){const e=n?.match(/^refs\/pull\/(\d+)(?:\/|$)/u);if(e==null)return null;const t=Number(e[1]);return Number.isSafeInteger(t)&&t>0?t:null}function c(n,e){for(const t of e){const r=u(n,t);if(r!==null)return r}return null}function u(n,e){const t=n[e]?.trim();return t||null}function l(n){return typeof n=="object"&&n!==null&&!Array.isArray(n)}
1
+ import f from"fs";import{isUnknownRecord as l}from"../kernel/unknown-values.js";export function readCiPullRequestHeadSha(n){if(i(u(n,"GITHUB_REF"))===null)return null;const e=u(n,"GITHUB_EVENT_PATH");if(e===null)return null;try{const t=JSON.parse(f.readFileSync(e,"utf8"));if(!l(t))return null;const r=t.pull_request;if(!l(r))return null;const o=r.head;return l(o)?normalizePullRequestHeadSha(o.sha):null}catch{return null}}export function normalizePullRequestHeadSha(n){return typeof n!="string"||!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/iu.test(n)?null:n.toLowerCase()}export function resolveCiGitReference(n){const e=u(n,"GITHUB_REF"),t=c(n,["GITHUB_HEAD_REF","GITHUB_REF_NAME","GITHUB_REF","CI_COMMIT_REF_NAME"]);return{branch:s(t),pullRequest:i(e)}}function s(n){if(n===null)return null;for(const e of["refs/heads/","refs/tags/"])if(n.startsWith(e))return n.slice(e.length)||null;return n}function i(n){const e=n?.match(/^refs\/pull\/(\d+)(?:\/|$)/u);if(e==null)return null;const t=Number(e[1]);return Number.isSafeInteger(t)&&t>0?t:null}function c(n,e){for(const t of e){const r=u(n,t);if(r!==null)return r}return null}function u(n,e){const t=n[e]?.trim();return t||null}
@@ -4,12 +4,10 @@ export interface FingerprintPolicy {
4
4
  readonly include: readonly string[];
5
5
  }
6
6
  export declare function normalizeFingerprintPolicy(value?: unknown): FingerprintPolicy;
7
- export declare function shouldIgnoreFingerprintPath(productDir: string, absOrRelPath: string, policy?: FingerprintPolicy): boolean;
8
7
  export declare function shouldIgnoreCanonicalFingerprintPath(relativePath: string, policy?: FingerprintPolicy): boolean;
9
8
  export declare function shouldIgnoreCanonicalFingerprintPathAtPrefix(relativePath: string, addressPrefix: string, policy?: FingerprintPolicy): boolean;
10
9
  export declare function hasIncludedFingerprintDescendantAtPrefix(relativePath: string, addressPrefix: string, policy: FingerprintPolicy): boolean;
11
10
  export declare function appendFingerprintPathToHash(hash: Hash, productDir: string, absPath: string, policy?: FingerprintPolicy): void;
12
11
  export declare function normalizeRelativePath(productDir: string, absOrRelPath: string): string;
13
12
  export declare function normalizePath(value: string): string;
14
- export declare function appendInputToHash(hash: Hash, productDir: string, input: string, fingerprints?: unknown): void;
15
13
  export declare function appendFileToHash(hash: Hash, productDir: string, absPath: string, fingerprints?: unknown): void;
@@ -1 +1 @@
1
- import y from"fs";import f from"path";import{physicalNodeMode as d,readPhysicalDirectoryEntries as b,readPhysicalNode as $}from"./physical-tree.js";const w=[".testkit",".next-testkit"],l={exclude:[],include:[]};export function normalizeFingerprintPolicy(t={}){if(t==null)return{exclude:[],include:[]};if(!S(t))throw new Error("testkit.config.ts fingerprints must be an object");return{exclude:h(t.exclude,"fingerprints.exclude"),include:h(t.include,"fingerprints.include")}}export function shouldIgnoreFingerprintPath(t,n,e=l){const r=normalizeRelativePath(t,n);return shouldIgnoreCanonicalFingerprintPath(r,e)}export function shouldIgnoreCanonicalFingerprintPath(t,n=l){return shouldIgnoreCanonicalFingerprintPathAtPrefix(t,"",n)}export function shouldIgnoreCanonicalFingerprintPathAtPrefix(t,n,e=l){const r=c(t),i=m(n,r);return!i||p(i,e.include)?!1:E(r)?!0:p(i,e.exclude)}export function hasIncludedFingerprintDescendantAtPrefix(t,n,e){const r=m(n,t);if(!r)return e.include.length>0;const i=`${r}/`;return e.include.some(s=>{const o=c(s);if(o.includes("*")){const u=A(o);return u.length===0||r.startsWith(u)||u.startsWith(i)}return P(o).startsWith(i)})}export function appendFingerprintPathToHash(t,n,e,r=l){const i=normalizeRelativePath(n,e),s=y.realpathSync.native(f.resolve(n));x(t,s,i,r)}export function normalizeRelativePath(t,n){const e=f.isAbsolute(n)?f.relative(t,n):n;return f.isAbsolute(n)?c(e.split(f.sep).join("/")):normalizePath(e)}export function normalizePath(t){return t.split(f.sep).join("/").replace(/\\/g,"/").replace(/^\.\/+/,"").replace(/\/+$/,"")}function h(t,n){if(t==null)return[];if(!F(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=c(e.trim());if(!i)throw new Error(`testkit.config.ts ${n}[${String(r)}] must be a non-empty path`);if(T(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 p(t,n){return n.some(e=>z(t,e))}function E(t){return p(t,w)}function z(t,n){const e=c(t),r=c(n);if(!r)return!1;const i=P(r);if(e===i||e.startsWith(`${i}/`))return!0;if(!r.includes("*"))return!1;const s=L(r);let o=e;for(;o;){if(s.test(o))return!0;const u=o.lastIndexOf("/");if(u<0)break;o=o.slice(0,u)}return!1}function c(t){return t.replace(/^\.\/+/,"").replace(/\/+$/,"")}function T(t){for(let n=0;n<t.length;n+=1)if(t[n]==="\\"){if(t[n+1]!=="\\")return!0;n+=1}return!1}function P(t){return t.replace(/\\\\/g,"\\")}function A(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 x(t,n,e,r){const i=$(n,e),s=shouldIgnoreCanonicalFingerprintPath(e,r);if(!(s&&(i.kind!=="directory"||!hasIncludedFingerprintDescendantAtPrefix(e,"",r)))&&(s||I(t,e,i),i.kind==="directory"))for(const o of b(n,e))x(t,n,m(e,o),r)}function I(t,n,e){switch(t.update("node\0"),a(t,"path",n),a(t,"kind",e.kind),e.kind){case"file":a(t,"mode",d(e.mode)),g(t,"contents",e.contents);break;case"directory":a(t,"mode",d(e.mode));break;case"symlink":case"ancestor-symlink":"obstaclePath"in e&&a(t,"obstacle",e.obstaclePath),g(t,"target",e.target);break;case"ancestor-missing":a(t,"obstacle",e.obstaclePath);break;case"ancestor-non-directory":a(t,"obstacle",e.obstaclePath),a(t,"mode",d(e.mode));break;case"missing":break}}function a(t,n,e){g(t,n,Buffer.from(e))}function g(t,n,e){t.update(`${n}\0${e.byteLength.toString()}\0`),t.update(e),t.update("\0")}function m(t,n){const e=c(t),r=c(n);return e?r?`${e}/${r}`:e:r}function L(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,"\\$&")}function S(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}function F(t){return Array.isArray(t)}export function appendInputToHash(t,n,e,r={}){const i=f.resolve(n,e||".");appendFingerprintPathToHash(t,n,i,normalizeFingerprintPolicy(r))}export function appendFileToHash(t,n,e,r={}){appendFingerprintPathToHash(t,n,e,normalizeFingerprintPolicy(r))}
1
+ import y from"fs";import u from"path";import{physicalNodeMode as l,readPhysicalDirectoryEntries as b,readPhysicalNode as $}from"./physical-tree.js";import{isUnknownArray as w,isUnknownRecord as E}from"../kernel/unknown-values.js";const z=[".testkit",".next-testkit"],d={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{exclude:h(t.exclude,"fingerprints.exclude"),include:h(t.include,"fingerprints.include")}}export function shouldIgnoreCanonicalFingerprintPath(t,n=d){return shouldIgnoreCanonicalFingerprintPathAtPrefix(t,"",n)}export function shouldIgnoreCanonicalFingerprintPathAtPrefix(t,n,e=d){const r=s(t),i=m(n,r);return!i||p(i,e.include)?!1:T(r)?!0:p(i,e.exclude)}export function hasIncludedFingerprintDescendantAtPrefix(t,n,e){const r=m(n,t);if(!r)return e.include.length>0;const i=`${r}/`;return e.include.some(c=>{const o=s(c);if(o.includes("*")){const f=S(o);return f.length===0||r.startsWith(f)||f.startsWith(i)}return P(o).startsWith(i)})}export function appendFingerprintPathToHash(t,n,e,r=d){const i=normalizeRelativePath(n,e),c=y.realpathSync.native(u.resolve(n));k(t,c,i,r)}export function normalizeRelativePath(t,n){const e=u.isAbsolute(n)?u.relative(t,n):n;return u.isAbsolute(n)?s(e.split(u.sep).join("/")):normalizePath(e)}export function normalizePath(t){return t.split(u.sep).join("/").replace(/\\/g,"/").replace(/^\.\/+/,"").replace(/\/+$/,"")}function h(t,n){if(t==null)return[];if(!w(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=s(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 p(t,n){return n.some(e=>I(t,e))}function T(t){return p(t,z)}function I(t,n){const e=s(t),r=s(n);if(!r)return!1;const i=P(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 f=o.lastIndexOf("/");if(f<0)break;o=o.slice(0,f)}return!1}function s(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 P(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 k(t,n,e,r){const i=$(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 b(n,e))k(t,n,m(e,o),r)}function R(t,n,e){switch(t.update("node\0"),a(t,"path",n),a(t,"kind",e.kind),e.kind){case"file":a(t,"mode",l(e.mode)),g(t,"contents",e.contents);break;case"directory":a(t,"mode",l(e.mode));break;case"symlink":case"ancestor-symlink":"obstaclePath"in e&&a(t,"obstacle",e.obstaclePath),g(t,"target",e.target);break;case"ancestor-missing":a(t,"obstacle",e.obstaclePath);break;case"ancestor-non-directory":a(t,"obstacle",e.obstaclePath),a(t,"mode",l(e.mode));break;case"missing":break}}function a(t,n,e){g(t,n,Buffer.from(e))}function g(t,n,e){t.update(`${n}\0${e.byteLength.toString()}\0`),t.update(e),t.update("\0")}function m(t,n){const e=s(t),r=s(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+=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 +1 @@
1
- import i from"node:fs";import h from"node:path";export function physicalPathAndHandleIdentitiesEqual(t,n,e){return(t.dev===n.dev||e==="win32"&&t.dev===0n&&n.dev!==0n)&&t.ino===n.ino&&t.type===n.type}const w=61440n;export function readPhysicalNode(t,n){const e=D(t,n);try{if(e.obstacle)return e.obstacle;const r=e.finalStat;if(!r)return{kind:"missing"};if(r.isSymbolicLink())return{kind:"symlink",target:$(e,r)};if(r.isFile())return R(e,r);if(r.isDirectory())return u(e,r),{identity:a(r),kind:"directory",mode:r.mode};throw u(e,r),E(n,r.mode)}finally{g(e.ancestors)}}export function readPhysicalDirectoryEntries(t,n){const e=D(t,n);try{if(e.obstacle||!e.finalStat?.isDirectory())throw new Error(`Fingerprint directory is not a stable physical directory: ${n||"."}`);u(e,e.finalStat);const r=i.readdirSync(e.finalPath,{encoding:"buffer"});return u(e,e.finalStat),r.map(o=>decodePhysicalPathSegment(o,n)).sort(T)}finally{g(e.ancestors)}}export function physicalNodeMode(t){return(t&4095n).toString(8).padStart(4,"0")}export function physicalNodeType(t){switch(t&w){case 4096n:return"fifo";case 8192n:return"character-device";case 16384n:return"directory";case 24576n:return"block-device";case 32768n:return"file";case 40960n:return"symlink";case 49152n:return"socket";default:return"unknown"}}export function decodePhysicalPathSegment(t,n=""){const e=Buffer.from(t),r=e.toString("utf8");if(!Buffer.from(r,"utf8").equals(e))throw new Error(`Fingerprint paths must be valid UTF-8: ${n||"."} contains filename bytes 0x${e.toString("hex")}`);return r}function D(t,n){const e=i.realpathSync.native(h.resolve(t)),r=_(n),o=[];let c=e;try{const l=i.lstatSync(e,{bigint:!0});if(!l.isDirectory()||l.isSymbolicLink())throw new Error(`Fingerprint root is not a physical directory: ${t}`);o.push(F(e,l));const f=r.length===0?[]:r.slice(0,-1);for(const[v,N]of f.entries()){c=h.join(c,N);const p=r.slice(0,v+1).join("/"),s=k(c);if(!s)return{ancestors:o,finalPath:c,finalStat:null,obstacle:{kind:"ancestor-missing",obstaclePath:p}};if(s.isSymbolicLink())return{ancestors:o,finalPath:c,finalStat:s,obstacle:{kind:"ancestor-symlink",obstaclePath:p,target:C(c,s,o)}};if(!s.isDirectory()){if(!s.isFile())throw E(p,s.mode);return{ancestors:o,finalPath:c,finalStat:s,obstacle:{kind:"ancestor-non-directory",mode:s.mode,obstaclePath:p}}}const L=F(c,s);o.push(L),y(o)}const b=r.length?h.join(e,...r):e,O=k(b);return y(o),{ancestors:o,finalPath:b,finalStat:O,obstacle:null}}catch(l){throw g(o),l}}function F(t,n){let e=null;if(process.platform!=="win32"){const r=Reflect.get(i.constants,"O_DIRECTORY"),o=Reflect.get(i.constants,"O_NOFOLLOW"),c=typeof r=="number"?r:0,l=typeof o=="number"?o:0;e=i.openSync(t,i.constants.O_RDONLY|c|l);const f=i.fstatSync(e,{bigint:!0});if(!f.isDirectory()||!m(f,n))throw i.closeSync(e),d(t)}return{fileDescriptor:e,identity:a(n),path:t}}function R(t,n){const e=Reflect.get(i.constants,"O_NOFOLLOW"),r=typeof e=="number"?e:0,o=i.openSync(t.finalPath,i.constants.O_RDONLY|r);try{const c=i.fstatSync(o,{bigint:!0});if(!c.isFile()||!physicalPathAndHandleIdentitiesEqual(a(n),a(c),process.platform))throw d(t.finalPath);u(t,c,"handle");const l=i.readFileSync(o),f=i.fstatSync(o,{bigint:!0});if(!x(c,f))throw d(t.finalPath);return u(t,f,"handle"),{contents:l,kind:"file",mode:f.mode}}finally{i.closeSync(o)}}function $(t,n){u(t,n);const e=i.readlinkSync(t.finalPath,{encoding:"buffer"});return u(t,n),e}function C(t,n,e){y(e);const r=i.readlinkSync(t,{encoding:"buffer"}),o=i.lstatSync(t,{bigint:!0});if(!o.isSymbolicLink()||!m(o,n))throw d(t);return y(e),r}function u(t,n,e="path"){y(t.ancestors);const r=i.lstatSync(t.finalPath,{bigint:!0});if(!(e==="handle"?physicalPathAndHandleIdentitiesEqual(a(r),a(n),process.platform):m(r,n)))throw d(t.finalPath)}function y(t){for(const n of t){const e=i.lstatSync(n.path,{bigint:!0});if(!e.isDirectory()||!S(a(e),n.identity))throw d(n.path);if(n.fileDescriptor!==null){const r=i.fstatSync(n.fileDescriptor,{bigint:!0});if(!r.isDirectory()||!S(a(r),n.identity))throw d(n.path)}}}function k(t){try{return i.lstatSync(t,{bigint:!0})}catch(n){if(P(n,"ENOENT")||P(n,"ENOTDIR"))return null;throw n}}function _(t){const n=t.replace(/^\.\/+/,"").replace(/\/+$/,"");if(!n)return[];const e=n.split("/");if(e.some(r=>r.length===0||r==="."||r===".."))throw new Error(`Invalid canonical fingerprint path: ${t}`);return e}function a(t){return{dev:t.dev,ino:t.ino,type:t.mode&w}}function m(t,n){return S(a(t),a(n))}function S(t,n){return t.dev===n.dev&&t.ino===n.ino&&t.type===n.type}function x(t,n){return m(t,n)&&t.mode===n.mode&&t.size===n.size&&t.mtimeNs===n.mtimeNs&&t.ctimeNs===n.ctimeNs}function g(t){for(const n of[...t].reverse())n.fileDescriptor!==null&&i.closeSync(n.fileDescriptor)}function d(t){return new Error(`Fingerprint path changed while reading: ${t}`)}function E(t,n){return new Error(`Fingerprinting does not support special filesystem nodes: ${t||"."} (${physicalNodeType(n)})`)}function P(t,n){return t!==null&&typeof t=="object"&&"code"in t&&Reflect.get(t,"code")===n}function T(t,n){return t<n?-1:t>n?1:0}
1
+ import i from"node:fs";import h from"node:path";import{hasErrorCode as w}from"../kernel/error-serialization.js";export function physicalPathAndHandleIdentitiesEqual(t,n,e){return(t.dev===n.dev||e==="win32"&&t.dev===0n&&n.dev!==0n)&&t.ino===n.ino&&t.type===n.type}const D=61440n;export function readPhysicalNode(t,n){const e=F(t,n);try{if(e.obstacle)return e.obstacle;const r=e.finalStat;if(!r)return{kind:"missing"};if(r.isSymbolicLink())return{kind:"symlink",target:R(e,r)};if(r.isFile())return $(e,r);if(r.isDirectory())return u(e,r),{identity:a(r),kind:"directory",mode:r.mode};throw u(e,r),P(n,r.mode)}finally{g(e.ancestors)}}export function readPhysicalDirectoryEntries(t,n){const e=F(t,n);try{if(e.obstacle||!e.finalStat?.isDirectory())throw new Error(`Fingerprint directory is not a stable physical directory: ${n||"."}`);u(e,e.finalStat);const r=i.readdirSync(e.finalPath,{encoding:"buffer"});return u(e,e.finalStat),r.map(o=>decodePhysicalPathSegment(o,n)).sort(T)}finally{g(e.ancestors)}}export function physicalNodeMode(t){return(t&4095n).toString(8).padStart(4,"0")}export function physicalNodeType(t){switch(t&D){case 4096n:return"fifo";case 8192n:return"character-device";case 16384n:return"directory";case 24576n:return"block-device";case 32768n:return"file";case 40960n:return"symlink";case 49152n:return"socket";default:return"unknown"}}export function decodePhysicalPathSegment(t,n=""){const e=Buffer.from(t),r=e.toString("utf8");if(!Buffer.from(r,"utf8").equals(e))throw new Error(`Fingerprint paths must be valid UTF-8: ${n||"."} contains filename bytes 0x${e.toString("hex")}`);return r}function F(t,n){const e=i.realpathSync.native(h.resolve(t)),r=_(n),o=[];let c=e;try{const l=i.lstatSync(e,{bigint:!0});if(!l.isDirectory()||l.isSymbolicLink())throw new Error(`Fingerprint root is not a physical directory: ${t}`);o.push(k(e,l));const f=r.length===0?[]:r.slice(0,-1);for(const[v,N]of f.entries()){c=h.join(c,N);const p=r.slice(0,v+1).join("/"),s=E(c);if(!s)return{ancestors:o,finalPath:c,finalStat:null,obstacle:{kind:"ancestor-missing",obstaclePath:p}};if(s.isSymbolicLink())return{ancestors:o,finalPath:c,finalStat:s,obstacle:{kind:"ancestor-symlink",obstaclePath:p,target:C(c,s,o)}};if(!s.isDirectory()){if(!s.isFile())throw P(p,s.mode);return{ancestors:o,finalPath:c,finalStat:s,obstacle:{kind:"ancestor-non-directory",mode:s.mode,obstaclePath:p}}}const L=k(c,s);o.push(L),y(o)}const b=r.length?h.join(e,...r):e,O=E(b);return y(o),{ancestors:o,finalPath:b,finalStat:O,obstacle:null}}catch(l){throw g(o),l}}function k(t,n){let e=null;if(process.platform!=="win32"){const r=Reflect.get(i.constants,"O_DIRECTORY"),o=Reflect.get(i.constants,"O_NOFOLLOW"),c=typeof r=="number"?r:0,l=typeof o=="number"?o:0;e=i.openSync(t,i.constants.O_RDONLY|c|l);const f=i.fstatSync(e,{bigint:!0});if(!f.isDirectory()||!m(f,n))throw i.closeSync(e),d(t)}return{fileDescriptor:e,identity:a(n),path:t}}function $(t,n){const e=Reflect.get(i.constants,"O_NOFOLLOW"),r=typeof e=="number"?e:0,o=i.openSync(t.finalPath,i.constants.O_RDONLY|r);try{const c=i.fstatSync(o,{bigint:!0});if(!c.isFile()||!physicalPathAndHandleIdentitiesEqual(a(n),a(c),process.platform))throw d(t.finalPath);u(t,c,"handle");const l=i.readFileSync(o),f=i.fstatSync(o,{bigint:!0});if(!x(c,f))throw d(t.finalPath);return u(t,f,"handle"),{contents:l,kind:"file",mode:f.mode}}finally{i.closeSync(o)}}function R(t,n){u(t,n);const e=i.readlinkSync(t.finalPath,{encoding:"buffer"});return u(t,n),e}function C(t,n,e){y(e);const r=i.readlinkSync(t,{encoding:"buffer"}),o=i.lstatSync(t,{bigint:!0});if(!o.isSymbolicLink()||!m(o,n))throw d(t);return y(e),r}function u(t,n,e="path"){y(t.ancestors);const r=i.lstatSync(t.finalPath,{bigint:!0});if(!(e==="handle"?physicalPathAndHandleIdentitiesEqual(a(r),a(n),process.platform):m(r,n)))throw d(t.finalPath)}function y(t){for(const n of t){const e=i.lstatSync(n.path,{bigint:!0});if(!e.isDirectory()||!S(a(e),n.identity))throw d(n.path);if(n.fileDescriptor!==null){const r=i.fstatSync(n.fileDescriptor,{bigint:!0});if(!r.isDirectory()||!S(a(r),n.identity))throw d(n.path)}}}function E(t){try{return i.lstatSync(t,{bigint:!0})}catch(n){if(w(n,"ENOENT")||w(n,"ENOTDIR"))return null;throw n}}function _(t){const n=t.replace(/^\.\/+/,"").replace(/\/+$/,"");if(!n)return[];const e=n.split("/");if(e.some(r=>r.length===0||r==="."||r===".."))throw new Error(`Invalid canonical fingerprint path: ${t}`);return e}function a(t){return{dev:t.dev,ino:t.ino,type:t.mode&D}}function m(t,n){return S(a(t),a(n))}function S(t,n){return t.dev===n.dev&&t.ino===n.ino&&t.type===n.type}function x(t,n){return m(t,n)&&t.mode===n.mode&&t.size===n.size&&t.mtimeNs===n.mtimeNs&&t.ctimeNs===n.ctimeNs}function g(t){for(const n of[...t].reverse())n.fileDescriptor!==null&&i.closeSync(n.fileDescriptor)}function d(t){return new Error(`Fingerprint path changed while reading: ${t}`)}function P(t,n){return new Error(`Fingerprinting does not support special filesystem nodes: ${t||"."} (${physicalNodeType(n)})`)}function T(t,n){return t<n?-1:t>n?1:0}
@@ -0,0 +1,20 @@
1
+ export interface DirectoryPublication {
2
+ readonly workspace: string;
3
+ readonly remote: string;
4
+ readonly branch: string;
5
+ readonly destination: string;
6
+ readonly message: string;
7
+ readonly token: string;
8
+ readonly env: Readonly<NodeJS.ProcessEnv>;
9
+ readonly maxFetchBytes: number;
10
+ readonly files: readonly {
11
+ path: string;
12
+ absolutePath: string;
13
+ executable: boolean;
14
+ }[];
15
+ }
16
+ export declare function publishDirectory(request: DirectoryPublication): Promise<{
17
+ revision: string;
18
+ previous: string | null;
19
+ changed: boolean;
20
+ }>;
@@ -0,0 +1,3 @@
1
+ import E from"node:path";import v from"node:fs";import{startManagedHostProcess as C,managedHostProcessInfrastructureError as R}from"../process/index.js";import{createManagedGitEnvironment as $}from"./managed-git.js";export async function publishDirectory(t){const h=E.join(t.workspace,"git"),a=Object.fromEntries(Object.entries($(t.env,process.platform,!0)).filter(([e])=>!/^(?:GIT_TRACE|GIT_CURL_VERBOSE|GIT_ASKPASS|SSH_ASKPASS)/iu.test(e))),d=Buffer.from(`x-access-token:${t.token}`).toString("base64");Object.assign(a,{GIT_TERMINAL_PROMPT:"0",GIT_CONFIG_COUNT:"1",GIT_CONFIG_KEY_0:`http.${t.remote}.extraHeader`,GIT_CONFIG_VALUE_0:`Authorization: Basic ${d}`,GIT_AUTHOR_NAME:t.env.GIT_AUTHOR_NAME||"Testkit",GIT_AUTHOR_EMAIL:t.env.GIT_AUTHOR_EMAIL||"testkit@localhost",GIT_COMMITTER_NAME:t.env.GIT_COMMITTER_NAME||"Testkit",GIT_COMMITTER_EMAIL:t.env.GIT_COMMITTER_EMAIL||"testkit@localhost"});async function r(e,n){const c=new AbortController,o=C({executable:"git",args:["-c","http.followRedirects=false","-c","credential.helper=","-c","core.hooksPath=","--git-dir",h,...e],cwd:t.workspace,env:a,displayLabel:`Report publication: git ${e[0]}`,outputMode:"bounded-capture",recoveryIdentity:"none",timeoutMs:12e4,signal:c.signal}),i={error:null},u=()=>{if(!i.error)try{let l=0;const S=E.join(h,"objects","pack");for(const O of v.readdirSync(S))try{l+=v.statSync(E.join(S,O)).size}catch(g){if(!(g instanceof Error)||!("code"in g)||g.code!=="ENOENT")throw g}if(l>t.maxFetchBytes)throw new Error("The destination exceeds the temporary Git storage budget. Use a smaller reports repository.")}catch(l){i.error=l instanceof Error?l:new Error("Could not measure temporary Git storage."),c.abort()}},f=e[0]!=="init"?setInterval(u,25):null,T=[];o.stdout.on("data",l=>T.push(l)),o.stdin.on("error",()=>{}),o.stdin.end(n);const p=await o.completion.finally(()=>{f&&clearInterval(f)});if(f&&u(),i.error)throw i.error;if(R("Report Git command",p)||p.exitCode!==0)throw new Error(`Report publication could not complete git ${e[0]}. Check repository access, branch protection, and whether the branch changed. Your checkout is untouched.`);return Buffer.concat(T)}if(await r(["init","--bare","--template=",h]),t.branch.startsWith("-")||t.branch.startsWith("refs/")||/[\0\r\n]/u.test(t.branch))throw new Error("Name a destination branch, not a revision expression.");await r(["check-ref-format","--branch",t.branch]);const m=`refs/heads/${t.branch}`,w=(await r(["ls-remote","--heads",t.remote])).toString("utf8"),b=w.split(`
2
+ `).find(e=>e.split(" ")[1]===m)?.split(" ")[0];let s=null,_=null;if(b)await r(["fetch","--keep","--filter=blob:none","--no-tags","--depth=1",t.remote,m]),s=(await r(["rev-parse","FETCH_HEAD^{commit}"])).toString("ascii").trim(),_=(await r(["rev-parse","FETCH_HEAD^{tree}"])).toString("ascii").trim();else if(w.trim())throw new Error(`Branch ${JSON.stringify(t.branch)} does not exist. Create it in the destination repository first.`);const I=[];for(const e of t.files){const n=(await r(["hash-object","-w","--no-filters","--",e.absolutePath])).toString("ascii").trim();I.push({path:e.path,entry:{name:Buffer.from(E.posix.basename(e.path)),mode:e.executable?"100755":"100644",kind:"blob",sha:n}})}const k=e=>r(["mktree","--missing","-z"],Buffer.concat(e.map(n=>Buffer.concat([Buffer.from(`${n.mode} ${n.kind} ${n.sha} `),n.name,Buffer.from([0])])))).then(n=>n.toString("ascii").trim());async function G(e){const n=[],c=new Set;for(const o of I.filter(i=>i.path.startsWith(e))){const i=o.path.slice(e.length);i.includes("/")?c.add(i.split("/")[0]):n.push(o.entry)}for(const o of c)n.push({name:Buffer.from(o),kind:"tree",mode:"040000",sha:await G(e+o+"/")});return k(n)}async function A(e,n,c){const o=e===null?[]:B(await r(["ls-tree","-z",e])),[i,...u]=n,f=o.find(p=>p.name.equals(Buffer.from(i)));if(f&&f.kind!=="tree")throw new Error("The destination or one of its parents is a file or a submodule. Choose a bundle directory.");const T=u.length===0?c:await A(f?.sha??null,u,c);return k([...o.filter(p=>!p.name.equals(Buffer.from(i))),{name:Buffer.from(i),mode:"040000",kind:"tree",sha:T}])}const M=await A(_,t.destination.split("/"),await G(""));if(M===_&&s!==null)return{revision:s,previous:s,changed:!1};const y=(await r(["commit-tree",M,...s===null?[]:["-p",s]],t.message+`
3
+ `)).toString("ascii").trim();try{await r(["push","--porcelain",`--force-with-lease=${m}:${s??""}`,t.remote,`${y}:${m}`])}catch{throw new Error(`Publication of commit ${y} was not confirmed. The branch may have changed, access may be denied, or the connection may have failed. Check the destination before retrying. Your checkout is untouched.`)}return{revision:y,previous:s,changed:!0}}function B(t){const h=[];let a=0;for(;a<t.length;){const d=t.indexOf(0,a),r=t.indexOf(9,a);if(d<0||r<a||r>d)throw new Error("Git returned an invalid tree.");const[m,w,b]=t.subarray(a,r).toString("ascii").split(" ");if(!/^[0-9a-f]{40}(?:[0-9a-f]{24})?$/u.test(b))throw new Error("Git returned an invalid tree.");h.push({mode:m,kind:w,sha:b,name:t.subarray(r+1,d)}),a=d+1}return h}
@@ -1,5 +1,5 @@
1
- import $ from"crypto";import p from"fs";import s from"path";import{appendFingerprintPathToHash as et,hasIncludedFingerprintDescendantAtPrefix as rt,normalizeFingerprintPolicy as N,shouldIgnoreCanonicalFingerprintPathAtPrefix as W}from"./fingerprint-policy.js";import{decodeGitPathRecord as C}from"./git-path.js";import{runManagedGitCommand as nt}from"./managed-git.js";import{physicalNodeMode as _,readPhysicalNode as j}from"./physical-tree.js";import{normalizeManagedHostEnvironment as ot,requireManagedHostCommandSuccess as D,resolveManagedHostExecutableIfAvailable as it}from"../process/index.js";import{readCiPullRequestHeadSha as at,resolveCiGitReference as z}from"./ci-environment.js";import{parseGitRepository as ct}from"./remote.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)??st(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=at(r),i=it("git",s.resolve(t),ot(process.env));if(!i&&!Ft(t)||i&&Dt(t))return null;const a=await K(t);if(a===null)return null;const c=Y(a,t),u=await w(c.repoRoot,["rev-parse","--verify","--quiet","HEAD"]),m=await lt(c.repoRoot,u,n),f=await w(c.repoRoot,["symbolic-ref","--quiet","--short","HEAD"]),b=await w(c.repoRoot,["config","--get","remote.origin.url"]),P=u!==null&&f===null,h=await ut(c,e,u,""),G=!!h,d=u||"unborn";return{kind:G?"dirty":"commit",repoRoot:c.repoRoot,worktreePath:c.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 st(t,e,r){const o=p.realpathSync.native(s.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 O(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(jt(r))return null;throw D(T(e),r),new Error("Unreachable Git repository probe result")}function Y(t,e){const r=p.realpathSync.native(s.resolve(t)),o=p.realpathSync.native(s.resolve(e)),n=Ct(s.relative(r,o));if(n===".."||n.startsWith("../")||s.isAbsolute(n)||process.platform==="win32"&&s.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 lt(t,e,r){if(e===null||r===null)return null;if(e===r)return r;const o=await v(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 ut(t,e,r,o){const n=await F(t,e,r,o),i=await F(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 F(t,e,r,o){await X(t.repoRoot,r);const n=$.createHash("sha256");let i=!1;const a=await vt(t.repoRoot),[c,u]=await Promise.all([V(t,a),pt(t,r,a)]),m=wt(c),f=Rt(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=j(t.productDir,d),U=M?await ft(t,tt,H,e,o):null;(!mt(g,E)||!ht(g,H,U,a))&&(i=!0,gt(n,d,E,g,H,U,a))}const P=await v(t.repoRoot,["ls-files","--others","--exclude-per-directory=.gitignore","--",t.productPathspec]),h=xt(t,A(P,"Git ls-files").map(d=>C(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),q(n,j(t.productDir,d.productRelativePath),a)}const G=await V(t,a);if(!Gt(c,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 v(t.repoRoot,["ls-files","--stage","--abbrev=64","--",t.productPathspec]);return dt(t,r,e)}function dt(t,e,r){const o=[];for(const n of A(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(" "),c=I(C(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=L(t,c);if(!u)throw new Error(`Git ls-files returned a path outside the product: ${c}`);o.push({mode:a[0],objectId:a[1],productRelativePath:u,rootRelativePath:c,stage:Number(a[2])})}return o.sort(Ht)}async function pt(t,e,r){if(e===null)return[];const o=await v(t.repoRoot,["ls-tree","-r","--full-tree",e,"--",t.productPathspec]);return yt(t,o,r)}async function ft(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 c=Y(a,i),u=await w(c.repoRoot,["rev-parse","--verify","--quiet","HEAD"]),m=await F(c,o,u,kt(n,e.productRelativePath)),f=j(t.productDir,e.productRelativePath);if(f.kind!=="directory"||!Et(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 ht(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"&&k(o,e.target)===n.objectId:n.mode==="100644"||n.mode==="100755"?e.kind==="file"&&Pt(e.mode,n.mode)===n.mode&&k(o,e.contents)===n.objectId:!1}function mt(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 gt(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 c of r)t.update("head-entry\0"),l(t,"mode",c.mode),l(t,"object",c.objectId);o.length===0&&t.update("index-missing\0");for(const c of o)t.update("index-entry\0"),l(t,"mode",c.mode),l(t,"object",c.objectId),l(t,"stage",c.stage.toString());i?bt(t,i,a):q(t,n,a)}function yt(t,e,r){const o=[];for(const n of A(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 c=I(C(n.slice(i+1))),u=L(t,c);if(!u)throw new Error(`Git ls-tree returned a path outside the product: ${c}`);o.push({mode:a[0],objectId:a[2],productRelativePath:u,rootRelativePath:c})}return o.sort((n,i)=>y(n.productRelativePath,i.productRelativePath))}async function vt(t){const e=(await v(t,["rev-parse","--show-object-format"])).trim();if(e==="sha1"||e==="sha256")return e;throw new Error(`Unsupported Git object format: ${e||"empty"}`)}function wt(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 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,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",k(r,e.contents)),S(t,e.contents);break;case"symlink":l(t,"blob",k(r,e.target)),S(t,e.target);break;case"directory":l(t,"mode",_(e.mode));break;case"ancestor-symlink":l(t,"obstacle",e.obstaclePath),S(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 Pt(t,e){return process.platform==="win32"?e:t&73n?"100755":"100644"}function k(t,e){return $.createHash(t).update(`blob ${e.byteLength.toString()}\0`).update(e).digest("hex")}function Gt(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 Et(t,e){return t.dev===e.dev&&t.ino===e.ino&&t.type===e.type}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=s.resolve(t.productDir,...r),n=s.relative(t.productDir,o);if(n===".."||n.startsWith(`..${s.sep}`)||s.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(s.resolve(t)),o=p.realpathSync.native(s.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`),S(t,Buffer.from(r))}function S(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=I(o);if(n===".."||n.startsWith("../")||s.isAbsolute(n)||process.platform==="win32"&&s.win32.isAbsolute(n))throw new Error(`Git returned a path outside its worktree: ${n}`);const i=L(t,n);i&&r.set(n,{productRelativePath:i,rootRelativePath:n})}return[...r.values()].sort((o,n)=>y(o.productRelativePath,n.productRelativePath))}function L(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 I(t){return t.replace(/^\.\/+/,"").replace(/\/+$/,"")}function Ct(t){return I(t.split(s.sep).join("/"))}function y(t,e){return t<e?-1:t>e?1:0}function Q(t,e){const r=$.createHash("sha256");return et(r,t,t,e),r.digest("hex")}async function v(t,e){const r=await O(t,e);return D(T(e),r).stdout}async function w(t,e){const r=await O(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 D(T(e),r),new Error("Unreachable Git command result")}function O(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 w(t,["rev-parse","--verify","--quiet","HEAD"])!==e)throw new Error("Git HEAD changed while collecting repository state")}function A(t,e){if(t.length===0)return[];if(!t.endsWith(`
1
+ import $ from"crypto";import p from"fs";import s from"path";import{appendFingerprintPathToHash as et,hasIncludedFingerprintDescendantAtPrefix as rt,normalizeFingerprintPolicy as N,shouldIgnoreCanonicalFingerprintPathAtPrefix as W}from"./fingerprint-policy.js";import{decodeGitPathRecord as C}from"./git-path.js";import{runManagedGitCommand as nt}from"./managed-git.js";import{physicalNodeMode as _,readPhysicalNode as D}from"./physical-tree.js";import{normalizeManagedHostEnvironment as ot,requireManagedHostCommandSuccess as F,resolveManagedHostExecutableIfAvailable as it}from"../process/index.js";import{readCiPullRequestHeadSha as at,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)??st(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=at(r),i=it("git",s.resolve(t),ot(process.env));if(!i&&!jt(t)||i&&Ft(t))return null;const a=await K(t);if(a===null)return null;const c=Y(a,t),u=await R(c.repoRoot,["rev-parse","--verify","--quiet","HEAD"]),m=await lt(c.repoRoot,u,n),f=await R(c.repoRoot,["symbolic-ref","--quiet","--short","HEAD"]),b=await R(c.repoRoot,["config","--get","remote.origin.url"]),P=u!==null&&f===null,h=await ut(c,e,u,""),G=!!h,d=u||"unborn";return{kind:G?"dirty":"commit",repoRoot:c.repoRoot,worktreePath:c.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 st(t,e,r){const o=p.realpathSync.native(s.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 O(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 F(T(e),r),new Error("Unreachable Git repository probe result")}function Y(t,e){const r=p.realpathSync.native(s.resolve(t)),o=p.realpathSync.native(s.resolve(e)),n=Ct(s.relative(r,o));if(n===".."||n.startsWith("../")||s.isAbsolute(n)||process.platform==="win32"&&s.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 lt(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 ut(t,e,r,o){const n=await j(t,e,r,o),i=await j(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 j(t,e,r,o){await X(t.repoRoot,r);const n=$.createHash("sha256");let i=!1;const a=await vt(t.repoRoot),[c,u]=await Promise.all([V(t,a),pt(t,r,a)]),m=wt(c),f=Rt(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=D(t.productDir,d),U=M?await ft(t,tt,H,e,o):null;(!mt(g,E)||!ht(g,H,U,a))&&(i=!0,gt(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,A(P,"Git ls-files").map(d=>C(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),q(n,D(t.productDir,d.productRelativePath),a)}const G=await V(t,a);if(!Gt(c,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 dt(t,r,e)}function dt(t,e,r){const o=[];for(const n of A(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(" "),c=I(C(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=L(t,c);if(!u)throw new Error(`Git ls-files returned a path outside the product: ${c}`);o.push({mode:a[0],objectId:a[1],productRelativePath:u,rootRelativePath:c,stage:Number(a[2])})}return o.sort(Ht)}async function pt(t,e,r){if(e===null)return[];const o=await w(t.repoRoot,["ls-tree","-r","--full-tree",e,"--",t.productPathspec]);return yt(t,o,r)}async function ft(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 c=Y(a,i),u=await R(c.repoRoot,["rev-parse","--verify","--quiet","HEAD"]),m=await j(c,o,u,kt(n,e.productRelativePath)),f=D(t.productDir,e.productRelativePath);if(f.kind!=="directory"||!Et(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 ht(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"&&k(o,e.target)===n.objectId:n.mode==="100644"||n.mode==="100755"?e.kind==="file"&&Pt(e.mode,n.mode)===n.mode&&k(o,e.contents)===n.objectId:!1}function mt(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 gt(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 c of r)t.update("head-entry\0"),l(t,"mode",c.mode),l(t,"object",c.objectId);o.length===0&&t.update("index-missing\0");for(const c of o)t.update("index-entry\0"),l(t,"mode",c.mode),l(t,"object",c.objectId),l(t,"stage",c.stage.toString());i?bt(t,i,a):q(t,n,a)}function yt(t,e,r){const o=[];for(const n of A(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 c=I(C(n.slice(i+1))),u=L(t,c);if(!u)throw new Error(`Git ls-tree returned a path outside the product: ${c}`);o.push({mode:a[0],objectId:a[2],productRelativePath:u,rootRelativePath:c})}return o.sort((n,i)=>y(n.productRelativePath,i.productRelativePath))}async function vt(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 wt(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 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,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",k(r,e.contents)),S(t,e.contents);break;case"symlink":l(t,"blob",k(r,e.target)),S(t,e.target);break;case"directory":l(t,"mode",_(e.mode));break;case"ancestor-symlink":l(t,"obstacle",e.obstaclePath),S(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 Pt(t,e){return process.platform==="win32"?e:t&73n?"100755":"100644"}function k(t,e){return $.createHash(t).update(`blob ${e.byteLength.toString()}\0`).update(e).digest("hex")}function Gt(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 Et(t,e){return t.dev===e.dev&&t.ino===e.ino&&t.type===e.type}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=s.resolve(t.productDir,...r),n=s.relative(t.productDir,o);if(n===".."||n.startsWith(`..${s.sep}`)||s.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(s.resolve(t)),o=p.realpathSync.native(s.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`),S(t,Buffer.from(r))}function S(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=I(o);if(n===".."||n.startsWith("../")||s.isAbsolute(n)||process.platform==="win32"&&s.win32.isAbsolute(n))throw new Error(`Git returned a path outside its worktree: ${n}`);const i=L(t,n);i&&r.set(n,{productRelativePath:i,rootRelativePath:n})}return[...r.values()].sort((o,n)=>y(o.productRelativePath,n.productRelativePath))}function L(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 I(t){return t.replace(/^\.\/+/,"").replace(/\/+$/,"")}function Ct(t){return I(t.split(s.sep).join("/"))}function y(t,e){return t<e?-1:t>e?1:0}function Q(t,e){const r=$.createHash("sha256");return et(r,t,t,e),r.digest("hex")}async function w(t,e){const r=await O(t,e);return F(T(e),r).stdout}async function R(t,e){const r=await O(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 F(T(e),r),new Error("Unreachable Git command result")}function O(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 A(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 T(t){const e=t.find(r=>!r.startsWith("-"));return e?`Git ${e}`:"Git command"}function jt(t){return`${t.stderr}
5
- ${t.stdout}`.toLowerCase().includes("not a git repository")}function Dt(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(s.resolve(t));for(;;){if(!p.statSync(r).isDirectory())return!1;for(const n of[".git","HEAD"])try{return p.lstatSync(s.join(r,n)),!1}catch(i){if(!R(i,"ENOENT"))return!1}const o=s.dirname(r);if(o===r)return!0;r=o}}catch{return!1}}function Ft(t){let e=p.realpathSync.native(s.resolve(t)),r=p.statSync(e).dev;const o=qt(),n=Ot(process.env.GIT_DISCOVERY_ACROSS_FILESYSTEM);let i=s.dirname(e);for(;i!==e;){if(o.has(e))return!1;if(Z(s.join(e,".git")))return!0;const a=p.statSync(i).dev;if(!n&&a!==r)return!1;e=i,r=a,i=s.dirname(e)}return o.has(e)?!1:Z(s.join(e,".git"))}function qt(){const t=process.env.GIT_CEILING_DIRECTORIES;return t?new Set(t.split(s.delimiter).filter(Boolean).map(e=>Lt(e))):new Set}function Lt(t){const e=s.resolve(t);try{return p.realpathSync.native(e)}catch(r){if(R(r,"ENOENT")||R(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(R(e,"ENOENT")||R(e,"ENOTDIR"))return!1;throw e}}function R(t,e){return t!==null&&typeof t=="object"&&"code"in t&&Reflect.get(t,"code")===e}
4
+ `)}function T(t){const e=t.find(r=>!r.startsWith("-"));return e?`Git ${e}`:"Git command"}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(s.resolve(t));for(;;){if(!p.statSync(r).isDirectory())return!1;for(const n of[".git","HEAD"])try{return p.lstatSync(s.join(r,n)),!1}catch(i){if(!v(i,"ENOENT"))return!1}const o=s.dirname(r);if(o===r)return!0;r=o}}catch{return!1}}function jt(t){let e=p.realpathSync.native(s.resolve(t)),r=p.statSync(e).dev;const o=qt(),n=Ot(process.env.GIT_DISCOVERY_ACROSS_FILESYSTEM);let i=s.dirname(e);for(;i!==e;){if(o.has(e))return!1;if(Z(s.join(e,".git")))return!0;const a=p.statSync(i).dev;if(!n&&a!==r)return!1;e=i,r=a,i=s.dirname(e)}return o.has(e)?!1:Z(s.join(e,".git"))}function qt(){const t=process.env.GIT_CEILING_DIRECTORIES;return t?new Set(t.split(s.delimiter).filter(Boolean).map(e=>Lt(e))):new Set}function Lt(t){const e=s.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}}
@@ -32,4 +32,3 @@ export interface NormalizedBuildPrepareConfig {
32
32
  }
33
33
  export declare function normalizeBuildConfig(value: unknown, label: string): NormalizedBuildConfig | null;
34
34
  export declare function buildConfigToPrepare(build: NormalizedBuildConfig | null | undefined): NormalizedBuildPrepareConfig;
35
- export declare function compiledEntryFromBuild(build: NormalizedBuildConfig | null | undefined): string | null;
@@ -1 +1 @@
1
- import o from"path";import{normalizeConfiguredInputs as c,normalizeConfiguredSteps as p,normalizeOptionalString as e}from"./configured-steps.js";export function normalizeBuildConfig(t,s){if(t==null)return null;if(!m(t))throw new Error(`${s} must be an object`);const n=e(t.kind);if(n==="tsc")return{kind:n,cwd:e(t.cwd),entry:e(t.entry)||"src/index.ts",tsconfig:e(t.tsconfig)||"tsconfig.json",outDir:e(t.outDir)||"dist",inputs:c(t.inputs,s)};if(n==="script"){const r=e(t.script);if(!r)throw new Error(`${s}.script must be a non-empty string`);return{kind:n,cwd:e(t.cwd),script:r,inputs:c(t.inputs,s)}}if(n==="next")return{kind:n,cwd:e(t.cwd),distDir:e(t.distDir)||"dist",tsconfig:e(t.tsconfig)||"tsconfig.json",inputs:c(t.inputs,s)};if(n==="steps")return{kind:n,inputs:c(t.inputs,s),steps:p(t.steps,`${s}.steps`)};throw new Error(`${s}.kind must be one of: tsc, script, next, steps`)}export function buildConfigToPrepare(t){if(!t)return{inputs:[],steps:[]};if(t.kind==="steps")return{inputs:[...t.inputs],steps:[...t.steps]};if(t.kind==="script")return{inputs:[...t.inputs],steps:[{kind:"command",cmd:t.script,cwd:t.cwd||void 0,inputs:[]}]};if(t.kind==="next")return{inputs:t.inputs.length>0?[...t.inputs]:w(t),steps:[{kind:"module",specifier:"@gfdelarue/testkit/config/next-runtime-tsconfig#writeNextRuntimeTsconfig",cwd:t.cwd||void 0,inputs:[]},{kind:"command",cmd:"{bin:next} build",cwd:t.cwd||void 0,inputs:[]}]};const s=t.inputs.length>0?[...t.inputs]:d(t),n=`{bin:tsc} -p ${t.tsconfig} --outDir "{prepareDir}/${t.outDir}"`,r=u(t.entry);return{inputs:s,steps:[{kind:"command",cmd:n,cwd:t.cwd||void 0,inputs:[]},{kind:"module",specifier:"@gfdelarue/testkit/config/build-assets#copyTscRuntimeAssets",cwd:t.cwd||void 0,inputs:[],args:{sourceDir:r,outDir:t.outDir}}]}}export function compiledEntryFromBuild(t){return!t||t.kind!=="tsc"?null:o.posix.join(t.outDir,f(t.entry))}function f(t){const n=t.split(o.sep).join("/").replace(/\.[cm]?[jt]sx?$/i,".js");return n.startsWith("src/")?n.slice(4):n}function u(t){const s=t.split(o.sep).join("/");if(s.startsWith("src/"))return"src";const n=s.split("/").filter(Boolean);return n.length>1?n[0]??".":"."}function d(t){const s=new Set([i(t.cwd,t.tsconfig),i(t.cwd,"package.json")]),n=i(t.cwd,t.entry.split(o.sep).join("/")),r=n.includes("/")?n.slice(0,n.indexOf("/")):n;return r&&s.add(r),[...s]}function w(t){return[...new Set([i(t.cwd,t.tsconfig),i(t.cwd,"package.json"),i(t.cwd,"src"),i(t.cwd,"app"),i(t.cwd,"public"),i(t.cwd,"next.config.js"),i(t.cwd,"next.config.mjs"),i(t.cwd,"next.config.ts")])]}function i(t,s){const n=(t||"").split(o.sep).join("/").replace(/^\.\/+/,"").replace(/\/+$/g,""),r=s.split(o.sep).join("/").replace(/^\.\/+/,"");return!n||n==="."?r:`${n}/${r}`.replace(/\/+/g,"/")}function m(t){return t!==null&&typeof t=="object"}
1
+ import c from"path";import{normalizeConfiguredInputs as o,normalizeConfiguredSteps as p,normalizeOptionalString as e}from"./configured-steps.js";export function normalizeBuildConfig(t,s){if(t==null)return null;if(!w(t))throw new Error(`${s} must be an object`);const n=e(t.kind);if(n==="tsc")return{kind:n,cwd:e(t.cwd),entry:e(t.entry)||"src/index.ts",tsconfig:e(t.tsconfig)||"tsconfig.json",outDir:e(t.outDir)||"dist",inputs:o(t.inputs,s)};if(n==="script"){const r=e(t.script);if(!r)throw new Error(`${s}.script must be a non-empty string`);return{kind:n,cwd:e(t.cwd),script:r,inputs:o(t.inputs,s)}}if(n==="next")return{kind:n,cwd:e(t.cwd),distDir:e(t.distDir)||"dist",tsconfig:e(t.tsconfig)||"tsconfig.json",inputs:o(t.inputs,s)};if(n==="steps")return{kind:n,inputs:o(t.inputs,s),steps:p(t.steps,`${s}.steps`)};throw new Error(`${s}.kind must be one of: tsc, script, next, steps`)}export function buildConfigToPrepare(t){if(!t)return{inputs:[],steps:[]};if(t.kind==="steps")return{inputs:[...t.inputs],steps:[...t.steps]};if(t.kind==="script")return{inputs:[...t.inputs],steps:[{kind:"command",cmd:t.script,cwd:t.cwd||void 0,inputs:[]}]};if(t.kind==="next")return{inputs:t.inputs.length>0?[...t.inputs]:u(t),steps:[{kind:"module",specifier:"@gfdelarue/testkit/config/next-runtime-tsconfig#writeNextRuntimeTsconfig",cwd:t.cwd||void 0,inputs:[]},{kind:"command",cmd:"{bin:next} build",cwd:t.cwd||void 0,inputs:[]}]};const s=t.inputs.length>0?[...t.inputs]:d(t),n=`{bin:tsc} -p ${t.tsconfig} --outDir "{prepareDir}/${t.outDir}"`,r=f(t.entry);return{inputs:s,steps:[{kind:"command",cmd:n,cwd:t.cwd||void 0,inputs:[]},{kind:"module",specifier:"@gfdelarue/testkit/config/build-assets#copyTscRuntimeAssets",cwd:t.cwd||void 0,inputs:[],args:{sourceDir:r,outDir:t.outDir}}]}}function f(t){const s=t.split(c.sep).join("/");if(s.startsWith("src/"))return"src";const n=s.split("/").filter(Boolean);return n.length>1?n[0]??".":"."}function d(t){const s=new Set([i(t.cwd,t.tsconfig),i(t.cwd,"package.json")]),n=i(t.cwd,t.entry.split(c.sep).join("/")),r=n.includes("/")?n.slice(0,n.indexOf("/")):n;return r&&s.add(r),[...s]}function u(t){return[...new Set([i(t.cwd,t.tsconfig),i(t.cwd,"package.json"),i(t.cwd,"src"),i(t.cwd,"app"),i(t.cwd,"public"),i(t.cwd,"next.config.js"),i(t.cwd,"next.config.mjs"),i(t.cwd,"next.config.ts")])]}function i(t,s){const n=(t||"").split(c.sep).join("/").replace(/^\.\/+/,"").replace(/\/+$/g,""),r=s.split(c.sep).join("/").replace(/^\.\/+/,"");return!n||n==="."?r:`${n}/${r}`.replace(/\/+/g,"/")}function w(t){return t!==null&&typeof t=="object"}
@@ -1 +1 @@
1
- import c from"fs";import s from"path";import{isTestkitPackageSpecifier as m,requireConfiguredStepTestkitModuleSpecifier as w}from"./configured-step-testkit-modules.js";export function normalizeOptionalString(n){if(typeof n!="string")return null;const r=n.trim();return r.length>0?r:null}export function normalizeConfiguredInputs(n,r){if(n==null)return[];if(!g(n))throw new Error(`${r}.inputs must be an array`);return n.map((t,e)=>{const i=normalizeOptionalString(t);if(!i)throw new Error(`${r}.inputs[${String(e)}] must be a non-empty string`);return i})}export function normalizeConfiguredSteps(n,r){if(n==null)return[];if(!g(n))throw new Error(`${r} must be an array`);return n.map((t,e)=>normalizeConfiguredStep(t,`${r}[${String(e)}]`))}export function normalizeConfiguredStep(n,r){if(!p(n))throw new Error(`${r} must be an object`);const t=normalizeOptionalString(n.kind);if(t==="command"){const e=normalizeOptionalString(n.run);if(!e)throw new Error(`${r}.run must be a non-empty string`);const i=normalizeStepArgs(n.args,r);return{kind:t,cmd:e,cwd:normalizeOptionalString(n.cwd),inputs:normalizeConfiguredInputs(n.inputs,r),...i!==void 0?{args:i}:{}}}if(t==="sql-file"){const e=normalizeOptionalString(n.path);if(!e)throw new Error(`${r}.path must be a non-empty string`);const i=normalizeStepArgs(n.args,r);return{kind:t,path:e,cwd:normalizeOptionalString(n.cwd),inputs:normalizeConfiguredInputs(n.inputs,r),...i!==void 0?{args:i}:{}}}if(t==="module"){const e=normalizeOptionalString(n.target);if(!e)throw new Error(`${r}.target must be a non-empty string`);const i=normalizeStepArgs(n.args,r);return{kind:t,specifier:e,cwd:normalizeOptionalString(n.cwd),inputs:normalizeConfiguredInputs(n.inputs,r),...i!==void 0?{args:i}:{}}}throw new Error(`${r}.kind must be one of: command, sql-file, module`)}export function parseModuleSpecifier(n){const[r="",t]=String(n).split("#",2);return{modulePath:r,exportName:t||"default"}}export function normalizeStepArgs(n,r){if(n!=null)try{const t=JSON.stringify(n);if(typeof t!="string")throw new Error("JSON serialization returned undefined");return JSON.parse(t)}catch{throw new Error(`${r}.args must be JSON-serializable`)}}export function isBareModuleSpecifier(n){return typeof n!="string"||n.length===0||n.startsWith("./")||n.startsWith("../")?!1:!s.isAbsolute(n)}export function resolveConfiguredCwd(n,r){return s.resolve(n,r||".")}export function resolveConfiguredPath(n,r,t){return s.resolve(resolveConfiguredCwd(n,r),t)}export function collectConfiguredInputs(n,{inputs:r=[],steps:t=[]}={}){const e=new Set;for(const i of r)e.add(resolveConfiguredPath(n,null,i));for(const i of t){if(i.kind==="sql-file"&&e.add(resolveConfiguredPath(n,i.cwd,i.path)),i.kind==="module"){const f=a(i),{modulePath:o}=parseModuleSpecifier(f),u=resolveConfiguredPath(n,i.cwd,o);o.startsWith("@gfdelarue/testkit")||c.existsSync(u)?o.startsWith("@gfdelarue/testkit")||e.add(u):isBareModuleSpecifier(o)||e.add(u)}for(const f of i.inputs||[])e.add(resolveConfiguredPath(n,i.cwd,f))}return[...e].sort()}export function summarizeConfiguredStep(n){return n.kind==="command"?`command: ${n.cmd.trim()}`:n.kind==="sql-file"?`sql: ${n.path}`:`module: ${n.specifier}`}export function finalizeConfiguredInputs(n=[],r){return n.map(t=>r(t))}export function finalizeConfiguredStep(n,r){return n.kind==="command"?{...n,cmd:r(n.cmd),...typeof n.cwd=="string"?{cwd:r(n.cwd)}:{},inputs:finalizeConfiguredInputs(n.inputs,r),...n.args!==void 0?{args:n.args}:{}}:n.kind==="sql-file"?{...n,path:r(n.path),...typeof n.cwd=="string"?{cwd:r(n.cwd)}:{},inputs:finalizeConfiguredInputs(n.inputs,r),...n.args!==void 0?{args:n.args}:{}}:{...n,specifier:r(n.specifier),...typeof n.cwd=="string"?{cwd:r(n.cwd)}:{},inputs:finalizeConfiguredInputs(n.inputs,r),...n.args!==void 0?{args:n.args}:{}}}export function finalizeConfiguredSteps(n=[],r){return n.map(t=>finalizeConfiguredStep(t,r))}export function validateConfiguredCollection({productDir:n,label:r,inputs:t=[],steps:e=[]}){for(const i of t)d(resolveConfiguredPath(n,null,i),`${r} input`);for(const i of e){if(i.cwd&&d(resolveConfiguredCwd(n,i.cwd),`${r} step cwd`),i.kind==="sql-file"&&d(resolveConfiguredPath(n,i.cwd,i.path),`${r} sql file`),i.kind==="module"){const f=a(i),{modulePath:o}=parseModuleSpecifier(f),u=resolveConfiguredPath(n,i.cwd,o);m(o)?w(o):c.existsSync(u)?d(u,`${r} module`):isBareModuleSpecifier(o)||d(u,`${r} module`)}for(const f of i.inputs||[])d(resolveConfiguredPath(n,i.cwd,f),`${r} step input`)}}function d(n,r){if(!c.existsSync(n))throw new Error(`${r} does not exist: ${n}`)}function a(n){const r="specifier"in n?n.specifier:void 0,t="target"in n?n.target:void 0;return r||t}function p(n){return n!==null&&typeof n=="object"}function g(n){return Array.isArray(n)}
1
+ import c from"fs";import s from"path";import{isTestkitPackageSpecifier as m,requireConfiguredStepTestkitModuleSpecifier as w}from"./configured-step-testkit-modules.js";import{isUnknownArray as a}from"../kernel/unknown-values.js";export function normalizeOptionalString(n){if(typeof n!="string")return null;const r=n.trim();return r.length>0?r:null}export function normalizeConfiguredInputs(n,r){if(n==null)return[];if(!a(n))throw new Error(`${r}.inputs must be an array`);return n.map((t,e)=>{const i=normalizeOptionalString(t);if(!i)throw new Error(`${r}.inputs[${String(e)}] must be a non-empty string`);return i})}export function normalizeConfiguredSteps(n,r){if(n==null)return[];if(!a(n))throw new Error(`${r} must be an array`);return n.map((t,e)=>normalizeConfiguredStep(t,`${r}[${String(e)}]`))}export function normalizeConfiguredStep(n,r){if(!p(n))throw new Error(`${r} must be an object`);const t=normalizeOptionalString(n.kind);if(t==="command"){const e=normalizeOptionalString(n.run);if(!e)throw new Error(`${r}.run must be a non-empty string`);const i=normalizeStepArgs(n.args,r);return{kind:t,cmd:e,cwd:normalizeOptionalString(n.cwd),inputs:normalizeConfiguredInputs(n.inputs,r),...i!==void 0?{args:i}:{}}}if(t==="sql-file"){const e=normalizeOptionalString(n.path);if(!e)throw new Error(`${r}.path must be a non-empty string`);const i=normalizeStepArgs(n.args,r);return{kind:t,path:e,cwd:normalizeOptionalString(n.cwd),inputs:normalizeConfiguredInputs(n.inputs,r),...i!==void 0?{args:i}:{}}}if(t==="module"){const e=normalizeOptionalString(n.target);if(!e)throw new Error(`${r}.target must be a non-empty string`);const i=normalizeStepArgs(n.args,r);return{kind:t,specifier:e,cwd:normalizeOptionalString(n.cwd),inputs:normalizeConfiguredInputs(n.inputs,r),...i!==void 0?{args:i}:{}}}throw new Error(`${r}.kind must be one of: command, sql-file, module`)}export function parseModuleSpecifier(n){const[r="",t]=String(n).split("#",2);return{modulePath:r,exportName:t||"default"}}export function normalizeStepArgs(n,r){if(n!=null)try{const t=JSON.stringify(n);if(typeof t!="string")throw new Error("JSON serialization returned undefined");return JSON.parse(t)}catch{throw new Error(`${r}.args must be JSON-serializable`)}}export function isBareModuleSpecifier(n){return typeof n!="string"||n.length===0||n.startsWith("./")||n.startsWith("../")?!1:!s.isAbsolute(n)}export function resolveConfiguredCwd(n,r){return s.resolve(n,r||".")}export function resolveConfiguredPath(n,r,t){return s.resolve(resolveConfiguredCwd(n,r),t)}export function collectConfiguredInputs(n,{inputs:r=[],steps:t=[]}={}){const e=new Set;for(const i of r)e.add(resolveConfiguredPath(n,null,i));for(const i of t){if(i.kind==="sql-file"&&e.add(resolveConfiguredPath(n,i.cwd,i.path)),i.kind==="module"){const f=g(i),{modulePath:o}=parseModuleSpecifier(f),u=resolveConfiguredPath(n,i.cwd,o);o.startsWith("@gfdelarue/testkit")||c.existsSync(u)?o.startsWith("@gfdelarue/testkit")||e.add(u):isBareModuleSpecifier(o)||e.add(u)}for(const f of i.inputs||[])e.add(resolveConfiguredPath(n,i.cwd,f))}return[...e].sort()}export function summarizeConfiguredStep(n){return n.kind==="command"?`command: ${n.cmd.trim()}`:n.kind==="sql-file"?`sql: ${n.path}`:`module: ${n.specifier}`}export function finalizeConfiguredInputs(n=[],r){return n.map(t=>r(t))}export function finalizeConfiguredStep(n,r){return n.kind==="command"?{...n,cmd:r(n.cmd),...typeof n.cwd=="string"?{cwd:r(n.cwd)}:{},inputs:finalizeConfiguredInputs(n.inputs,r),...n.args!==void 0?{args:n.args}:{}}:n.kind==="sql-file"?{...n,path:r(n.path),...typeof n.cwd=="string"?{cwd:r(n.cwd)}:{},inputs:finalizeConfiguredInputs(n.inputs,r),...n.args!==void 0?{args:n.args}:{}}:{...n,specifier:r(n.specifier),...typeof n.cwd=="string"?{cwd:r(n.cwd)}:{},inputs:finalizeConfiguredInputs(n.inputs,r),...n.args!==void 0?{args:n.args}:{}}}export function finalizeConfiguredSteps(n=[],r){return n.map(t=>finalizeConfiguredStep(t,r))}export function validateConfiguredCollection({productDir:n,label:r,inputs:t=[],steps:e=[]}){for(const i of t)d(resolveConfiguredPath(n,null,i),`${r} input`);for(const i of e){if(i.cwd&&d(resolveConfiguredCwd(n,i.cwd),`${r} step cwd`),i.kind==="sql-file"&&d(resolveConfiguredPath(n,i.cwd,i.path),`${r} sql file`),i.kind==="module"){const f=g(i),{modulePath:o}=parseModuleSpecifier(f),u=resolveConfiguredPath(n,i.cwd,o);m(o)?w(o):c.existsSync(u)?d(u,`${r} module`):isBareModuleSpecifier(o)||d(u,`${r} module`)}for(const f of i.inputs||[])d(resolveConfiguredPath(n,i.cwd,f),`${r} step input`)}}function d(n,r){if(!c.existsSync(n))throw new Error(`${r} does not exist: ${n}`)}function g(n){const r="specifier"in n?n.specifier:void 0,t="target"in n?n.target:void 0;return r||t}function p(n){return n!==null&&typeof n=="object"}
@@ -1,5 +1,4 @@
1
1
  type RuntimeIdentifier = number | string;
2
- export type RuntimeUrlMapping = readonly [false | null | string | undefined, string];
3
2
  type StringMap = ReadonlyMap<string, string>;
4
3
  export interface ExecutionEnvironmentLease {
5
4
  leaseDir?: null | string;
@@ -47,7 +46,6 @@ export interface ExecutionTemplateContextValues {
47
46
  serviceName?: string;
48
47
  serviceStateDir?: null | string;
49
48
  stateDirByService?: StringMap;
50
- urlMappings?: readonly RuntimeUrlMapping[];
51
49
  }
52
50
  export interface ExecutionEnvironmentLocalConfig {
53
51
  env?: Readonly<Record<string, string>>;
@@ -1 +1 @@
1
- import v from"path";import{joinPathList as m,packageBinPath as E,shellQuote as w}from"../process/host/platform.js";export function buildExecutionEnv(r,e={},t=process.env,n={}){return o(r,null,e,t,n)}export function buildTemplateExecutionEnv(r,e={},t=process.env,n={}){return o(r,null,e,t,{...n,omitRuntimeDatabaseBindings:!0})}export function buildTaskExecutionEnv(r,e,t={},n=process.env,i={}){return o(r,e,t,n,i)}export function resolveExecutionEnvValues(r,e,t=process.env){return o(r,null,{},t,{includeTestkitControls:!1},new Set(e))}function o(r,e,t,n,i={},s=null){const a=c(i.inheritProcessEnv===!1?{}:n,s,!1);delete a.DATABASE_URL;const u=buildTemplateContext(r,e),p=i.omitRuntimeDatabaseBindings===!0,f=c(r.testkit.serviceEnv,s,p),b=c(r.testkit.local?.env,s,p);return{...a,...d(f,u),...d(b,u),...d(c(t,s,!1),u),...i.includeTestkitControls===!1?{}:{TESTKIT_ACTIVE:"1",TESTKIT_MODE:i.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 finalizeString(r,e){let t=resolveTemplateString(r,e);for(const[n,i]of e.urlMappings||[])n&&i&&n!==i&&(t=t.split(n).join(i));return t}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,n])=>[t,finalizeSerializable(n,e)]))}export function resolveTemplateString(r,e){return typeof r!="string"?r:r.replace(/\{([a-zA-Z]+)(?::([^}]+))?\}/g,(t,n,i)=>{switch(n){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 s=v.resolve(e.productDir||process.cwd(),e.serviceCwd||".");return w(E(s,i))}case"pathList":{const s=l(i||"").split(",").map(a=>a.trim()).filter(Boolean).map(a=>v.resolve(e.productDir||process.cwd(),a));return m(s)}case"prepareDir":return e.prepareDir||"";case"lease":return e.leaseId?String(e.leaseId):"";case"leaseDir":return e.leaseDir||"";case"port":{const s=i||e.serviceName,a=e.portMap?.get(s);if(!a)throw new Error(`Unknown port placeholder for service "${s}"`);return String(a)}case"baseUrl":{const s=i||e.serviceName,a=e.baseUrlByService?.get(s);if(!a)throw new Error(`Unknown baseUrl placeholder for service "${s}"`);return a}case"readyUrl":{const s=i||e.serviceName,a=e.readyUrlByService?.get(s);if(!a)throw new Error(`Unknown readyUrl placeholder for service "${s}"`);return a}case"publicHost":return e.publicHost||"127.0.0.1";case"publicBaseUrl":{const s=i||e.serviceName,a=e.publicBaseUrlByService?.get(s)||e.baseUrlByService?.get(s);if(!a)throw new Error(`Unknown publicBaseUrl placeholder for service "${s}"`);return a}case"privateBaseUrl":{const s=i||e.serviceName,a=e.privateBaseUrlByService?.get(s)||e.baseUrlByService?.get(s);if(!a)throw new Error(`Unknown privateBaseUrl placeholder for service "${s}"`);return a}case"serviceContextUrl":{const s=i||e.serviceName,a=e.serviceContextUrlByService?.get(s)||e.baseUrlByService?.get(s);if(!a)throw new Error(`Unknown serviceContextUrl placeholder for service "${s}"`);return a}case"dbUrl":case"dbHost":case"dbPort":case"dbName":case"dbUser":case"dbPassword":case"dbSsl":{const s=i||e.serviceName;return U(n,t,s,e)}default:throw new Error(`Unsupported template token "{${n}${i?`:${i}`:""}}"`)}})}function d(r,e){return Object.fromEntries(Object.entries(r||{}).map(([t,n])=>[t,typeof n=="string"?S(t,n,e):n]))}function S(r,e,t){try{return finalizeString(e,t)}catch(n){const i=n instanceof Error?n.message:l(n);throw new Error(`Service "${t.serviceName}" env ${r}: ${i}`,{cause:n})}}function c(r,e,t){return Object.fromEntries(Object.entries(r||{}).filter(([n,i])=>e&&!e.has(n)?!1:!(t&&typeof i=="string"&&findDatabasePlaceholders(i).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,n){const i=n.stateDirByService?.get(t),s=n.databaseBindingsByService?n.databaseBindingsByService.get(t):i?n.databaseBindingsReader?.(i):null;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())}function l(r){const e=Reflect.apply(String,void 0,[r]);if(typeof e!="string")throw new Error("String conversion returned an invalid result");return e}
1
+ import v from"path";import{joinPathList as m,packageBinPath as E,shellQuote as w}from"../process/host/platform.js";export function buildExecutionEnv(r,e={},s=process.env,n={}){return o(r,null,e,s,n)}export function buildTemplateExecutionEnv(r,e={},s=process.env,n={}){return o(r,null,e,s,{...n,omitRuntimeDatabaseBindings:!0})}export function buildTaskExecutionEnv(r,e,s={},n=process.env,i={}){return o(r,e,s,n,i)}export function resolveExecutionEnvValues(r,e,s=process.env){return o(r,null,{},s,{includeTestkitControls:!1},new Set(e))}function o(r,e,s,n,i={},t=null){const a=c(i.inheritProcessEnv===!1?{}:n,t,!1);delete a.DATABASE_URL;const u=buildTemplateContext(r,e),p=i.omitRuntimeDatabaseBindings===!0,b=c(r.testkit.serviceEnv,t,p),f=c(r.testkit.local?.env,t,p);return{...a,...d(b,u),...d(f,u),...d(c(s,t,!1),u),...i.includeTestkitControls===!1?{}:{TESTKIT_ACTIVE:"1",TESTKIT_MODE:i.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 s=r.testkit.templateContext||{};return{...s,runtimeId:r.runtimeId||s.runtimeId||null,runtimeLabel:r.runtimeLabel||s.runtimeLabel||null,productDir:r.productDir||s.productDir||null,serviceName:r.name,serviceStateDir:r.stateDir||s.serviceStateDir||null,prepareDir:r.testkit.prepareDir||s.prepareDir||null,leaseId:e?.leaseId||null,leaseDir:e?.leaseDir||null}}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(s=>finalizeSerializable(s,e)):r===null||typeof r!="object"?r:Object.fromEntries(Object.entries(r).map(([s,n])=>[s,finalizeSerializable(n,e)]))}export function resolveTemplateString(r,e){return typeof r!="string"?r:r.replace(/\{([a-zA-Z]+)(?::([^}]+))?\}/g,(s,n,i)=>{switch(n){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 t=v.resolve(e.productDir||process.cwd(),e.serviceCwd||".");return w(E(t,i))}case"pathList":{const t=l(i||"").split(",").map(a=>a.trim()).filter(Boolean).map(a=>v.resolve(e.productDir||process.cwd(),a));return m(t)}case"prepareDir":return e.prepareDir||"";case"lease":return e.leaseId?String(e.leaseId):"";case"leaseDir":return e.leaseDir||"";case"port":{const t=i||e.serviceName,a=e.portMap?.get(t);if(!a)throw new Error(`Unknown port placeholder for service "${t}"`);return String(a)}case"baseUrl":{const t=i||e.serviceName,a=e.baseUrlByService?.get(t);if(!a)throw new Error(`Unknown baseUrl placeholder for service "${t}"`);return a}case"readyUrl":{const t=i||e.serviceName,a=e.readyUrlByService?.get(t);if(!a)throw new Error(`Unknown readyUrl placeholder for service "${t}"`);return a}case"publicHost":return e.publicHost||"127.0.0.1";case"publicBaseUrl":{const t=i||e.serviceName,a=e.publicBaseUrlByService?.get(t)||e.baseUrlByService?.get(t);if(!a)throw new Error(`Unknown publicBaseUrl placeholder for service "${t}"`);return a}case"privateBaseUrl":{const t=i||e.serviceName,a=e.privateBaseUrlByService?.get(t)||e.baseUrlByService?.get(t);if(!a)throw new Error(`Unknown privateBaseUrl placeholder for service "${t}"`);return a}case"serviceContextUrl":{const t=i||e.serviceName,a=e.serviceContextUrlByService?.get(t)||e.baseUrlByService?.get(t);if(!a)throw new Error(`Unknown serviceContextUrl placeholder for service "${t}"`);return a}case"dbUrl":case"dbHost":case"dbPort":case"dbName":case"dbUser":case"dbPassword":case"dbSsl":{const t=i||e.serviceName;return U(n,s,t,e)}default:throw new Error(`Unsupported template token "{${n}${i?`:${i}`:""}}"`)}})}function d(r,e){return Object.fromEntries(Object.entries(r||{}).map(([s,n])=>[s,typeof n=="string"?S(s,n,e):n]))}function S(r,e,s){try{return finalizeString(e,s)}catch(n){const i=n instanceof Error?n.message:l(n);throw new Error(`Service "${s.serviceName}" env ${r}: ${i}`,{cause:n})}}function c(r,e,s){return Object.fromEntries(Object.entries(r||{}).filter(([n,i])=>e&&!e.has(n)?!1:!(s&&typeof i=="string"&&findDatabasePlaceholders(i).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,s,n){const i=n.stateDirByService?.get(s),t=n.databaseBindingsByService?n.databaseBindingsByService.get(s):i?n.databaseBindingsReader?.(i):null;if(!t)throw new Error(`Cannot resolve ${e}: service "${s}" has no database binding in this context`);switch(r){case"dbUrl":return t.url;case"dbHost":return t.host;case"dbPort":return String(t.port);case"dbName":return t.database;case"dbUser":return t.user;case"dbPassword":return t.password;case"dbSsl":return D(t)}}function D(r){const e=r.sslMode,s=l(e||"").trim();return s&&!B(s)?s: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())}function l(r){const e=Reflect.apply(String,void 0,[r]);if(typeof e!="string")throw new Error("String conversion returned an invalid result");return e}
@@ -1,2 +1,2 @@
1
- export { buildExecutionEnv, buildTaskExecutionEnv, buildTemplateContext, buildTemplateExecutionEnv, finalizeSerializable, finalizeString, findDatabasePlaceholders, resolveExecutionEnvValues, resolveTemplateString, type BuildExecutionEnvironmentOptions, type DatabasePlaceholder, type DatabaseTemplateBindings, type DatabaseTemplateBindingsReader, type ExecutionEnvironmentConfig, type ExecutionEnvironmentLease, type ExecutionEnvironmentLocalConfig, type ExecutionEnvironmentTestkitConfig, type ExecutionTemplateContextValues, type RuntimeUrlMapping, type TemplateResolutionContext, } from "./environment.js";
1
+ export { buildExecutionEnv, buildTaskExecutionEnv, buildTemplateContext, buildTemplateExecutionEnv, finalizeSerializable, finalizeString, findDatabasePlaceholders, resolveExecutionEnvValues, resolveTemplateString, type BuildExecutionEnvironmentOptions, type DatabasePlaceholder, type DatabaseTemplateBindings, type DatabaseTemplateBindingsReader, type ExecutionEnvironmentConfig, type ExecutionEnvironmentLease, type ExecutionEnvironmentLocalConfig, type ExecutionEnvironmentTestkitConfig, type ExecutionTemplateContextValues, type TemplateResolutionContext, } from "./environment.js";
2
2
  export { collectConfiguredInputs, configuredCommandInvocation, parseModuleSpecifier, resolveConfiguredCwd, resolveConfiguredPath, type ConfiguredCommandInvocation, type ConfiguredStepReporter, type ConfiguredStepServiceConfig, } from "./run.js";