@gfdelarue/testkit 0.3.7 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/README.md +125 -0
  2. package/dist/app/bugs/areas.d.ts +38 -0
  3. package/dist/app/bugs/areas.js +1 -0
  4. package/dist/app/bugs/assessment.d.ts +9 -0
  5. package/dist/app/bugs/assessment.js +1 -0
  6. package/dist/app/bugs/categories.d.ts +41 -0
  7. package/dist/app/bugs/categories.js +1 -0
  8. package/dist/app/bugs/ci-report.d.ts +21 -0
  9. package/dist/app/bugs/ci-report.js +2 -0
  10. package/dist/app/bugs/ci-summary.d.ts +13 -0
  11. package/dist/app/bugs/ci-summary.js +5 -0
  12. package/dist/app/bugs/draft.d.ts +70 -0
  13. package/dist/app/bugs/draft.js +4 -0
  14. package/dist/app/bugs/facts.d.ts +48 -0
  15. package/dist/app/bugs/facts.js +2 -0
  16. package/dist/app/bugs/file.d.ts +45 -0
  17. package/dist/app/bugs/file.js +1 -0
  18. package/dist/app/bugs/gate.d.ts +22 -0
  19. package/dist/app/bugs/gate.js +1 -0
  20. package/dist/app/bugs/guide.d.ts +20 -0
  21. package/dist/app/bugs/guide.js +8 -0
  22. package/dist/app/bugs/issue.d.ts +18 -0
  23. package/dist/app/bugs/issue.js +6 -0
  24. package/dist/app/bugs/known-failures.d.ts +49 -0
  25. package/dist/app/bugs/known-failures.js +1 -0
  26. package/dist/app/bugs/record.d.ts +14 -0
  27. package/dist/app/bugs/record.js +2 -0
  28. package/dist/app/bugs/run-reading.d.ts +51 -0
  29. package/dist/app/bugs/run-reading.js +1 -0
  30. package/dist/app/bugs/store.d.ts +34 -0
  31. package/dist/app/bugs/store.js +2 -0
  32. package/dist/app/campaigns/brief.d.ts +7 -0
  33. package/dist/app/campaigns/brief.js +10 -0
  34. package/dist/app/campaigns/campaign.d.ts +14 -0
  35. package/dist/app/campaigns/campaign.js +1 -0
  36. package/dist/app/campaigns/index.d.ts +38 -0
  37. package/dist/app/campaigns/index.js +2 -0
  38. package/dist/app/campaigns/store.d.ts +5 -0
  39. package/dist/app/campaigns/store.js +1 -0
  40. package/dist/app/campaigns/summary.d.ts +13 -0
  41. package/dist/app/campaigns/summary.js +8 -0
  42. package/dist/app/run/engine-execution.d.ts +1 -0
  43. package/dist/app/run/engine-execution.js +1 -1
  44. package/dist/app/run/failure-details.d.ts +1 -0
  45. package/dist/app/run/failure-details.js +1 -1
  46. package/dist/app/tracker/index.d.ts +8 -0
  47. package/dist/app/tracker/index.js +1 -0
  48. package/dist/app/tracker/remote.d.ts +13 -0
  49. package/dist/app/tracker/remote.js +1 -0
  50. package/dist/app/tracker/token.d.ts +7 -0
  51. package/dist/app/tracker/token.js +5 -0
  52. package/dist/cli/command-catalog.d.ts +21 -122
  53. package/dist/cli/command-catalog.js +1 -1
  54. package/dist/cli/command-registry.d.ts +1 -0
  55. package/dist/cli/command-registry.js +1 -1
  56. package/dist/cli/commands/bug/file.d.ts +16 -0
  57. package/dist/cli/commands/bug/file.js +2 -0
  58. package/dist/cli/commands/bug/guide.d.ts +5 -0
  59. package/dist/cli/commands/bug/guide.js +1 -0
  60. package/dist/cli/commands/bug/record.d.ts +9 -0
  61. package/dist/cli/commands/bug/record.js +2 -0
  62. package/dist/cli/commands/campaign/finish.d.ts +10 -0
  63. package/dist/cli/commands/campaign/finish.js +1 -0
  64. package/dist/cli/commands/campaign/start.d.ts +7 -0
  65. package/dist/cli/commands/campaign/start.js +1 -0
  66. package/dist/cli/commands/campaign/status.d.ts +13 -0
  67. package/dist/cli/commands/campaign/status.js +2 -0
  68. package/dist/cli/commands/ci/report.d.ts +15 -0
  69. package/dist/cli/commands/ci/report.js +2 -0
  70. package/dist/cli/commands/run.d.ts +1 -1
  71. package/dist/cli/commands/run.js +2 -1
  72. package/dist/cli/entrypoint.js +1 -1
  73. package/dist/cli/framework/execute.d.ts +1 -0
  74. package/dist/cli/framework/execute.js +6 -6
  75. package/dist/cli/index.js +1 -1
  76. package/dist/cli/renderers/bug/text.d.ts +8 -0
  77. package/dist/cli/renderers/bug/text.js +1 -0
  78. package/dist/cli/renderers/campaign/text.d.ts +2 -0
  79. package/dist/cli/renderers/campaign/text.js +1 -0
  80. package/dist/cli/renderers/ci/text.d.ts +2 -0
  81. package/dist/cli/renderers/ci/text.js +1 -0
  82. package/dist/cli/version-drift.js +7 -7
  83. package/dist/config/authoring/index.d.ts +10 -5
  84. package/dist/config/loader/bugs.d.ts +15 -0
  85. package/dist/config/loader/bugs.js +1 -0
  86. package/dist/config/loader/index.d.ts +0 -1
  87. package/dist/config/loader/index.js +1 -1
  88. package/dist/engines/adapters/execution.d.ts +4 -0
  89. package/dist/engines/adapters/execution.js +1 -1
  90. package/dist/engines/k6/adapter/executor.js +1 -1
  91. package/dist/engines/k6/runtime/checks.js +1 -1
  92. package/dist/engines/k6/runtime/http-assertions.d.ts +2 -2
  93. package/dist/engines/k6/runtime/http-assertions.js +1 -1
  94. package/dist/engines/k6/runtime/types.d.ts +2 -0
  95. package/dist/engines/node-vitest/adapter/evidence.js +2 -2
  96. package/dist/engines/node-vitest/adapter/failure-details.d.ts +1 -0
  97. package/dist/engines/node-vitest/adapter/failure-details.js +2 -2
  98. package/dist/engines/playwright/adapter/adapter.js +1 -1
  99. package/dist/engines/playwright/adapter/attachments.d.ts +1 -0
  100. package/dist/engines/playwright/adapter/config.js +3 -3
  101. package/dist/engines/playwright/adapter/evidence.js +1 -1
  102. package/dist/engines/playwright/adapter/reporter.js +1 -1
  103. package/dist/evidence/bundles.d.ts +2 -3
  104. package/dist/evidence/bundles.js +1 -1
  105. package/dist/evidence/contracts.d.ts +0 -8
  106. package/dist/evidence/index.d.ts +1 -1
  107. package/dist/evidence/index.js +1 -1
  108. package/dist/kernel/redaction.d.ts +1 -0
  109. package/dist/kernel/redaction.js +1 -1
  110. package/dist/process/execa-bundle.json +1 -1
  111. package/dist/process/files/json-file.d.ts +1 -0
  112. package/dist/process/files/json-file.js +1 -1
  113. package/dist/repo/history.d.ts +17 -0
  114. package/dist/repo/history.js +4 -0
  115. package/dist/repo/tree-listing.d.ts +10 -0
  116. package/dist/repo/tree-listing.js +1 -0
  117. package/dist/tracker/gitea.d.ts +14 -0
  118. package/dist/tracker/gitea.js +1 -0
  119. package/dist/tracker/github.d.ts +14 -0
  120. package/dist/tracker/github.js +1 -0
  121. package/dist/tracker/http.d.ts +33 -0
  122. package/dist/tracker/http.js +1 -0
  123. package/dist/tracker/index.d.ts +4 -0
  124. package/dist/tracker/index.js +1 -0
  125. package/dist/tracker/redaction.d.ts +4 -0
  126. package/dist/tracker/redaction.js +2 -0
  127. package/dist/tracker/requests.d.ts +17 -0
  128. package/dist/tracker/requests.js +1 -0
  129. package/dist/tracker/responses.d.ts +24 -0
  130. package/dist/tracker/responses.js +1 -0
  131. package/dist/tracker/types.d.ts +51 -0
  132. package/docs/bug-records.md +303 -0
  133. package/package.json +4 -10
  134. package/testkit-config-schema.md +50 -23
  135. package/dist/app/cloud/campaigns.d.ts +0 -6
  136. package/dist/app/cloud/campaigns.js +0 -1
  137. package/dist/app/cloud/control-actions.d.ts +0 -12
  138. package/dist/app/cloud/control-actions.js +0 -2
  139. package/dist/app/cloud/control-links.d.ts +0 -5
  140. package/dist/app/cloud/control-links.js +0 -1
  141. package/dist/app/cloud/control-read.d.ts +0 -24
  142. package/dist/app/cloud/control-read.js +0 -1
  143. package/dist/app/cloud/control-resources.d.ts +0 -3
  144. package/dist/app/cloud/control-resources.js +0 -1
  145. package/dist/app/cloud/control.d.ts +0 -34
  146. package/dist/app/cloud/control.js +0 -5
  147. package/dist/app/cloud/credentials.d.ts +0 -11
  148. package/dist/app/cloud/credentials.js +0 -2
  149. package/dist/app/cloud/index.d.ts +0 -22
  150. package/dist/app/cloud/index.js +0 -2
  151. package/dist/app/cloud/login.d.ts +0 -63
  152. package/dist/app/cloud/login.js +0 -2
  153. package/dist/app/cloud/migration/archive.d.ts +0 -13
  154. package/dist/app/cloud/migration/archive.js +0 -1
  155. package/dist/app/cloud/migration/commands.d.ts +0 -8
  156. package/dist/app/cloud/migration/commands.js +0 -1
  157. package/dist/app/cloud/migration/evidence.d.ts +0 -13
  158. package/dist/app/cloud/migration/evidence.js +0 -2
  159. package/dist/app/cloud/migration/files.d.ts +0 -16
  160. package/dist/app/cloud/migration/files.js +0 -2
  161. package/dist/app/cloud/migration/index.d.ts +0 -6
  162. package/dist/app/cloud/migration/index.js +0 -1
  163. package/dist/app/cloud/migration/journal.d.ts +0 -64
  164. package/dist/app/cloud/migration/journal.js +0 -1
  165. package/dist/app/cloud/migration/mapping-dispositions.d.ts +0 -3
  166. package/dist/app/cloud/migration/mapping-dispositions.js +0 -1
  167. package/dist/app/cloud/migration/mapping.d.ts +0 -11
  168. package/dist/app/cloud/migration/mapping.js +0 -2
  169. package/dist/app/cloud/migration/model.d.ts +0 -175
  170. package/dist/app/cloud/migration/plan.d.ts +0 -7
  171. package/dist/app/cloud/migration/plan.js +0 -1
  172. package/dist/app/cloud/migration/progress.d.ts +0 -19
  173. package/dist/app/cloud/migration/progress.js +0 -1
  174. package/dist/app/cloud/migration/reconcile.d.ts +0 -22
  175. package/dist/app/cloud/migration/reconcile.js +0 -1
  176. package/dist/app/cloud/migration/reconciliation-spool.d.ts +0 -4
  177. package/dist/app/cloud/migration/reconciliation-spool.js +0 -1
  178. package/dist/app/cloud/migration/state.d.ts +0 -15
  179. package/dist/app/cloud/migration/state.js +0 -1
  180. package/dist/app/cloud/migration/transfer.d.ts +0 -6
  181. package/dist/app/cloud/migration/transfer.js +0 -1
  182. package/dist/cli/cloud-apply-help.d.ts +0 -1
  183. package/dist/cli/cloud-apply-help.js +0 -56
  184. package/dist/cli/cloud-selection.d.ts +0 -2
  185. package/dist/cli/cloud-selection.js +0 -3
  186. package/dist/cli/commands/cloud/apply.d.ts +0 -5
  187. package/dist/cli/commands/cloud/apply.js +0 -1
  188. package/dist/cli/commands/cloud/campaign/create.d.ts +0 -5
  189. package/dist/cli/commands/cloud/campaign/create.js +0 -1
  190. package/dist/cli/commands/cloud/download.d.ts +0 -5
  191. package/dist/cli/commands/cloud/download.js +0 -1
  192. package/dist/cli/commands/cloud/list.d.ts +0 -5
  193. package/dist/cli/commands/cloud/list.js +0 -1
  194. package/dist/cli/commands/cloud/login.d.ts +0 -13
  195. package/dist/cli/commands/cloud/login.js +0 -2
  196. package/dist/cli/commands/cloud/logout.d.ts +0 -7
  197. package/dist/cli/commands/cloud/logout.js +0 -1
  198. package/dist/cli/commands/cloud/migrate/apply.d.ts +0 -2
  199. package/dist/cli/commands/cloud/migrate/apply.js +0 -1
  200. package/dist/cli/commands/cloud/migrate/plan.d.ts +0 -7
  201. package/dist/cli/commands/cloud/migrate/plan.js +0 -1
  202. package/dist/cli/commands/cloud/migrate/progress.d.ts +0 -2
  203. package/dist/cli/commands/cloud/migrate/progress.js +0 -2
  204. package/dist/cli/commands/cloud/migrate/reconcile.d.ts +0 -2
  205. package/dist/cli/commands/cloud/migrate/reconcile.js +0 -1
  206. package/dist/cli/commands/cloud/policy/preview.d.ts +0 -5
  207. package/dist/cli/commands/cloud/policy/preview.js +0 -1
  208. package/dist/cli/commands/cloud/project/create.d.ts +0 -5
  209. package/dist/cli/commands/cloud/project/create.js +0 -1
  210. package/dist/cli/commands/cloud/publish.d.ts +0 -7
  211. package/dist/cli/commands/cloud/publish.js +0 -1
  212. package/dist/cli/commands/cloud/session/revoke.d.ts +0 -8
  213. package/dist/cli/commands/cloud/session/revoke.js +0 -1
  214. package/dist/cli/commands/cloud/show.d.ts +0 -5
  215. package/dist/cli/commands/cloud/show.js +0 -1
  216. package/dist/cli/commands/cloud/snapshot.d.ts +0 -5
  217. package/dist/cli/commands/cloud/snapshot.js +0 -1
  218. package/dist/cli/commands/cloud/status.d.ts +0 -33
  219. package/dist/cli/commands/cloud/status.js +0 -1
  220. package/dist/cloud/account.d.ts +0 -34
  221. package/dist/cloud/account.js +0 -1
  222. package/dist/cloud/archive-contract.d.ts +0 -1
  223. package/dist/cloud/archive-contract.js +0 -1
  224. package/dist/cloud/archive.d.ts +0 -12
  225. package/dist/cloud/archive.js +0 -1
  226. package/dist/cloud/campaigns.d.ts +0 -200
  227. package/dist/cloud/campaigns.js +0 -2
  228. package/dist/cloud/client.d.ts +0 -68
  229. package/dist/cloud/client.js +0 -4
  230. package/dist/cloud/download.d.ts +0 -5
  231. package/dist/cloud/download.js +0 -1
  232. package/dist/cloud/historical-contract.d.ts +0 -224
  233. package/dist/cloud/historical-contract.js +0 -1
  234. package/dist/cloud/historical-json.d.ts +0 -16
  235. package/dist/cloud/historical-json.js +0 -1
  236. package/dist/cloud/historical.d.ts +0 -69
  237. package/dist/cloud/historical.js +0 -1
  238. package/dist/cloud/pagination.d.ts +0 -5
  239. package/dist/cloud/pagination.js +0 -1
  240. package/dist/cloud/transport.d.ts +0 -18
  241. package/dist/cloud/transport.js +0 -1
  242. package/dist/config/loader/cloud.d.ts +0 -2
  243. package/dist/config/loader/cloud.js +0 -1
  244. package/docs/cloud-control.md +0 -108
  245. package/docs/testkit-cloud-boundary.md +0 -628
  246. package/node_modules/buffer-crc32/LICENSE +0 -19
  247. package/node_modules/buffer-crc32/README.md +0 -55
  248. package/node_modules/buffer-crc32/dist/index.cjs +0 -306
  249. package/node_modules/buffer-crc32/dist/index.d.cts +0 -35
  250. package/node_modules/buffer-crc32/dist/index.d.mts +0 -35
  251. package/node_modules/buffer-crc32/dist/index.mjs +0 -304
  252. package/node_modules/buffer-crc32/index.d.ts +0 -35
  253. package/node_modules/buffer-crc32/package.json +0 -54
  254. package/node_modules/yazl/LICENSE +0 -21
  255. package/node_modules/yazl/README.md +0 -457
  256. package/node_modules/yazl/index.js +0 -826
  257. package/node_modules/yazl/package.json +0 -34
  258. /package/dist/{app/cloud/migration/model.js → tracker/types.js} +0 -0
@@ -1 +1 @@
1
- import{isEngineTaskDeadlineExceededReason as d}from"../api/index.js";import"../../domain/test-outcomes.js";export async function executeEngineTasks(e,t,n,i,o){const r=[];for(const a of t){e.observer?.taskStarted(a.task);const s=Date.now(),c=taskBeforeStartOutcome(a.task,e.signal,s,n);c!==null?r.push(c):a.preparationFailure!==null?r.push(terminalOutcome(a.task.id,"failed",s,emptyEvidence(),a.preparationFailure)):r.push(await i(a,s))}for(const[a,s]of r.entries())e.observer?.evidenceRecorded(t[a].task.id,s.evidence),e.observer?.taskFinished(s);return{evidence:{artifacts:r.flatMap(a=>a.evidence.artifacts),logs:r.flatMap(a=>a.evidence.logs),metrics:r.flatMap(a=>a.evidence.metrics),native:o},invocationId:e.invocationId,outcomes:r}}export function taskBeforeStartOutcome(e,t,n,i){return e.deadlineMs<=n||d(t.reason)?terminalOutcome(e.id,"failed",n,emptyEvidence(),taskTimeoutFailure(e.timeoutMs,i)):t.aborted?terminalOutcome(e.id,"cancelled",n,emptyEvidence(),{code:`${i.codePrefix}_CANCELLED`,message:`${i.label} task was cancelled before its process started`}):null}export function taskTimeoutFailure(e,t){return{code:`${t.codePrefix}_TASK_TIMEOUT`,details:{timeoutMs:e},message:`${t.label} task exceeded its ${e.toString()}ms timeout`}}export function managedProcessFailure(e,t,n=null){return e.terminationError===null?null:{classification:"infra",code:`${t.codePrefix}_PROCESS_TERMINATION_INCOMPLETE`,details:{cancelled:e.cancelled,...n===null?{}:{evidenceCollectionFailure:errorDetails(n)},outputLimitExceeded:e.outputLimitExceeded,timedOut:e.timedOut},message:`${t.label} managed-process termination was incomplete: ${e.terminationError.message}`,...e.terminationError.stack===void 0?{}:{stack:e.terminationError.stack}}}export function classifyManagedProcessResult(e,t,n,i){const o=a=>({failure:a,status:"failed"}),r=managedProcessFailure(e,i);return r!==null?o(r):e.timedOut||n?o(taskTimeoutFailure(t,i)):e.cancelled?{failure:{code:`${i.codePrefix}_CANCELLED`,message:`${i.label} task was cancelled`},status:"cancelled"}:e.outputLimitExceeded?o({code:`${i.codePrefix}_OUTPUT_LIMIT`,message:`${i.label} task exceeded the bounded stdout/stderr capture limit`}):e.error!==null?o({...failureFromError(e.error,`${i.codePrefix}_PROCESS_START_FAILED`),classification:"infra"}):null}export function processExitFailure(e,t,n=null,i=t.label){return{failure:{code:`${t.codePrefix}_PROCESS_FAILED`,details:{...e.exitCode===null?{}:{exitCode:e.exitCode},...e.exitSignal===null?{}:{exitSignal:e.exitSignal}},message:n??(e.exitCode===null?`${i} process terminated by ${e.exitSignal??"an unknown signal"}`:`${i} process exited with code ${e.exitCode.toString()}`)},status:"failed"}}export function firstOutputLine(e){const t=e.split(/\r?\n/u).map(n=>n.trim()).find(Boolean);return t===void 0?null:t.slice(0,4e3)}export function terminalOutcome(e,t,n,i,o){const r=Date.now();return{durationMs:Math.max(0,r-n),evidence:i,failure:o,finishedAtMs:r,startedAtMs:n,status:t,taskId:e}}export function processOutputEvidence(e){return{artifacts:[],logs:[...e.stdout.length===0?[]:[{stream:"stdout",text:e.stdout}],...e.stderr.length===0?[]:[{stream:"stderr",text:e.stderr}]],metrics:[]}}export function emptyEvidence(){return{artifacts:[],logs:[],metrics:[]}}export function failureFromError(e,t){const n=toError(e);return{code:t,message:n.message,...n.stack===void 0?{}:{stack:n.stack}}}export function errorDetails(e){return{message:e.message,name:e.name,...e.stack===void 0?{}:{stack:e.stack}}}export function toError(e){return e instanceof Error?e:new Error(String(e))}
1
+ import{isEngineTaskDeadlineExceededReason as d}from"../api/index.js";import"../../domain/test-outcomes.js";export async function executeEngineTasks(e,t,n,i,o){const a=[];for(const r of t){e.observer?.taskStarted(r.task);const s=Date.now(),c=taskBeforeStartOutcome(r.task,e.signal,s,n);c!==null?a.push(c):r.preparationFailure!==null?a.push(terminalOutcome(r.task.id,"failed",s,emptyEvidence(),r.preparationFailure)):a.push(await i(r,s))}for(const[r,s]of a.entries())e.observer?.evidenceRecorded(t[r].task.id,s.evidence),e.observer?.taskFinished(s);return{evidence:{artifacts:a.flatMap(r=>r.evidence.artifacts),logs:a.flatMap(r=>r.evidence.logs),metrics:a.flatMap(r=>r.evidence.metrics),native:o},invocationId:e.invocationId,outcomes:a}}export function taskBeforeStartOutcome(e,t,n,i){return e.deadlineMs<=n||d(t.reason)?terminalOutcome(e.id,"failed",n,emptyEvidence(),taskTimeoutFailure(e.timeoutMs,i)):t.aborted?terminalOutcome(e.id,"cancelled",n,emptyEvidence(),{code:`${i.codePrefix}_CANCELLED`,message:`${i.label} task was cancelled before its process started`}):null}export function taskTimeoutFailure(e,t){return{code:`${t.codePrefix}_TASK_TIMEOUT`,details:{timeoutMs:e},message:`${t.label} task exceeded its ${e.toString()}ms timeout`}}export function managedProcessFailure(e,t,n=null){return e.terminationError===null?null:{classification:"infra",code:`${t.codePrefix}_PROCESS_TERMINATION_INCOMPLETE`,details:{cancelled:e.cancelled,...n===null?{}:{evidenceCollectionFailure:errorDetails(n)},outputLimitExceeded:e.outputLimitExceeded,timedOut:e.timedOut},message:`${t.label} managed-process termination was incomplete: ${e.terminationError.message}`,...e.terminationError.stack===void 0?{}:{stack:e.terminationError.stack}}}export function classifyManagedProcessResult(e,t,n,i){const o=r=>({failure:r,status:"failed"}),a=managedProcessFailure(e,i);return a!==null?o(a):e.timedOut||n?o(taskTimeoutFailure(t,i)):e.cancelled?{failure:{code:`${i.codePrefix}_CANCELLED`,message:`${i.label} task was cancelled`},status:"cancelled"}:e.outputLimitExceeded?o({code:`${i.codePrefix}_OUTPUT_LIMIT`,message:`${i.label} task exceeded the bounded stdout/stderr capture limit`}):e.error!==null?o({...failureFromError(e.error,`${i.codePrefix}_PROCESS_START_FAILED`),classification:"infra"}):null}export function processExitDetails(e){return{...e.exitCode===null?{}:{exitCode:e.exitCode},...e.exitSignal===null?{}:{exitSignal:e.exitSignal}}}export function processExitFailure(e,t,n=null,i=t.label){return{failure:{code:`${t.codePrefix}_PROCESS_FAILED`,details:processExitDetails(e),message:n??(e.exitCode===null?`${i} process terminated by ${e.exitSignal??"an unknown signal"}`:`${i} process exited with code ${e.exitCode.toString()}`)},status:"failed"}}export function firstOutputLine(e){const t=e.split(/\r?\n/u).map(n=>n.trim()).find(Boolean);return t===void 0?null:t.slice(0,4e3)}export function terminalOutcome(e,t,n,i,o){const a=Date.now();return{durationMs:Math.max(0,a-n),evidence:i,failure:o,finishedAtMs:a,startedAtMs:n,status:t,taskId:e}}export function processOutputEvidence(e){return{artifacts:[],logs:[...e.stdout.length===0?[]:[{stream:"stdout",text:e.stdout}],...e.stderr.length===0?[]:[{stream:"stderr",text:e.stderr}]],metrics:[]}}export function emptyEvidence(){return{artifacts:[],logs:[],metrics:[]}}export function failureFromError(e,t){const n=toError(e);return{code:t,message:n.message,...n.stack===void 0?{}:{stack:n.stack}}}export function errorDetails(e){return{message:e.message,name:e.name,...e.stack===void 0?{}:{stack:e.stack}}}export function toError(e){return e instanceof Error?e:new Error(String(e))}
@@ -1 +1 @@
1
- import{makeTempDirAsync as k}from"../../../process/files/temp-dir.js";import{classifyManagedProcessResult as O,terminalOutcome as g,emptyEvidence as b,failureFromError as E,processExitFailure as M,toError as w,executeEngineTasks as P,managedProcessFailure as F}from"../../adapters/execution.js";import{requireRootDirectory as U,requireAbsoluteWorkspacePath as K,requireConsumerFile as L,safeSegment as h}from"../../adapters/workspace.js";import f from"node:fs/promises";import T from"node:path";import{isEngineTaskDeadlineExceededReason as C,resolveEngineTaskInputs as V}from"../../api/index.js";import{createInvocationProcessEnvironment as j,runManagedProcess as $}from"../../../process/index.js";import{bundleK6Suite as Y,K6ConsumerCompilationError as B}from"./bundle.js";import{collectK6Evidence as z}from"./evidence.js";import{assertK6SpawnEnvironmentBudget as H,createK6RuntimeInputDocument as G,K6_RUNTIME_INPUT_FILE_ENV as J,K6_RUNTIME_INPUT_SHA256_ENV as W,mergeK6Environment as X,selectK6CustomEnvironment as R,verifyOwnedK6RuntimeInput as Q,writeOwnedK6RuntimeInput as Z}from"./input.js";import{K6_RUNTIME_INVOCATION_ID_ENV as q,K6_RUNTIME_TASK_ID_ENV as ee}from"../../api/k6-runtime-input.js";import{collectK6NativeSummaryArtifacts as te,K6_NATIVE_SUMMARY_DIRECTORY as re}from"./native-summary.js";import{engineEvidenceIdentity as ne}from"../../adapters/evidence-identity.js";const I={codePrefix:"K6",label:"k6"};class N extends Error{constructor(t){const r=w(t);super(r.message,{cause:r}),this.name="K6ConsumerPreparationError"}}export const k6ExecutorDependencies={removeOwnedDirectory:e=>f.rm(e,{force:!0,recursive:!0}),writeRuntimeInput:Z};export async function executeK6Engine(e,t,r,n=k6ExecutorDependencies){const i=await ie(e,t,n,r);return P(e,i.tasks,I,(a,s)=>oe(e,i,a,s),fe(e,i.installation))}async function ie(e,t,r,n){const i=e.profile,a=t.executable,s=await U(e.workspace.rootDirectory);K(e.workspace.temporaryDirectory,"temporary directory"),K(e.workspace.artifactDirectory,"artifact directory"),await f.mkdir(e.workspace.temporaryDirectory,{recursive:!0}),await f.mkdir(e.workspace.artifactDirectory,{recursive:!0});const o=await k(`k6-${h(e.invocationId)}-`,{parentDir:e.workspace.temporaryDirectory});n.defer(()=>r.removeOwnedDirectory(o));const c=await j(o),d=[];for(const[u,l]of e.tasks.entries()){const m=V(e,l),D=await k(`${u.toString().padStart(3,"0")}-${h(l.id)}-`,{parentDir:e.workspace.artifactDirectory}),p=T.join(o,`${u.toString().padStart(3,"0")}-${h(l.id)}`),S=T.join(p,"suite.js"),y=T.join(p,re);try{await f.mkdir(p,{mode:448,recursive:!1}),await f.mkdir(y,{mode:448,recursive:!1});const _=await A(()=>L(s,l.file,"k6 task file"));await A(()=>{de(m.supportModules),me(e,i,l.serviceName,m),R(m.environment)});const x=await r.writeRuntimeInput(p,G(e,l,m));await Y({configuration:m.configuration,entrypoints:t.authoringEntrypoints,nativeSummaryDirectory:y,outputFile:S,rootDirectory:s,signal:e.signal,sourceFile:_}),d.push({artifactDirectory:D,inputs:m,task:l,preparationFailure:null,prepared:{bundleFile:S,nativeSummaryDirectory:y,runtimeInput:x}})}catch(_){d.push({artifactDirectory:D,inputs:m,task:l,preparationFailure:pe(_),prepared:null})}}return Object.freeze({executable:a,installation:t,invocationEnvironment:c.environment,rootDirectory:s,tasks:Object.freeze(d)})}async function oe(e,t,r,n){const i=Math.max(0,r.task.deadlineMs-n);try{await Q(r.prepared.runtimeInput)}catch(u){return g(r.task.id,"failed",n,b(),{...E(u,"K6_INPUT_INVALID"),classification:"infra"})}const a=`${r.prepared.bundleFile}.summary.json`,s=le(e,t,r),o=await $({args:["run","--address","127.0.0.1:0","--no-color","--quiet","--summary-export",a,r.prepared.bundleFile],cwd:t.rootDirectory,displayLabel:"k6",env:s,executable:t.executable,signal:e.signal,timeoutMs:i});let c;try{c=await z({artifactDirectory:r.artifactDirectory,stderr:o.stderr,stdout:o.stdout,summaryFile:a});const u=await te(r.prepared.nativeSummaryDirectory,r.artifactDirectory);u.length>0&&(c={...c,evidence:{...c.evidence,artifacts:[...c.evidence.artifacts,...u]}})}catch(u){return g(r.task.id,"failed",n,{artifacts:[],logs:[...o.stdout.length>0?[{stream:"stdout",text:o.stdout}]:[],...o.stderr.length>0?[{stream:"stderr",text:o.stderr}]:[]],metrics:[]},k6EvidenceCollectionFailure(o,u))}const d=classifyK6ProcessResult(o,c.summary,c.summaryError,r.task.timeoutMs,C(e.signal.reason));return g(r.task.id,d.status,n,c.evidence,d.failure)}export function classifyK6ProcessResult(e,t,r,n,i){const a=O(e,n,i,I);if(a!==null)return a;const s=ce(e.stderr),o=ae(e.stderr);if(o!==null)return{failure:{code:"K6_SUMMARY_OUTPUT_INVALID",message:o,stack:e.stderr},status:"failed"};if(s!==null)return{failure:{code:"K6_RUNTIME_EXCEPTION",message:s,stack:e.stderr},status:"failed"};const c=ue(t);return c.length>0?{failure:{code:"K6_THRESHOLDS_FAILED",details:{thresholds:c},message:`k6 thresholds failed: ${c.join(", ")}`},status:"failed"}:e.exitCode!==0?M(e,I):r!==null?{failure:{...E(r,"K6_SUMMARY_INVALID"),classification:"infra"},status:"failed"}:{failure:null,status:"passed"}}export function k6EvidenceCollectionFailure(e,t){const r=w(t);return F(e,I,r)??{...E(r,"K6_EVIDENCE_FAILED"),classification:"infra"}}function ae(e){return e.match(/TESTKIT_K6_SUMMARY_OUTPUT_INVALID:\s*([^\n"]+)/u)?.[1]?.trim()??null}function ce(e){for(const t of e.split(/\r?\n/u)){if(!t.includes("level=error")||!t.includes("source=stacktrace")&&!t.includes('hint="script exception"'))continue;const r=t.match(/\bmsg="((?:\\.|[^"\\])*)"/u)?.[1];if(r===void 0)continue;const n=se(r).split(/\r?\n/u)[0]?.trim();if(!n)continue;return n.match(/^(?:Error|GoError|TypeError|ReferenceError|RangeError):\s*(.+)$/u)?.[1]?.trim()??n}return null}function se(e){try{const t=JSON.parse(`"${e}"`);if(typeof t=="string")return t}catch{}return e}function ue(e){if(e===null||!v(e.metrics))return[];const t=[];for(const[r,n]of Object.entries(e.metrics))if(!(!v(n)||!v(n.thresholds)))for(const[i,a]of Object.entries(n.thresholds))a===!0&&t.push(`${r}(${i})`);return t.sort()}function le(e,t,r){const n=r.task,i=X(t.invocationEnvironment,R(r.inputs.environment),{K6_NO_USAGE_REPORT:"true",TESTKIT_ACTIVE:"1",[J]:r.prepared.runtimeInput.file,[W]:r.prepared.runtimeInput.sha256,TESTKIT_INTERNAL_FILE_DEADLINE_MS:String(n.deadlineMs),TESTKIT_INTERNAL_FILE_TIMEOUT_SECONDS:String(Math.max(1,Math.ceil(n.timeoutMs/1e3))),[q]:e.invocationId,TESTKIT_LEASE_DIR:n.orchestration.lease.directory,TESTKIT_LEASE_ID:n.orchestration.lease.id,TESTKIT_MODE:n.orchestration.mode,...n.orchestration.runtimeId===void 0?{}:{TESTKIT_RUNTIME_ID:n.orchestration.runtimeId},TESTKIT_TEST_FILE:n.file,[ee]:n.id});return e.profile.id==="k6-http"&&Object.defineProperty(i,"BASE_URL",{configurable:!0,enumerable:!0,value:e.target.endpoints[n.serviceName],writable:!0}),H(i),Object.freeze(i)}function me(e,t,r,n){if(t.id==="k6-http"){const a=e.target.endpoints[r];if(typeof a!="string"||a.trim().length===0)throw new Error(`k6-http requires a published endpoint for service ${r}`);return}const i=n.environment.DATABASE_URL;if(typeof i!="string"||i.trim().length===0)throw new Error("k6-dal requires DATABASE_URL in its resolved task inputs")}function de(e){const t=Object.keys(e);if(t.length!==0)throw new Error(`k6 does not accept support modules (${t.join(", ")}); repository configuration must cross the engine boundary as validated JSON`)}function fe(e,t){return{...ne({engineId:"k6",installationKind:"direct-package",profile:e.profile,target:t.target}),packageName:t.targetPackage.name,packageVersion:t.targetPackage.version,runtimeObservedVersion:t.engineVersion}}function pe(e){return{...E(e,"K6_PREPARE_FAILED"),classification:e instanceof N||e instanceof B?"product":"infra"}}async function A(e){try{return await e()}catch(t){throw new N(t)}}function v(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}
1
+ import{makeTempDirAsync as S}from"../../../process/files/temp-dir.js";import{classifyManagedProcessResult as O,terminalOutcome as y,emptyEvidence as M,failureFromError as I,processExitDetails as R,processExitFailure as P,toError as w,executeEngineTasks as F,managedProcessFailure as U}from"../../adapters/execution.js";import{requireRootDirectory as L,requireAbsoluteWorkspacePath as K,requireConsumerFile as C,safeSegment as T}from"../../adapters/workspace.js";import m from"node:fs/promises";import v from"node:path";import{isEngineTaskDeadlineExceededReason as V,resolveEngineTaskInputs as j}from"../../api/index.js";import{createInvocationProcessEnvironment as $,runManagedProcess as Y}from"../../../process/index.js";import{bundleK6Suite as B,K6ConsumerCompilationError as H}from"./bundle.js";import{collectK6Evidence as z}from"./evidence.js";import{assertK6SpawnEnvironmentBudget as G,createK6RuntimeInputDocument as J,K6_RUNTIME_INPUT_FILE_ENV as W,K6_RUNTIME_INPUT_SHA256_ENV as X,mergeK6Environment as Q,selectK6CustomEnvironment as N,verifyOwnedK6RuntimeInput as Z,writeOwnedK6RuntimeInput as q}from"./input.js";import{K6_RUNTIME_INVOCATION_ID_ENV as ee,K6_RUNTIME_TASK_ID_ENV as te}from"../../api/k6-runtime-input.js";import{collectK6NativeSummaryArtifacts as re,K6_NATIVE_SUMMARY_DIRECTORY as ne}from"./native-summary.js";import{engineEvidenceIdentity as ie}from"../../adapters/evidence-identity.js";const g={codePrefix:"K6",label:"k6"},oe=/^Uncaught testkit suite error during (setup|exec): /u;class A extends Error{constructor(t){const r=w(t);super(r.message,{cause:r}),this.name="K6ConsumerPreparationError"}}export const k6ExecutorDependencies={removeOwnedDirectory:e=>m.rm(e,{force:!0,recursive:!0}),writeRuntimeInput:q};export async function executeK6Engine(e,t,r,n=k6ExecutorDependencies){const i=await ae(e,t,n,r);return F(e,i.tasks,g,(a,u)=>ce(e,i,a,u),Ie(e,i.installation))}async function ae(e,t,r,n){const i=e.profile,a=t.executable,u=await L(e.workspace.rootDirectory);K(e.workspace.temporaryDirectory,"temporary directory"),K(e.workspace.artifactDirectory,"artifact directory"),await m.mkdir(e.workspace.temporaryDirectory,{recursive:!0}),await m.mkdir(e.workspace.artifactDirectory,{recursive:!0});const o=await S(`k6-${T(e.invocationId)}-`,{parentDir:e.workspace.temporaryDirectory});n.defer(()=>r.removeOwnedDirectory(o));const c=await $(o),s=[];for(const[l,d]of e.tasks.entries()){const f=j(e,d),D=await S(`${l.toString().padStart(3,"0")}-${T(d.id)}-`,{parentDir:e.workspace.artifactDirectory}),E=v.join(o,`${l.toString().padStart(3,"0")}-${T(d.id)}`),k=v.join(E,"suite.js"),_=v.join(E,ne);try{await m.mkdir(E,{mode:448,recursive:!1}),await m.mkdir(_,{mode:448,recursive:!1});const h=await x(()=>C(u,d.file,"k6 task file"));await x(()=>{Ee(f.supportModules),pe(e,i,d.serviceName,f),N(f.environment)});const b=await r.writeRuntimeInput(E,J(e,d,f));await B({configuration:f.configuration,entrypoints:t.authoringEntrypoints,nativeSummaryDirectory:_,outputFile:k,rootDirectory:u,signal:e.signal,sourceFile:h}),s.push({artifactDirectory:D,inputs:f,task:d,preparationFailure:null,prepared:{bundleFile:k,nativeSummaryDirectory:_,runtimeInput:b}})}catch(h){s.push({artifactDirectory:D,inputs:f,task:d,preparationFailure:ge(h),prepared:null})}}return Object.freeze({executable:a,installation:t,invocationEnvironment:c.environment,rootDirectory:u,tasks:Object.freeze(s)})}async function ce(e,t,r,n){const i=Math.max(0,r.task.deadlineMs-n);try{await Z(r.prepared.runtimeInput)}catch(l){return y(r.task.id,"failed",n,M(),{...I(l,"K6_INPUT_INVALID"),classification:"infra"})}const a=`${r.prepared.bundleFile}.summary.json`,u=me(e,t,r),o=await Y({args:["run","--address","127.0.0.1:0","--no-color","--quiet","--summary-export",a,r.prepared.bundleFile],cwd:t.rootDirectory,displayLabel:"k6",env:u,executable:t.executable,signal:e.signal,timeoutMs:i});let c;try{c=await z({artifactDirectory:r.artifactDirectory,stderr:o.stderr,stdout:o.stdout,summaryFile:a});const l=await re(r.prepared.nativeSummaryDirectory,r.artifactDirectory);l.length>0&&(c={...c,evidence:{...c.evidence,artifacts:[...c.evidence.artifacts,...l]}})}catch(l){return y(r.task.id,"failed",n,{artifacts:[],logs:[...o.stdout.length>0?[{stream:"stdout",text:o.stdout}]:[],...o.stderr.length>0?[{stream:"stderr",text:o.stderr}]:[]],metrics:[]},k6EvidenceCollectionFailure(o,l))}const s=classifyK6ProcessResult(o,c.summary,c.summaryError,r.task.timeoutMs,V(e.signal.reason));return y(r.task.id,s.status,n,c.evidence,s.failure)}export function classifyK6ProcessResult(e,t,r,n,i){const a=O(e,n,i,g);if(a!==null)return a;const u=ue(e.stderr),o=se(e.stderr);if(o!==null)return{failure:{code:"K6_SUMMARY_OUTPUT_INVALID",message:o,stack:e.stderr},status:"failed"};if(u!==null){const s=oe.exec(u)?.[1];return{failure:{code:"K6_RUNTIME_EXCEPTION",details:{...s===void 0?{}:{phase:s},...R(e),summaryRead:r===null},message:u,stack:e.stderr},status:"failed"}}const c=de(t);if(c.length>0){const s=fe(t);return{failure:{code:"K6_THRESHOLDS_FAILED",details:{...R(e),...s===null?{}:{failedChecks:s},thresholds:c},message:`k6 thresholds failed: ${c.join(", ")}`},status:"failed"}}return e.exitCode!==0?P(e,g):r!==null?{failure:{...I(r,"K6_SUMMARY_INVALID"),classification:"infra"},status:"failed"}:{failure:null,status:"passed"}}export function k6EvidenceCollectionFailure(e,t){const r=w(t);return U(e,g,r)??{...I(r,"K6_EVIDENCE_FAILED"),classification:"infra"}}function se(e){return e.match(/TESTKIT_K6_SUMMARY_OUTPUT_INVALID:\s*([^\n"]+)/u)?.[1]?.trim()??null}function ue(e){for(const t of e.split(/\r?\n/u)){if(!t.includes("level=error")||!t.includes("source=stacktrace")&&!t.includes('hint="script exception"'))continue;const r=t.match(/\bmsg="((?:\\.|[^"\\])*)"/u)?.[1];if(r===void 0)continue;const n=le(r).split(/\r?\n/u)[0]?.trim();if(!n)continue;return n.match(/^(?:Error|GoError|TypeError|ReferenceError|RangeError):\s*(.+)$/u)?.[1]?.trim()??n}return null}function le(e){try{const t=JSON.parse(`"${e}"`);if(typeof t=="string")return t}catch{}return e}function de(e){if(e===null||!p(e.metrics))return[];const t=[];for(const[r,n]of Object.entries(e.metrics))if(!(!p(n)||!p(n.thresholds)))for(const[i,a]of Object.entries(n.thresholds))a===!0&&t.push(`${r}(${i})`);return t.sort()}function fe(e){if(e===null||!p(e.metrics)||!p(e.metrics.checks))return null;const{fails:t}=e.metrics.checks;return typeof t=="number"&&Number.isSafeInteger(t)&&t>=0?t:null}function me(e,t,r){const n=r.task,i=Q(t.invocationEnvironment,N(r.inputs.environment),{K6_NO_USAGE_REPORT:"true",TESTKIT_ACTIVE:"1",[W]:r.prepared.runtimeInput.file,[X]:r.prepared.runtimeInput.sha256,TESTKIT_INTERNAL_FILE_DEADLINE_MS:String(n.deadlineMs),TESTKIT_INTERNAL_FILE_TIMEOUT_SECONDS:String(Math.max(1,Math.ceil(n.timeoutMs/1e3))),[ee]:e.invocationId,TESTKIT_LEASE_DIR:n.orchestration.lease.directory,TESTKIT_LEASE_ID:n.orchestration.lease.id,TESTKIT_MODE:n.orchestration.mode,...n.orchestration.runtimeId===void 0?{}:{TESTKIT_RUNTIME_ID:n.orchestration.runtimeId},TESTKIT_TEST_FILE:n.file,[te]:n.id});return e.profile.id==="k6-http"&&Object.defineProperty(i,"BASE_URL",{configurable:!0,enumerable:!0,value:e.target.endpoints[n.serviceName],writable:!0}),G(i),Object.freeze(i)}function pe(e,t,r,n){if(t.id==="k6-http"){const a=e.target.endpoints[r];if(typeof a!="string"||a.trim().length===0)throw new Error(`k6-http requires a published endpoint for service ${r}`);return}const i=n.environment.DATABASE_URL;if(typeof i!="string"||i.trim().length===0)throw new Error("k6-dal requires DATABASE_URL in its resolved task inputs")}function Ee(e){const t=Object.keys(e);if(t.length!==0)throw new Error(`k6 does not accept support modules (${t.join(", ")}); repository configuration must cross the engine boundary as validated JSON`)}function Ie(e,t){return{...ie({engineId:"k6",installationKind:"direct-package",profile:e.profile,target:t.target}),packageName:t.targetPackage.name,packageVersion:t.targetPackage.version,runtimeObservedVersion:t.engineVersion}}function ge(e){return{...I(e,"K6_PREPARE_FAILED"),classification:e instanceof A||e instanceof H?"product":"infra"}}async function x(e){try{return await e()}catch(t){throw new A(t)}}function p(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}
@@ -1 +1 @@
1
- import{check as S}from"k6";import N from"k6/execution";import{emitArtifact as b}from"./artifacts.js";import{areRowsSorted as z}from"./shared/ordering.js";const c=w();export const failureDetails={begin:startFailureCollection,flush:emitFailureCollectionArtifact};export function evaluateCheck(e,t,n,i=null){const r=s(t,"unnamed check"),u=S(e,{[r]:n});if(!u){const a=i?i():{kind:"runtime-check",key:r,title:r,checkName:r,phase:c.phase};recordFailureDetail(a)}return u}export function json(e,t){if(typeof e.body!="string")throw new TypeError("response body must be text to parse JSON");const n=JSON.parse(e.body);if(t&&!t(n))throw new TypeError("response JSON did not match the expected shape");return n}export function contains(e,t,n){const i=String(t);return e.some(r=>r[i]===n)}export function allMatch(e,t){return e.length>0&&e.every(t)}export function isSorted(e,t,n="asc"){return z(e,t,n)}export function startFailureCollection(e){c.phase=s(e,"exec"),c.detailsByKey=new Map}export function emitFailureCollectionArtifact(){const e=[...c.detailsByKey.values()].sort((t,n)=>t.key.localeCompare(n.key)).map(t=>({...t}));e.length>0&&b("failure-details",{phase:c.phase,failures:e},{kind:"testkit.failure-details",summary:`${String(e.length)} failure detail(s)`}),startFailureCollection(c.phase)}export function recordFailureDetail(e){const t=J(e);if(!t)return;const n=N.vu.metrics.tags.group;typeof n=="string"&&n.length>0&&(t.groupPath=n.replace(/^::/,"").split("::"),t.key=K(t.groupPath,t.key));const i=c.detailsByKey.get(t.key);if(!i){c.detailsByKey.set(t.key,t);return}i.count+=t.count}function w(){return{phase:"exec",detailsByKey:new Map}}function J(e){if(!f(e))return null;const t=s(o(e,"kind"),null),n=s(o(e,"key"),null),i=s(o(e,"title"),null);if(!t||!n||!i)return null;const r={kind:t,key:n,title:i,count:1},u=s(o(e,"checkName"),null);u&&(r.checkName=u);const a=s(o(e,"phase"),null);a&&(r.phase=a);const p=s(o(e,"message"),null);p&&(r.message=p);const m=o(e,"expected");m!==void 0&&(r.expected=m);const y=o(e,"actual");y!==void 0&&(r.actual=y);const h=s(o(e,"traceId"),null);h&&(r.traceId=h);const d=l(o(e,"request"));d&&(r.request=d);const k=l(o(e,"response"));k&&(r.response=k);const g=l(o(e,"location"));g&&(r.location=g);const x=s(o(e,"stack"),null);return x&&(r.stack=x),r}function K(e,t){return e.length===0?t:[...e,t].join(" > ")}function s(e,t){if(typeof e!="string")return t;const n=e.trim();return n.length>0?n:t}function l(e){if(!f(e))return null;try{const t=B(e);if(t===void 0)return null;const n=JSON.parse(t);return O(n)?n:null}catch{return null}}function o(e,t){return Reflect.get(e,t)}function f(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function O(e){return f(e)}function B(e){return JSON.stringify(e)}
1
+ import{check as b}from"k6";import S from"k6/execution";import{emitArtifact as z}from"./artifacts.js";import{areRowsSorted as J}from"./shared/ordering.js";const c=O();export const failureDetails={begin:startFailureCollection,flush:emitFailureCollectionArtifact};export function evaluateCheck(e,t,n,i=null){const r=s(t,"unnamed check"),a=b(e,{[r]:n});if(!a){const l=i?i():{kind:"runtime-check",key:r,title:r,checkName:r,phase:c.phase};recordFailureDetail(l)}return a}export function json(e,t){if(typeof e.body!="string")throw new TypeError("response body must be text to parse JSON");const n=JSON.parse(e.body);if(t&&!t(n))throw new TypeError("response JSON did not match the expected shape");return n}export function contains(e,t,n){const i=String(t);return e.some(r=>r[i]===n)}export function allMatch(e,t){return e.length>0&&e.every(t)}export function isSorted(e,t,n="asc"){return J(e,t,n)}export function startFailureCollection(e){c.phase=s(e,"exec"),c.detailsByName=new Map}export function emitFailureCollectionArtifact(){const e=[...c.detailsByName.values()].sort((t,n)=>t.key.localeCompare(n.key)||t.title.localeCompare(n.title)).map(t=>({...t}));e.length>0&&z("failure-details",{phase:c.phase,failures:e},{kind:"testkit.failure-details",summary:`${String(e.length)} failure detail(s)`}),startFailureCollection(c.phase)}export function recordFailureDetail(e){const t=w(e);if(!t)return;const n=S.vu.metrics.tags.group;typeof n=="string"&&n.length>0&&(t.groupPath=n.replace(/^::/,"").split("::"),t.key=C(t.groupPath,t.key));const i=JSON.stringify([t.key,t.labelled??null,t.title]),r=c.detailsByName.get(i);if(!r){c.detailsByName.set(i,t);return}r.count+=t.count}function O(){return{phase:"exec",detailsByName:new Map}}function w(e){if(!f(e))return null;const t=s(o(e,"kind"),null),n=s(o(e,"key"),null),i=s(o(e,"title"),null);if(!t||!n||!i)return null;const r={kind:t,key:n,title:i,count:1},a=o(e,"labelled");typeof a=="boolean"&&(r.labelled=a);const l=s(o(e,"checkName"),null);l&&(r.checkName=l);const p=s(o(e,"phase"),null);p&&(r.phase=p);const m=s(o(e,"message"),null);m&&(r.message=m);const d=o(e,"expected");d!==void 0&&(r.expected=d);const h=o(e,"actual");h!==void 0&&(r.actual=h);const y=s(o(e,"traceId"),null);y&&(r.traceId=y);const k=u(o(e,"request"));k&&(r.request=k);const g=u(o(e,"response"));g&&(r.response=g);const x=u(o(e,"location"));x&&(r.location=x);const N=s(o(e,"stack"),null);return N&&(r.stack=N),r}function C(e,t){return e.length===0?t:[...e,t].join(" > ")}function s(e,t){if(typeof e!="string")return t;const n=e.trim();return n.length>0?n:t}function u(e){if(!f(e))return null;try{const t=F(e);if(t===void 0)return null;const n=JSON.parse(t);return B(n)?n:null}catch{return null}}function o(e,t){return Reflect.get(e,t)}function f(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function B(e){return f(e)}function F(e){return JSON.stringify(e)}
@@ -4,8 +4,8 @@ export declare function expectStatusOneOf(response: RuntimeResponse, expectedVal
4
4
  export declare function expectNotStatus(response: RuntimeResponse, unexpected: number, label?: string | null): boolean;
5
5
  export declare function expectJson(response: RuntimeResponse, predicate: RuntimePredicate<unknown>, label?: string | null): boolean;
6
6
  export declare function expectJsonPath(response: RuntimeResponse, path: string, predicate: RuntimePredicate<unknown>, label?: string | null): boolean;
7
- export declare function expectErrorShape(response: RuntimeResponse, label?: string): boolean;
8
- export declare function expectErrorMessage(response: RuntimeResponse, label?: string): boolean;
7
+ export declare function expectErrorShape(response: RuntimeResponse, label?: string | null): boolean;
8
+ export declare function expectErrorMessage(response: RuntimeResponse, label?: string | null): boolean;
9
9
  export declare function expectResponse(response: RuntimeResponse, predicate: RuntimePredicate<RuntimeResponse>, label: string | null): boolean;
10
10
  export declare function expectValue<T>(value: T, predicate: RuntimePredicate<T>, label: string | null, options?: RuntimeAssertionOptions<T>): boolean;
11
11
  export declare function expectCondition(predicate: () => boolean, label: string | null, options?: RuntimeConditionAssertionOptions): boolean;
@@ -1 +1 @@
1
- import{evaluateCheck as l}from"./checks.js";import{extractErrorMessageFromBody as w,hasStandardErrorShape as N}from"./shared/error-body.js";import{buildValueAssertionDetail as y,formatAssertionValue as d,normalizeErrorValue as J,resolveActual as q,resolveConditionActual as A,resolveConditionFailureMessage as F,resolveFailureMessage as P}from"./shared/assertion-details.js";import{safeJson as $,summarizeHttpTrace as c,toBodyPreview as E}from"./http.js";const R=String;export function expectStatus(t,e,r=null){return expectStatusOneOf(t,[e],r)}export function expectStatusOneOf(t,e,r=null){const n=j(e),o=s(r,V(n)),i=c(t),u=t.status;return l(t,o,a=>n.includes(a.status),()=>p({kind:"http-assertion",title:o,trace:i,expected:n.length===1?n[0]:n,actual:u,response:t,message:`${i?.method??"HTTP"} ${i?.path??"(unknown path)"} expected ${b(n)}, got ${String(u)}`}))}export function expectNotStatus(t,e,r=null){const n=s(r,`status is not ${String(e)}`),o=c(t),i=t.status;return l(t,n,u=>u.status!==e,()=>p({kind:"http-assertion",title:n,trace:o,expected:{not:e},actual:i,response:t,message:`${o?.method??"HTTP"} ${o?.path??"(unknown path)"} expected not ${String(e)}, got ${String(i)}`}))}export function expectJson(t,e,r=null){const n=s(r,"response JSON matches expectation"),o=c(t),i=$(t);return i.ok?l(i.value,n,u=>e(u),()=>({kind:"json-assertion",key:x(o,n),title:n,message:`${o?.method??"HTTP"} ${o?.path??"(unknown path)"} JSON assertion failed`,request:o?f(o):null,response:o?m(o):null,actual:k(i.value)})):S(t,o,n,"valid JSON response")}export function expectJsonPath(t,e,r,n=null){const o=s(n,`response JSON path ${e} matches expectation`),i=c(t),u=$(t);if(!u.ok)return S(t,i,o,`valid JSON containing ${e}`);const a=z(u.value,e);return l(a,o,T=>r(T),()=>({kind:"json-path-assertion",key:x(i,o),title:o,message:`${i?.method??"HTTP"} ${i?.path??"(unknown path)"} JSON path ${e} assertion failed`,expected:e,actual:k(a),request:i?f(i):null,response:i?m(i):null}))}export function expectErrorShape(t,e="response has standard error shape"){return expectJson(t,N,e)}export function expectErrorMessage(t,e="response includes extractable error message"){return expectJson(t,r=>w(r)!==null,e)}export function expectResponse(t,e,r){return l(t,s(r,"response matches expectation"),e)}export function expectValue(t,e,r,n={}){const o=s(r,"value matches expectation");return l(t,o,e,()=>y({kind:n.kind??"value-assertion",title:o,actual:q(t,n.actual),expected:n.expected,message:P(t,o,n.describeFailure)}))}export function expectCondition(t,e,r={}){const n=s(e,"condition holds");return l(null,n,()=>t(),()=>y({kind:r.kind??"condition-assertion",title:n,actual:A(r.actual),expected:r.expected,message:F(n,r.describeFailure)}))}export function expectRowCount(t,e,r=null){const n=e;if(!Number.isInteger(n)||n<0)throw new Error("expectRowCount requires a non-negative integer count");return expectValue(t,o=>o.length===n,s(r,`row count is ${String(n)}`),{actual:o=>o.length,describeFailure:o=>`expected ${String(n)} row(s), got ${String(o.length)}`,expected:n,kind:"row-count-assertion"})}export function expectSingleRow(t,e="query returns one row"){return expectRowCount(t,1,e)?t[0]??null:null}export function expectNoRows(t,e="query returns no rows"){return expectRowCount(t,0,e)}export function expectField(t,e,r,n=null){const o=String(e),u=(O(t)?t.at(0):t)?.[o],a=g(r)?void 0:r;return expectValue(u,H(r),s(n,`${o||"field"} matches expectation`),{actual:()=>u,describeFailure:()=>Array.isArray(t)&&t.length===0?`expected ${o||"field"}=${d(a)}, but query returned 0 rows`:`${o||"field"} expected ${d(a)}, got ${d(u)}`,expected:a,kind:"field-assertion"})}export function expectTruthyField(t,e,r=null){const n=String(e);return expectField(t,n,o=>!!o,s(r,`${n||"field"} is truthy`))}export function expectType(t,e,r=null){const n=C(e);return expectValue(t,o=>h(o)===n,s(r,`type is ${n}`),{actual:o=>h(o),describeFailure:o=>`expected type ${n}, got ${h(o)}`,expected:n,kind:"type-assertion"})}export function captureError(t){try{return t(),null}catch(e){return e}}export function expectError(t,e=null,r=null){const n=I(t)?captureError(t):t,o=g(e)?e:null;return expectValue(n,u=>u?o?o(u):!0:!1,s(typeof e=="string"&&r===null?e:r,"error is captured"),{actual:J(n),describeFailure:()=>"expected operation to throw",expected:"error",kind:"error-assertion"})}function S(t,e,r,n){return l(t,r,()=>!1,()=>p({kind:"http-json-assertion",title:r,trace:e,expected:n,actual:"invalid JSON response",response:t,message:`${e?.method??"HTTP"} ${e?.path??"(unknown path)"} did not return valid JSON`}))}function p({kind:t,title:e,trace:r,expected:n,actual:o,response:i,message:u}){return{kind:t,key:x(r,e),title:e,message:u,expected:n,actual:o,traceId:r?.id??void 0,request:r?f(r):null,response:r?m(r,i):null}}function f(t){return{id:t.id,requestId:t.requestId,method:t.method,path:t.path,url:t.url,headers:t.requestHeaders}}function m(t,e=null){const r=t.response?.status??e?.status??null;return{status:r===0?null:r,contentType:t.response?.contentType??null,bodyPreview:t.response?.bodyPreview??E(e),bodyTruncated:!!t.response?.bodyTruncated}}function x(t,e){return t?.path?`${t.method} ${t.path} > ${e}`:e}function V(t){return`status is ${b(t)}`}function b(t){return t.length===0?"expected":t.length===1?String(t[0]):t.map(e=>String(e)).join(" or ")}function j(t){const e=t.filter(r=>Number.isFinite(r));if(e.length===0)throw new Error("expectStatusOneOf requires at least one numeric expected status");return e}function z(t,e){const r=e.split(".").map(o=>o.trim()).filter(Boolean);let n=t;for(const o of r){if(n===null||typeof n!="object")return;n=Reflect.get(n,o)}return n}function k(t){if(t==null)return t;if(typeof t=="string")return t.length>240?`${t.slice(0,237)}...`:t;try{return(M(t)??"undefined").slice(0,240)}catch{return R(t)}}function H(t){return g(t)?t:e=>Object.is(e,t)}function h(t){return t===null?"null":Array.isArray(t)?"array":typeof t}function C(t){if(typeof t!="string")throw new Error("expectType requires a type name");const e=t.trim();if(!B(e))throw new Error("expectType requires a supported type name");return e}function B(t){return t==="array"||t==="bigint"||t==="boolean"||t==="function"||t==="null"||t==="number"||t==="object"||t==="string"||t==="symbol"||t==="undefined"}function s(t,e){const r=typeof t=="string"?t.trim():"";return r.length>0?r:e}function g(t){return typeof t=="function"}function I(t){return typeof t=="function"}function M(t){return JSON.stringify(t)}function O(t){return Array.isArray(t)}
1
+ import{evaluateCheck as a}from"./checks.js";import{extractErrorMessageFromBody as q,hasStandardErrorShape as F}from"./shared/error-body.js";import{buildValueAssertionDetail as S,formatAssertionValue as p,normalizeErrorValue as E,resolveActual as P,resolveConditionActual as R,resolveConditionFailureMessage as V,resolveFailureMessage as j}from"./shared/assertion-details.js";import{safeJson as b,summarizeHttpTrace as d,toBodyPreview as z}from"./http.js";const T="response JSON matches expectation",H=String;export function expectStatus(t,e,r=null){return expectStatusOneOf(t,[e],r)}export function expectStatusOneOf(t,e,r=null){const n=I(e),o=c(r,C(n)),i=d(t),u=t.status;return a(t,o.title,s=>n.includes(s.status),()=>m({...o,kind:"http-assertion",trace:i,expected:n.length===1?n[0]:n,actual:u,response:t,message:`${i?.method??"HTTP"} ${i?.path??"(unknown path)"} expected ${w(n)}, got ${String(u)}`}))}export function expectNotStatus(t,e,r=null){const n=c(r,`status is not ${String(e)}`),o=d(t),i=t.status;return a(t,n.title,u=>u.status!==e,()=>m({...n,kind:"http-assertion",trace:o,expected:{not:e},actual:i,response:t,message:`${o?.method??"HTTP"} ${o?.path??"(unknown path)"} expected not ${String(e)}, got ${String(i)}`}))}export function expectJson(t,e,r=null){return f(t,e,c(r,T))}function f(t,e,r){const n=d(t),o=b(t);return o.ok?a(o.value,r.title,i=>e(i),()=>({...r,kind:"json-assertion",key:g(n,r.title),message:`${n?.method??"HTTP"} ${n?.path??"(unknown path)"} JSON assertion failed`,request:n?x(n):null,response:n?h(n):null,actual:N(o.value)})):k(t,n,r,"valid JSON response")}export function expectJsonPath(t,e,r,n=null){const o=c(n,`response JSON path ${e} matches expectation`),i=d(t),u=b(t);if(!u.ok)return k(t,i,o,`valid JSON containing ${e}`);const s=O(u.value,e);return a(s,o.title,J=>r(J),()=>({...o,kind:"json-path-assertion",key:g(i,o.title),message:`${i?.method??"HTTP"} ${i?.path??"(unknown path)"} JSON path ${e} assertion failed`,expected:e,actual:N(s),request:i?x(i):null,response:i?h(i):null}))}export function expectErrorShape(t,e){return f(t,F,A(e,"response has standard error shape"))}export function expectErrorMessage(t,e){return f(t,r=>q(r)!==null,A(e,"response includes extractable error message"))}export function expectResponse(t,e,r){return a(t,l(r,"response matches expectation"),e)}export function expectValue(t,e,r,n={}){const o=l(r,"value matches expectation");return a(t,o,e,()=>S({kind:n.kind??"value-assertion",title:o,actual:P(t,n.actual),expected:n.expected,message:j(t,o,n.describeFailure)}))}export function expectCondition(t,e,r={}){const n=l(e,"condition holds");return a(null,n,()=>t(),()=>S({kind:r.kind??"condition-assertion",title:n,actual:R(r.actual),expected:r.expected,message:V(n,r.describeFailure)}))}export function expectRowCount(t,e,r=null){const n=e;if(!Number.isInteger(n)||n<0)throw new Error("expectRowCount requires a non-negative integer count");return expectValue(t,o=>o.length===n,l(r,`row count is ${String(n)}`),{actual:o=>o.length,describeFailure:o=>`expected ${String(n)} row(s), got ${String(o.length)}`,expected:n,kind:"row-count-assertion"})}export function expectSingleRow(t,e="query returns one row"){return expectRowCount(t,1,e)?t[0]??null:null}export function expectNoRows(t,e="query returns no rows"){return expectRowCount(t,0,e)}export function expectField(t,e,r,n=null){const o=String(e),u=(K(t)?t.at(0):t)?.[o],s=$(r)?void 0:r;return expectValue(u,B(r),l(n,`${o||"field"} matches expectation`),{actual:()=>u,describeFailure:()=>Array.isArray(t)&&t.length===0?`expected ${o||"field"}=${p(s)}, but query returned 0 rows`:`${o||"field"} expected ${p(s)}, got ${p(u)}`,expected:s,kind:"field-assertion"})}export function expectTruthyField(t,e,r=null){const n=String(e);return expectField(t,n,o=>!!o,l(r,`${n||"field"} is truthy`))}export function expectType(t,e,r=null){const n=M(e);return expectValue(t,o=>y(o)===n,l(r,`type is ${n}`),{actual:o=>y(o),describeFailure:o=>`expected type ${n}, got ${y(o)}`,expected:n,kind:"type-assertion"})}export function captureError(t){try{return t(),null}catch(e){return e}}export function expectError(t,e=null,r=null){const n=L(t)?captureError(t):t,o=$(e)?e:null;return expectValue(n,u=>u?o?o(u):!0:!1,l(typeof e=="string"&&r===null?e:r,"error is captured"),{actual:E(n),describeFailure:()=>"expected operation to throw",expected:"error",kind:"error-assertion"})}function k(t,e,r,n){return a(t,r.title,()=>!1,()=>m({...r,kind:"http-json-assertion",trace:e,expected:n,actual:"invalid JSON response",response:t,message:`${e?.method??"HTTP"} ${e?.path??"(unknown path)"} did not return valid JSON`}))}function m({kind:t,labelled:e,title:r,trace:n,expected:o,actual:i,response:u,message:s}){return{kind:t,key:g(n,r),title:r,labelled:e,message:s,expected:o,actual:i,traceId:n?.id??void 0,request:n?x(n):null,response:n?h(n,u):null}}function x(t){return{id:t.id,requestId:t.requestId,method:t.method,path:t.path,url:t.url,headers:t.requestHeaders}}function h(t,e=null){const r=t.response?.status??e?.status??null;return{status:r===0?null:r,contentType:t.response?.contentType??null,bodyPreview:t.response?.bodyPreview??z(e),bodyTruncated:!!t.response?.bodyTruncated}}function g(t,e){return t?.path?`${t.method} ${t.path} > ${e}`:e}function C(t){return`status is ${w(t)}`}function w(t){return t.length===0?"expected":t.length===1?String(t[0]):t.map(e=>String(e)).join(" or ")}function I(t){const e=t.filter(r=>Number.isFinite(r));if(e.length===0)throw new Error("expectStatusOneOf requires at least one numeric expected status");return e}function O(t,e){const r=e.split(".").map(o=>o.trim()).filter(Boolean);let n=t;for(const o of r){if(n===null||typeof n!="object")return;n=Reflect.get(n,o)}return n}function N(t){if(t==null)return t;if(typeof t=="string")return t.length>240?`${t.slice(0,237)}...`:t;try{return(_(t)??"undefined").slice(0,240)}catch{return H(t)}}function B(t){return $(t)?t:e=>Object.is(e,t)}function y(t){return t===null?"null":Array.isArray(t)?"array":typeof t}function M(t){if(typeof t!="string")throw new Error("expectType requires a type name");const e=t.trim();if(!D(e))throw new Error("expectType requires a supported type name");return e}function D(t){return t==="array"||t==="bigint"||t==="boolean"||t==="function"||t==="null"||t==="number"||t==="object"||t==="string"||t==="symbol"||t==="undefined"}function l(t,e){const r=typeof t=="string"?t.trim():"";return r.length>0?r:e}function c(t,e){const r=l(t,"");return r===""?{labelled:!1,title:e}:{labelled:!0,title:r}}function A(t,e){return c(t,t===void 0?e:T)}function $(t){return typeof t=="function"}function L(t){return typeof t=="function"}function _(t){return JSON.stringify(t)}function K(t){return Array.isArray(t)}
@@ -200,6 +200,7 @@ export interface RuntimeFailureDetail {
200
200
  groupPath?: string[];
201
201
  key: string;
202
202
  kind: string;
203
+ labelled?: boolean;
203
204
  location?: Record<string, unknown>;
204
205
  message?: string;
205
206
  phase?: string;
@@ -216,6 +217,7 @@ export interface RuntimeFailureDetailInput {
216
217
  expected?: unknown;
217
218
  key: string;
218
219
  kind: string;
220
+ labelled?: boolean;
219
221
  location?: object | null;
220
222
  message?: string;
221
223
  phase?: string;
@@ -1,2 +1,2 @@
1
- import w from"node:fs/promises";import y from"node:path";import{evidenceFileAccess as R,readStableEvidenceFile as b,writeOwnedEvidenceFile as $}from"../../adapters/evidence-files.js";import{NODE_VITEST_DATA_BUDGET_FAILURE_DETAIL_KIND as F,NODE_VITEST_DATA_FAILURE_DETAILS_ARTIFACT_KIND as _,NODE_VITEST_DATA_FAILURE_DETAILS_ARTIFACT_NAME as V}from"../runtime/runtime-input.js";import{vitestTestFailureDetails as B}from"./failure-details.js";import{readNativeReport as L,requireNativeEvidenceJson as M}from"../../adapters/native-report.js";const z=16*1024*1024,N=1024;export const NODE_VITEST_DATA_ARTIFACT_MAX_BYTES=16*1024*1024,NODE_VITEST_DATA_EVIDENCE_MAX_BYTES=64*1024*1024;const C=".testkit-owned-evidence";export async function collectNodeVitestEvidence(t,e=R){await w.mkdir(t.artifactDirectory,{recursive:!0});const r=await w.realpath(t.artifactDirectory),s=y.join(r,C);await w.mkdir(s,{mode:448,recursive:!1});const n=[],a=[];t.stdout.length>0&&(a.push({stream:"stdout",text:t.stdout}),n.push(await k(r,"stdout.log","node-vitest-stdout",t.stdout))),t.stderr.length>0&&(a.push({stream:"stderr",text:t.stderr}),n.push(await k(r,"stderr.log","node-vitest-stderr",t.stderr)));const i=await J(t.reportFile,t.identity,t.rootDirectory,s,e);n.push(...i.artifacts);const f=t.profileId==="node-vitest-data"?await X(r,s,t.dataManifestFile,e):{artifacts:[],error:null};return n.push(...f.artifacts),{dataEvidenceError:f.error,evidence:{artifacts:n,logs:a,metrics:[],...i.summary===null?{}:{native:{vitest:i.summary}}},reportError:i.error,reportFailureMessage:i.failureMessage,reportSummary:i.summary}}async function J(t,e,r,s,n){try{const a=await I(t,n);if(!a.exists||a.contents===null)throw new Error("Vitest did not produce its Testkit native report");const i=L(a.value,e).result;if(!T(i)||!T(i.summary))throw new Error("Vitest native report must contain result and summary objects");const f=G(i.summary),c=B(i.failures,r);if(f.numFailedTests!==c.length)throw new Error("Vitest native report failure count does not match its summary");if(!Array.isArray(i.errors)||!i.errors.every(m=>typeof m=="string"))throw new Error("Vitest native report errors must contain strings");if(f.success&&i.errors.length>0)throw new Error("Vitest native report success conflicts with runtime errors");const u=await $(s,"testkit-vitest-report.json",a.contents),o=await S(u,"application/json","engine.testkit-vitest-report","testkit-vitest-report","Testkit native Vitest report"),d=await U(s,c);return{artifacts:[o,...d===null?[]:[d]],error:null,failureMessage:typeof c[0]?.message=="string"?c[0].message:i.errors.find(m=>m.trim().length>0)??null,summary:f}}catch(a){return{artifacts:[],error:D(a),failureMessage:null,summary:null}}}async function U(t,e){if(e.length===0)return null;const r={failures:e,phase:"exec"},s=await $(t,"vitest-failure-details.json",Buffer.from(`${JSON.stringify(r,null,2)}
2
- `,"utf8"));return{...await S(s,"application/json","testkit.failure-details","vitest-failure-details",`${e.length.toString()} failed Vitest test${e.length===1?"":"s"}`),data:r}}async function X(t,e,r,s){let n;try{n=await I(r,s)}catch(c){return{artifacts:[],error:D(c)}}if(!n.exists)return{artifacts:[],error:new Error("Data profile did not produce an artifact manifest")};let a;try{a=Y(n.value)}catch(c){return{artifacts:[],error:D(c)}}const i=[],f=new Set;try{if(n.contents===null)throw new Error("Data artifact manifest bytes are unavailable");let c=n.contents.byteLength;if(c>NODE_VITEST_DATA_EVIDENCE_MAX_BYTES)throw new Error(`Data evidence exceeds its ${NODE_VITEST_DATA_EVIDENCE_MAX_BYTES.toString()} byte aggregate limit`);for(const[o,d]of a.artifacts.entries()){const m=await K(t,e,d,o,NODE_VITEST_DATA_EVIDENCE_MAX_BYTES-c,s),{artifact:l}=m,p=`${l.kind}\0${l.name}\0${m.sourcePath}`;if(f.has(p))throw new Error(`Data artifact manifest contains duplicate artifact ${l.name}`);f.add(p);const E=q(l.metadata?.sizeBytes,`Data artifact manifest artifacts[${o.toString()}]`);c+=E,i.push(l)}const u=await $(e,"manifest.json",n.contents);i.push(await S(u,"application/json","engine.data-manifest","data-artifact-manifest",`${i.length.toString()} data artifact(s)`))}catch(c){return{artifacts:i,error:D(c)}}return{artifacts:i,error:null}}function Y(t){if(!T(t))throw new Error("Data artifact manifest must contain an object");if(t.schemaVersion!==1)throw new Error("Data artifact manifest schemaVersion must be 1");if(!Array.isArray(t.artifacts))throw new Error("Data artifact manifest artifacts must be an array");if(t.artifacts.length===0)throw new Error("Data artifact manifest must contain at least one artifact");if(t.artifacts.length>N)throw new Error(`Data artifact manifest exceeds the ${N.toString()} artifact limit`);return{artifacts:t.artifacts,schemaVersion:t.schemaVersion}}async function K(t,e,r,s,n,a){const i=`Data artifact manifest artifacts[${s.toString()}]`;if(!T(r))throw new Error(`${i} must contain an object`);const f=g(r.kind,`${i}.kind`),c=g(r.name,`${i}.name`),u=g(r.path,`${i}.path`),o=await H(t,u,`${i}.path`),d=j(r.contentType,`${i}.contentType`),m=j(r.summary,`${i}.summary`),l=r.metadata===void 0?void 0:A(r.metadata,`${i}.metadata`),{contents:p}=await b(o,NODE_VITEST_DATA_ARTIFACT_MAX_BYTES,i,a);if(p.byteLength>n)throw new Error(`Data evidence exceeds its ${NODE_VITEST_DATA_EVIDENCE_MAX_BYTES.toString()} byte aggregate limit`);const E=W(f,c,d,p,i),O=await $(e,`${s.toString().padStart(4,"0")}-${Q(c)}${Z(o)}`,p);return{artifact:{kind:f,name:c,path:O,...d===void 0?{}:{contentType:d},...E===void 0?{}:{data:E},...m===void 0?{}:{summary:m},metadata:{...l??{},sizeBytes:p.byteLength}},sourcePath:o}}function W(t,e,r,s,n){if(t!==_)return;if(e!==V)throw new Error(`${n}.name must be ${JSON.stringify(V)} for ${_}`);if(r!=="application/json")throw new Error(`${n}.contentType must be "application/json" for ${_}`);const a=v(s,n),i=A(a,n);if(x(i,["failures","phase"],n),i.phase!=="exec")throw new Error(`${n}.phase must be "exec"`);if(!Array.isArray(i.failures)||i.failures.length===0)throw new Error(`${n}.failures must be a non-empty array`);for(const[f,c]of i.failures.entries()){const u=`${n}.failures[${f.toString()}]`,o=A(c,u);if(x(o,["actual","checkName","count","expected","groupPath","key","kind","message","title"],u),o.kind!==F)throw new Error(`${u}.kind must be ${JSON.stringify(F)}`);if(o.count!==1)throw new Error(`${u}.count must be 1`);const d=g(o.checkName,`${u}.checkName`),m=g(o.key,`${u}.key`),l=g(o.title,`${u}.title`);if(g(o.message,`${u}.message`),!Array.isArray(o.groupPath)||o.groupPath.length!==1||typeof o.groupPath[0]!="string"||o.groupPath[0].trim().length===0)throw new Error(`${u}.groupPath must contain one scenario name`);if(m!==`${o.groupPath[0]} > ${l}`)throw new Error(`${u}.key does not match its scenario and title`);if(!l.includes(`"${d}"`))throw new Error(`${u}.title does not identify its checkName`)}return i}async function k(t,e,r,s){const n=y.join(t,e);return await w.writeFile(n,s,"utf8"),S(n,"text/plain; charset=utf-8","engine.output",r,et(s)??"captured output")}async function S(t,e,r,s,n){const a=await w.stat(t);if(!a.isFile())throw new Error(`${s} artifact is not a regular file`);return{contentType:e,kind:r,metadata:{sizeBytes:a.size},name:s,path:t,summary:n}}async function I(t,e){let r;try{({contents:r}=await b(t,z,t,e))}catch(n){if(nt(n)&&n.code==="ENOENT")return{contents:null,exists:!1,value:null};throw n}const s=v(r,t);return{contents:r,exists:!0,value:s}}function v(t,e){const r=JSON.parse(t.toString("utf8"));return M(r,e),r}function G(t){const e={numFailedTests:h(t.numFailedTests,"Vitest native report.summary.numFailedTests"),numPassedTests:h(t.numPassedTests,"Vitest native report.summary.numPassedTests"),numPendingTests:h(t.numPendingTests,"Vitest native report.summary.numPendingTests"),numTodoTests:h(t.numTodoTests,"Vitest native report.summary.numTodoTests"),numTotalTests:h(t.numTotalTests,"Vitest native report.summary.numTotalTests"),numFailedTestSuites:h(t.numFailedTestSuites,"Vitest native report.summary.numFailedTestSuites"),numPassedTestSuites:h(t.numPassedTestSuites,"Vitest native report.summary.numPassedTestSuites"),numPendingTestSuites:h(t.numPendingTestSuites,"Vitest native report.summary.numPendingTestSuites"),numTotalTestSuites:h(t.numTotalTestSuites,"Vitest native report.summary.numTotalTestSuites"),success:tt(t.success,"Vitest native report.summary.success")};if(e.numTotalTests!==e.numFailedTests+e.numPassedTests+e.numPendingTests+e.numTodoTests||e.numTotalTestSuites!==e.numFailedTestSuites+e.numPassedTestSuites+e.numPendingTestSuites||e.success&&(e.numFailedTests>0||e.numFailedTestSuites>0||e.numTotalTestSuites===0))throw new Error("Vitest native report summary contains inconsistent counts or success");return e}async function H(t,e,r){const s=y.isAbsolute(e)?e:y.resolve(t,e),n=await w.lstat(s);if(!n.isFile()||n.isSymbolicLink())throw new Error(`${r} must be a regular non-symbolic-link file`);const a=await w.realpath(s);if(!rt(t,a))throw new Error(`${r} must stay within the task artifact directory`);return a}function Q(t){const e=t.trim().toLowerCase().replace(/[^a-z0-9._-]+/gu,"-").replace(/^-+|-+$/gu,"").slice(0,80);return e.length===0?"artifact":e}function Z(t){const e=y.extname(t).toLowerCase();return/^\.[a-z0-9]{1,12}$/u.test(e)?e:".bin"}function q(t,e){if(typeof t!="number"||!Number.isSafeInteger(t)||t<0)throw new Error(`${e} did not produce a valid sizeBytes identity`);return t}function A(t,e){if(!T(t))throw new Error(`${e} must contain an object`);const r={};for(const[s,n]of Object.entries(t))r[s]=P(n,`${e}.${s}`);return r}function x(t,e,r){const s=Object.keys(t).sort(),n=[...e].sort();if(s.length!==n.length||s.some((a,i)=>a!==n[i]))throw new Error(`${r} must contain exactly ${n.join(", ")}`)}function P(t,e){if(t===null||typeof t=="boolean"||typeof t=="string")return t;if(typeof t=="number"){if(!Number.isFinite(t))throw new Error(`${e} must be finite`);return t}return Array.isArray(t)?t.map((r,s)=>P(r,`${e}[${s.toString()}]`)):A(t,e)}function j(t,e){if(t!==void 0){if(typeof t!="string")throw new Error(`${e} must be a string`);return t}}function g(t,e){if(typeof t!="string"||t.trim().length===0)throw new Error(`${e} must be a non-empty string`);return t}function h(t,e){if(typeof t!="number"||!Number.isSafeInteger(t)||t<0)throw new Error(`${e} must be a non-negative safe integer`);return t}function tt(t,e){if(typeof t!="boolean")throw new Error(`${e} must be boolean`);return t}function et(t){return t.split(/\r?\n/u).map(e=>e.trim()).find(Boolean)??null}function rt(t,e){const r=y.relative(t,e);return r===""||!r.startsWith("..")&&!y.isAbsolute(r)}function T(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}function nt(t){return t instanceof Error}function D(t){return t instanceof Error?t:new Error(String(t))}
1
+ import w from"node:fs/promises";import y from"node:path";import{evidenceFileAccess as B,readStableEvidenceFile as V,writeOwnedEvidenceFile as $}from"../../adapters/evidence-files.js";import{NODE_VITEST_DATA_BUDGET_FAILURE_DETAIL_KIND as b,NODE_VITEST_DATA_FAILURE_DETAILS_ARTIFACT_KIND as _,NODE_VITEST_DATA_FAILURE_DETAILS_ARTIFACT_NAME as F}from"../runtime/runtime-input.js";import{vitestErrorDetails as L,vitestTestFailureDetails as M}from"./failure-details.js";import{readNativeReport as z,requireNativeEvidenceJson as C}from"../../adapters/native-report.js";const J=16*1024*1024,N=1024;export const NODE_VITEST_DATA_ARTIFACT_MAX_BYTES=16*1024*1024,NODE_VITEST_DATA_EVIDENCE_MAX_BYTES=64*1024*1024;const U=".testkit-owned-evidence";export async function collectNodeVitestEvidence(t,e=B){await w.mkdir(t.artifactDirectory,{recursive:!0});const r=await w.realpath(t.artifactDirectory),s=y.join(r,U);await w.mkdir(s,{mode:448,recursive:!1});const n=[],a=[];t.stdout.length>0&&(a.push({stream:"stdout",text:t.stdout}),n.push(await I(r,"stdout.log","node-vitest-stdout",t.stdout))),t.stderr.length>0&&(a.push({stream:"stderr",text:t.stderr}),n.push(await I(r,"stderr.log","node-vitest-stderr",t.stderr)));const i=await X(t.reportFile,t.identity,t.rootDirectory,s,e);n.push(...i.artifacts);const f=t.profileId==="node-vitest-data"?await K(r,s,t.dataManifestFile,e):{artifacts:[],error:null};return n.push(...f.artifacts),{dataEvidenceError:f.error,evidence:{artifacts:n,logs:a,metrics:[],...i.summary===null?{}:{native:{vitest:i.summary}}},reportError:i.error,reportFailureMessage:i.failureMessage,reportSummary:i.summary}}async function X(t,e,r,s,n){try{const a=await v(t,n);if(!a.exists||a.contents===null)throw new Error("Vitest did not produce its Testkit native report");const i=z(a.value,e).result;if(!T(i)||!T(i.summary))throw new Error("Vitest native report must contain result and summary objects");const f=Q(i.summary),c=M(i.failures,r);if(f.numFailedTests!==c.length)throw new Error("Vitest native report failure count does not match its summary");if(!Array.isArray(i.errors)||!i.errors.every(m=>typeof m=="string"))throw new Error("Vitest native report errors must contain strings");if(f.success&&i.errors.length>0)throw new Error("Vitest native report success conflicts with runtime errors");const u=await $(s,"testkit-vitest-report.json",a.contents),o=await S(u,"application/json","engine.testkit-vitest-report","testkit-vitest-report","Testkit native Vitest report"),d=await Y(s,c,L(i.errors));return{artifacts:[o,...d===null?[]:[d]],error:null,failureMessage:typeof c[0]?.message=="string"?c[0].message:i.errors.find(m=>m.trim().length>0)??null,summary:f}}catch(a){return{artifacts:[],error:D(a),failureMessage:null,summary:null}}}async function Y(t,e,r){if(e.length+r.length===0)return null;const s={failures:[...e,...r],phase:"exec"},n=await $(t,"vitest-failure-details.json",Buffer.from(`${JSON.stringify(s,null,2)}
2
+ `,"utf8"));return{...await S(n,"application/json","testkit.failure-details","vitest-failure-details",[...e.length===0?[]:[k(e.length,"failed Vitest test")],...r.length===0?[]:[`${k(r.length,"Vitest error")} outside a test`]].join(" and ")),data:s}}function k(t,e){return`${t.toString()} ${e}${t===1?"":"s"}`}async function K(t,e,r,s){let n;try{n=await v(r,s)}catch(c){return{artifacts:[],error:D(c)}}if(!n.exists)return{artifacts:[],error:new Error("Data profile did not produce an artifact manifest")};let a;try{a=W(n.value)}catch(c){return{artifacts:[],error:D(c)}}const i=[],f=new Set;try{if(n.contents===null)throw new Error("Data artifact manifest bytes are unavailable");let c=n.contents.byteLength;if(c>NODE_VITEST_DATA_EVIDENCE_MAX_BYTES)throw new Error(`Data evidence exceeds its ${NODE_VITEST_DATA_EVIDENCE_MAX_BYTES.toString()} byte aggregate limit`);for(const[o,d]of a.artifacts.entries()){const m=await G(t,e,d,o,NODE_VITEST_DATA_EVIDENCE_MAX_BYTES-c,s),{artifact:l}=m,p=`${l.kind}\0${l.name}\0${m.sourcePath}`;if(f.has(p))throw new Error(`Data artifact manifest contains duplicate artifact ${l.name}`);f.add(p);const E=et(l.metadata?.sizeBytes,`Data artifact manifest artifacts[${o.toString()}]`);c+=E,i.push(l)}const u=await $(e,"manifest.json",n.contents);i.push(await S(u,"application/json","engine.data-manifest","data-artifact-manifest",`${i.length.toString()} data artifact(s)`))}catch(c){return{artifacts:i,error:D(c)}}return{artifacts:i,error:null}}function W(t){if(!T(t))throw new Error("Data artifact manifest must contain an object");if(t.schemaVersion!==1)throw new Error("Data artifact manifest schemaVersion must be 1");if(!Array.isArray(t.artifacts))throw new Error("Data artifact manifest artifacts must be an array");if(t.artifacts.length===0)throw new Error("Data artifact manifest must contain at least one artifact");if(t.artifacts.length>N)throw new Error(`Data artifact manifest exceeds the ${N.toString()} artifact limit`);return{artifacts:t.artifacts,schemaVersion:t.schemaVersion}}async function G(t,e,r,s,n,a){const i=`Data artifact manifest artifacts[${s.toString()}]`;if(!T(r))throw new Error(`${i} must contain an object`);const f=g(r.kind,`${i}.kind`),c=g(r.name,`${i}.name`),u=g(r.path,`${i}.path`),o=await Z(t,u,`${i}.path`),d=O(r.contentType,`${i}.contentType`),m=O(r.summary,`${i}.summary`),l=r.metadata===void 0?void 0:A(r.metadata,`${i}.metadata`),{contents:p}=await V(o,NODE_VITEST_DATA_ARTIFACT_MAX_BYTES,i,a);if(p.byteLength>n)throw new Error(`Data evidence exceeds its ${NODE_VITEST_DATA_EVIDENCE_MAX_BYTES.toString()} byte aggregate limit`);const E=H(f,c,d,p,i),R=await $(e,`${s.toString().padStart(4,"0")}-${q(c)}${tt(o)}`,p);return{artifact:{kind:f,name:c,path:R,...d===void 0?{}:{contentType:d},...E===void 0?{}:{data:E},...m===void 0?{}:{summary:m},metadata:{...l??{},sizeBytes:p.byteLength}},sourcePath:o}}function H(t,e,r,s,n){if(t!==_)return;if(e!==F)throw new Error(`${n}.name must be ${JSON.stringify(F)} for ${_}`);if(r!=="application/json")throw new Error(`${n}.contentType must be "application/json" for ${_}`);const a=x(s,n),i=A(a,n);if(P(i,["failures","phase"],n),i.phase!=="exec")throw new Error(`${n}.phase must be "exec"`);if(!Array.isArray(i.failures)||i.failures.length===0)throw new Error(`${n}.failures must be a non-empty array`);for(const[f,c]of i.failures.entries()){const u=`${n}.failures[${f.toString()}]`,o=A(c,u);if(P(o,["actual","checkName","count","expected","groupPath","key","kind","message","title"],u),o.kind!==b)throw new Error(`${u}.kind must be ${JSON.stringify(b)}`);if(o.count!==1)throw new Error(`${u}.count must be 1`);const d=g(o.checkName,`${u}.checkName`),m=g(o.key,`${u}.key`),l=g(o.title,`${u}.title`);if(g(o.message,`${u}.message`),!Array.isArray(o.groupPath)||o.groupPath.length!==1||typeof o.groupPath[0]!="string"||o.groupPath[0].trim().length===0)throw new Error(`${u}.groupPath must contain one scenario name`);if(m!==`${o.groupPath[0]} > ${l}`)throw new Error(`${u}.key does not match its scenario and title`);if(!l.includes(`"${d}"`))throw new Error(`${u}.title does not identify its checkName`)}return i}async function I(t,e,r,s){const n=y.join(t,e);return await w.writeFile(n,s,"utf8"),S(n,"text/plain; charset=utf-8","engine.output",r,nt(s)??"captured output")}async function S(t,e,r,s,n){const a=await w.stat(t);if(!a.isFile())throw new Error(`${s} artifact is not a regular file`);return{contentType:e,kind:r,metadata:{sizeBytes:a.size},name:s,path:t,summary:n}}async function v(t,e){let r;try{({contents:r}=await V(t,J,t,e))}catch(n){if(st(n)&&n.code==="ENOENT")return{contents:null,exists:!1,value:null};throw n}const s=x(r,t);return{contents:r,exists:!0,value:s}}function x(t,e){const r=JSON.parse(t.toString("utf8"));return C(r,e),r}function Q(t){const e={numFailedTests:h(t.numFailedTests,"Vitest native report.summary.numFailedTests"),numPassedTests:h(t.numPassedTests,"Vitest native report.summary.numPassedTests"),numPendingTests:h(t.numPendingTests,"Vitest native report.summary.numPendingTests"),numTodoTests:h(t.numTodoTests,"Vitest native report.summary.numTodoTests"),numTotalTests:h(t.numTotalTests,"Vitest native report.summary.numTotalTests"),numFailedTestSuites:h(t.numFailedTestSuites,"Vitest native report.summary.numFailedTestSuites"),numPassedTestSuites:h(t.numPassedTestSuites,"Vitest native report.summary.numPassedTestSuites"),numPendingTestSuites:h(t.numPendingTestSuites,"Vitest native report.summary.numPendingTestSuites"),numTotalTestSuites:h(t.numTotalTestSuites,"Vitest native report.summary.numTotalTestSuites"),success:rt(t.success,"Vitest native report.summary.success")};if(e.numTotalTests!==e.numFailedTests+e.numPassedTests+e.numPendingTests+e.numTodoTests||e.numTotalTestSuites!==e.numFailedTestSuites+e.numPassedTestSuites+e.numPendingTestSuites||e.success&&(e.numFailedTests>0||e.numFailedTestSuites>0||e.numTotalTestSuites===0))throw new Error("Vitest native report summary contains inconsistent counts or success");return e}async function Z(t,e,r){const s=y.isAbsolute(e)?e:y.resolve(t,e),n=await w.lstat(s);if(!n.isFile()||n.isSymbolicLink())throw new Error(`${r} must be a regular non-symbolic-link file`);const a=await w.realpath(s);if(!it(t,a))throw new Error(`${r} must stay within the task artifact directory`);return a}function q(t){const e=t.trim().toLowerCase().replace(/[^a-z0-9._-]+/gu,"-").replace(/^-+|-+$/gu,"").slice(0,80);return e.length===0?"artifact":e}function tt(t){const e=y.extname(t).toLowerCase();return/^\.[a-z0-9]{1,12}$/u.test(e)?e:".bin"}function et(t,e){if(typeof t!="number"||!Number.isSafeInteger(t)||t<0)throw new Error(`${e} did not produce a valid sizeBytes identity`);return t}function A(t,e){if(!T(t))throw new Error(`${e} must contain an object`);const r={};for(const[s,n]of Object.entries(t))r[s]=j(n,`${e}.${s}`);return r}function P(t,e,r){const s=Object.keys(t).sort(),n=[...e].sort();if(s.length!==n.length||s.some((a,i)=>a!==n[i]))throw new Error(`${r} must contain exactly ${n.join(", ")}`)}function j(t,e){if(t===null||typeof t=="boolean"||typeof t=="string")return t;if(typeof t=="number"){if(!Number.isFinite(t))throw new Error(`${e} must be finite`);return t}return Array.isArray(t)?t.map((r,s)=>j(r,`${e}[${s.toString()}]`)):A(t,e)}function O(t,e){if(t!==void 0){if(typeof t!="string")throw new Error(`${e} must be a string`);return t}}function g(t,e){if(typeof t!="string"||t.trim().length===0)throw new Error(`${e} must be a non-empty string`);return t}function h(t,e){if(typeof t!="number"||!Number.isSafeInteger(t)||t<0)throw new Error(`${e} must be a non-negative safe integer`);return t}function rt(t,e){if(typeof t!="boolean")throw new Error(`${e} must be boolean`);return t}function nt(t){return t.split(/\r?\n/u).map(e=>e.trim()).find(Boolean)??null}function it(t,e){const r=y.relative(t,e);return r===""||!r.startsWith("..")&&!y.isAbsolute(r)}function T(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}function st(t){return t instanceof Error}function D(t){return t instanceof Error?t:new Error(String(t))}
@@ -1,2 +1,3 @@
1
1
  import type { EngineJsonObject } from "../../api/index.js";
2
2
  export declare function vitestTestFailureDetails(value: unknown, rootDirectory: string): EngineJsonObject[];
3
+ export declare function vitestErrorDetails(errors: readonly string[]): EngineJsonObject[];
@@ -1,2 +1,2 @@
1
- import s from"node:path";export function vitestTestFailureDetails(t,e){if(!Array.isArray(t))throw new Error("Vitest native report failures must be an array");const i=new Set;return t.map(n=>{if(typeof n!="object"||n===null||Array.isArray(n))throw new Error("Vitest native report failure must contain an object");const r=n,u=l(r.id,"id");if(i.has(u))throw new Error("Vitest native report contains a duplicate test identity");i.add(u);const o=l(r.title,"title"),h=w(e,l(r.file,"file")),c=g(r.line),f=g(r.column);if(!Array.isArray(r.messages)||!r.messages.every(a=>typeof a=="string"))throw new Error("Vitest native report failure messages must contain strings");const m=r.messages.map(a=>a.trim()).filter(Boolean),p=m.slice(1).join(`
2
- `);return{classification:"product",count:1,key:o,kind:"vitest-test",location:{path:h,...c===null?{}:{line:c},...f===null?{}:{column:f}},message:m[0]??`Vitest assertion failed: ${o}`,...p.length===0?{}:{stack:p},title:o}})}function w(t,e){if(!s.isAbsolute(e))return e;const i=s.relative(t,e);return i.length===0||i.startsWith("..")||s.isAbsolute(i)?e:i.split(s.sep).join("/")}function g(t){if(t===null)return null;if(typeof t!="number"||!Number.isSafeInteger(t)||t<0)throw new Error("Vitest native report failure coordinate must be a non-negative integer or null");return t}function l(t,e){if(typeof t!="string"||t.trim().length===0)throw new Error(`Vitest native report failure ${e} must be a non-empty string`);return t}
1
+ import s from"node:path";export function vitestTestFailureDetails(t,e){if(!Array.isArray(t))throw new Error("Vitest native report failures must be an array");const r=new Set;return t.map(n=>{if(typeof n!="object"||n===null||Array.isArray(n))throw new Error("Vitest native report failure must contain an object");const i=n,u=l(i.id,"id");if(r.has(u))throw new Error("Vitest native report contains a duplicate test identity");r.add(u);const o=l(i.title,"title"),d=h(e,l(i.file,"file")),c=g(i.line),f=g(i.column);if(!Array.isArray(i.messages)||!i.messages.every(a=>typeof a=="string"))throw new Error("Vitest native report failure messages must contain strings");const m=i.messages.map(a=>a.trim()).filter(Boolean),p=m.slice(1).join(`
2
+ `);return{classification:"product",count:1,key:o,kind:"vitest-test",location:{path:d,...c===null?{}:{line:c},...f===null?{}:{column:f}},message:m[0]??`Vitest assertion failed: ${o}`,...p.length===0?{}:{stack:p},title:o}})}export function vitestErrorDetails(t){const e="Vitest error outside a test";return t.map((r,n)=>({count:1,key:`error ${String(n+1)}`,kind:"vitest-error",message:r.trim()||e,title:e}))}function h(t,e){if(!s.isAbsolute(e))return e;const r=s.relative(t,e);return r.length===0||r.startsWith("..")||s.isAbsolute(r)?e:r.split(s.sep).join("/")}function g(t){if(t===null)return null;if(typeof t!="number"||!Number.isSafeInteger(t)||t<0)throw new Error("Vitest native report failure coordinate must be a non-negative integer or null");return t}function l(t,e){if(typeof t!="string"||t.trim().length===0)throw new Error(`Vitest native report failure ${e} must be a non-empty string`);return t}
@@ -1 +1 @@
1
- import{makeTempDirAsync as D}from"../../../process/files/temp-dir.js";import{classifyManagedProcessResult as F,terminalOutcome as T,processOutputEvidence as N,failureFromError as P,firstOutputLine as R,processExitFailure as C,toError as h,executeEngineTasks as M,managedProcessFailure as x,emptyEvidence as b,taskTimeoutFailure as W}from"../../adapters/execution.js";import{requireRootDirectory as B,requireAbsoluteWorkspacePath as O,requireConsumerFile as Y,safeSegment as S}from"../../adapters/workspace.js";import u from"node:fs/promises";import H from"node:path";import{setTimeout as U}from"node:timers/promises";import{pathToFileURL as $}from"node:url";import{isEngineTaskDeadlineExceededReason as K,resolveEngineTaskInputs as G}from"../../api/index.js";import{createNativeInvocationProcessEnvironment as V,runManagedProcess as j}from"../../../process/index.js";import{PlaywrightConsumerConfigurationError as J,playwrightExactFileFilter as z,writePlaywrightTaskWorkspace as X}from"./config.js";import{collectPlaywrightEvidence as Q}from"./evidence.js";import{engineEvidenceIdentity as Z}from"../../adapters/evidence-identity.js";import{getNodeStartupCacheEnvironment as q}from"../../adapters/node-startup-cache.js";const y={codePrefix:"PLAYWRIGHT",label:"Playwright"};export const PLAYWRIGHT_SPAWN_ENVIRONMENT_MAX_BYTES=24*1024;const k=64;class L extends Error{constructor(t){const r=h(t);super(r.message,{cause:r}),this.name="PlaywrightConsumerPreparationError"}}export const playwrightAdapterDependencies={createInvocationEnvironment:V,removeOwnedDirectory:et,writeTaskWorkspace:X};const tt=new Set(["EBUSY","ENOTEMPTY","EPERM"]),rt=5;async function et(e){for(let t=0;;t+=1)try{await u.rm(e,{force:!0,recursive:!0});return}catch(r){const i=r instanceof Error&&"code"in r?r.code:void 0;if(t===rt||typeof i!="string"||!tt.has(i))throw r;await U((t+1)*100)}}export async function executePlaywrightEngine(e,t,r,i=playwrightAdapterDependencies){const o=await it(e,t,i,r);return M(e,o.tasks,y,(a,s)=>ot(e,o,a,s),ct(e,o.installation))}async function it(e,t,r,i){const o=await B(e.workspace.rootDirectory);O(e.workspace.temporaryDirectory,"temporary directory"),O(e.workspace.artifactDirectory,"artifact directory"),await u.mkdir(e.workspace.temporaryDirectory,{recursive:!0}),await u.mkdir(e.workspace.artifactDirectory,{recursive:!0});const a=await u.realpath(e.workspace.temporaryDirectory),s=await u.realpath(e.workspace.artifactDirectory),n=await D(`playwright-${S(e.invocationId)}-`,{parentDir:a});i.defer(()=>r.removeOwnedDirectory(n));const l=await r.createInvocationEnvironment(n);i.defer(()=>r.removeOwnedDirectory(l.temporaryDirectory));const p=[];for(const[g,c]of e.tasks.entries()){const I=G(e,c),v=`${g.toString().padStart(3,"0")}-${S(c.id)}`,_=H.join(n,v),f=await D(`${v}-`,{parentDir:s});try{const m=st(e,c.serviceName),E=await lt(()=>Y(o,c.file,"Playwright task file")),w=await r.writeTaskWorkspace({artifactDirectory:f,baseUrl:m,browserFile:t.browserExecutable,endpoints:e.target.endpoints,inputs:I,invocationId:e.invocationId,managedModuleEntrypoints:{authoring:t.authoringEntrypoints.authoring,cli:t.family.cli,playwright:t.family.playwright.entrypoint,playwrightCore:t.family.playwrightCore.entrypoint,playwrightTest:t.family.playwrightTest.entrypoint,root:t.authoringEntrypoints.root,rootPackageJson:t.authoringEntrypoints.rootPackageJson},moduleFile:t.family.playwrightTest.entrypoint,orchestration:c.orchestration,rootDirectory:o,serviceName:c.serviceName,sourceFile:E,taskDirectory:_,taskId:c.id});i.defer(async()=>{try{await r.removeOwnedDirectory(w.attachmentDirectory)}catch(d){if(!(d instanceof Error&&"code"in d&&d.code==="ENOTDIR"))throw d}}),p.push({artifactDirectory:f,inputs:I,task:c,preparationFailure:null,prepared:{baseUrl:m,sourceFile:E,workspace:w}})}catch(m){const E=(await pt([_,f],r)).map(w=>w.error);p.push({artifactDirectory:f,inputs:I,task:c,preparationFailure:playwrightPreparationFailure(m,E),prepared:null})}}return Object.freeze({installation:t,invocationEnvironment:l.environment,rootDirectory:o,tasks:Object.freeze(p)})}async function ot(e,t,r,i){const a={...await q(e.workspace.cacheDirectory,r.inputs.environment),...nt(e,t,r)};try{A(a)}catch{delete a.NODE_COMPILE_CACHE}const s=r.task.deadlineMs-Date.now();if(s<=0)return T(r.task.id,"failed",i,b(),W(r.task.timeoutMs,y));const n=await j({args:[t.installation.family.cli,"test",z(r.prepared.sourceFile),"--config",r.prepared.workspace.configFile],cwd:t.rootDirectory,displayLabel:"Playwright",env:a,executable:process.execPath,signal:e.signal,timeoutMs:s});let l;try{l=await Q({identity:{engineId:"playwright",invocationId:e.invocationId,taskId:r.task.id,sourceFile:r.prepared.sourceFile},artifactDirectory:r.artifactDirectory,outputDirectory:r.prepared.workspace.outputDirectory,reportFile:r.prepared.workspace.reportFile,rootDirectory:t.rootDirectory,sourceFile:r.prepared.sourceFile,stderr:n.stderr,stdout:n.stdout})}catch(g){return T(r.task.id,"failed",i,N(n),playwrightEvidenceCollectionFailure(n,g))}const p=classifyPlaywrightProcessResult(n,l,r.task.timeoutMs,K(e.signal.reason));return T(r.task.id,p.status,i,l.evidence,p.failure)}export function classifyPlaywrightProcessResult(e,t,r,i){const o=F(e,r,i,y);if(o!==null)return o;if(t.outputLimit!==null){const n=t.outputLimit;return{failure:{code:"PLAYWRIGHT_OUTPUT_ARTIFACT_LIMIT",details:{...n},message:`Playwright output exceeded its ${n.limit.toString()} ${n.kind==="bytes"?"byte aggregate":"entry"} artifact limit`},status:"failed"}}const a=t.attachmentFailures.find(n=>n.code==="PLAYWRIGHT_ATTACHMENT_LIMIT");if(a!==void 0)return{failure:a,status:"failed"};if(t.report.failure!==null)return t.report;const s=t.attachmentFailures.at(0);return s!==void 0?{failure:s,status:"failed"}:e.exitCode!==0?C(e,y,R(e.stderr)??R(e.stdout)):t.reportError!==null?{failure:{...P(t.reportError,"PLAYWRIGHT_REPORT_INVALID"),classification:"infra"},status:"failed"}:t.report}export function playwrightEvidenceCollectionFailure(e,t){const r=h(t);return x(e,y,r)??{...P(r,"PLAYWRIGHT_EVIDENCE_FAILED"),classification:"infra"}}function nt(e,t,r){const i={...t.invocationEnvironment},o=r.task,a=r.prepared.workspace.inputBootstrapFile,s=r.prepared.workspace.resolutionBootstrapFile;return Object.assign(i,{BASE_URL:r.prepared.baseUrl,FORCE_COLOR:"0",NO_COLOR:"1",PLAYWRIGHT_BROWSERS_PATH:t.installation.registry.canonicalValue,PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD:"1",TESTKIT_ACTIVE:"1",TESTKIT_INTERNAL_FILE_DEADLINE_MS:String(o.deadlineMs),TESTKIT_INTERNAL_FILE_TIMEOUT_SECONDS:String(Math.max(1,Math.ceil(o.timeoutMs/1e3))),TESTKIT_INVOCATION_ID:e.invocationId,TESTKIT_LEASE_DIR:o.orchestration.lease.directory,TESTKIT_LEASE_ID:o.orchestration.lease.id,TESTKIT_MODE:o.orchestration.mode,...o.orchestration.runtimeId===void 0?{}:{TESTKIT_RUNTIME_ID:o.orchestration.runtimeId},TESTKIT_TEST_FILE:o.file,TESTKIT_TEST_ID:o.id}),i.NODE_OPTIONS=at(s,a),A(i),i}function at(e,t){return["--experimental-strip-types",`--require=${JSON.stringify(e)}`,`--import=${JSON.stringify($(t).href)}`].join(" ")}export function playwrightSpawnEnvironmentBytes(e){return Object.entries(e).reduce((t,[r,i])=>t+(process.platform==="win32"?(r.length+i.length+2)*2:Buffer.byteLength(r,"utf8")+Buffer.byteLength(i,"utf8")+2),0)}function A(e){if(Object.keys(e).length>k)throw new Error(`Playwright physical spawn environment exceeds its ${k.toString()} entry limit`);if(playwrightSpawnEnvironmentBytes(e)>PLAYWRIGHT_SPAWN_ENVIRONMENT_MAX_BYTES)throw new Error(`Playwright physical spawn environment exceeds its ${PLAYWRIGHT_SPAWN_ENVIRONMENT_MAX_BYTES.toString()} byte limit`)}function st(e,t){const r=e.target.endpoints[t];if(typeof r!="string"||r.trim().length===0)throw new Error(`Playwright task service ${t} does not have a published endpoint`);let i;try{i=new URL(r)}catch(o){throw new Error(`Playwright task service ${t} endpoint is not a valid URL`,{cause:o})}if(i.protocol!=="http:"&&i.protocol!=="https:")throw new Error(`Playwright task service ${t} endpoint must use http or https`);return i.href}function ct(e,t){return{...Z({engineId:"playwright",installationKind:"root-playwright-family",profile:e.profile}),browserName:"chromium",packages:{playwright:t.family.playwright.version,playwrightCore:t.family.playwrightCore.version,playwrightTest:t.family.playwrightTest.version},registryMode:t.registry.mode}}export function playwrightPreparationFailure(e,t){if(t.length===0)return{...P(e,"PLAYWRIGHT_PREPARE_FAILED"),classification:e instanceof L||e instanceof J?"product":"infra"};const r=h(e);return{classification:"infra",code:"PLAYWRIGHT_PREPARE_CLEANUP_FAILED",details:{cleanupFailures:t.map(i=>i.message),preparationFailure:r.message},message:`Playwright task preparation failed and its partial workspace could not be removed: ${t.map(i=>i.message).join("; ")}`,...r.stack===void 0?{}:{stack:r.stack}}}async function lt(e){try{return await e()}catch(t){throw new L(t)}}async function pt(e,t){const r=[];for(const i of e)try{await t.removeOwnedDirectory(i)}catch(o){r.push({directory:i,error:h(o)})}return r}
1
+ import{makeTempDirAsync as D}from"../../../process/files/temp-dir.js";import{classifyManagedProcessResult as F,terminalOutcome as T,processOutputEvidence as N,failureFromError as P,firstOutputLine as R,processExitDetails as C,processExitFailure as x,toError as h,executeEngineTasks as M,managedProcessFailure as b,emptyEvidence as W,taskTimeoutFailure as B}from"../../adapters/execution.js";import{requireRootDirectory as Y,requireAbsoluteWorkspacePath as O,requireConsumerFile as H,safeSegment as S}from"../../adapters/workspace.js";import u from"node:fs/promises";import U from"node:path";import{setTimeout as $}from"node:timers/promises";import{pathToFileURL as K}from"node:url";import{isEngineTaskDeadlineExceededReason as G,resolveEngineTaskInputs as V}from"../../api/index.js";import{createNativeInvocationProcessEnvironment as j,runManagedProcess as J}from"../../../process/index.js";import{PlaywrightConsumerConfigurationError as z,playwrightExactFileFilter as X,writePlaywrightTaskWorkspace as Q}from"./config.js";import{collectPlaywrightEvidence as Z}from"./evidence.js";import{engineEvidenceIdentity as q}from"../../adapters/evidence-identity.js";import{getNodeStartupCacheEnvironment as rr}from"../../adapters/node-startup-cache.js";const y={codePrefix:"PLAYWRIGHT",label:"Playwright"};export const PLAYWRIGHT_SPAWN_ENVIRONMENT_MAX_BYTES=24*1024;const k=64;class L extends Error{constructor(r){const e=h(r);super(e.message,{cause:e}),this.name="PlaywrightConsumerPreparationError"}}export const playwrightAdapterDependencies={createInvocationEnvironment:j,removeOwnedDirectory:ir,writeTaskWorkspace:Q};const tr=new Set(["EBUSY","ENOTEMPTY","EPERM"]),er=5;async function ir(t){for(let r=0;;r+=1)try{await u.rm(t,{force:!0,recursive:!0});return}catch(e){const i=e instanceof Error&&"code"in e?e.code:void 0;if(r===er||typeof i!="string"||!tr.has(i))throw e;await $((r+1)*100)}}export async function executePlaywrightEngine(t,r,e,i=playwrightAdapterDependencies){const o=await or(t,r,i,e);return M(t,o.tasks,y,(a,s)=>nr(t,o,a,s),lr(t,o.installation))}async function or(t,r,e,i){const o=await Y(t.workspace.rootDirectory);O(t.workspace.temporaryDirectory,"temporary directory"),O(t.workspace.artifactDirectory,"artifact directory"),await u.mkdir(t.workspace.temporaryDirectory,{recursive:!0}),await u.mkdir(t.workspace.artifactDirectory,{recursive:!0});const a=await u.realpath(t.workspace.temporaryDirectory),s=await u.realpath(t.workspace.artifactDirectory),n=await D(`playwright-${S(t.invocationId)}-`,{parentDir:a});i.defer(()=>e.removeOwnedDirectory(n));const l=await e.createInvocationEnvironment(n);i.defer(()=>e.removeOwnedDirectory(l.temporaryDirectory));const p=[];for(const[g,c]of t.tasks.entries()){const I=V(t,c),v=`${g.toString().padStart(3,"0")}-${S(c.id)}`,_=U.join(n,v),f=await D(`${v}-`,{parentDir:s});try{const m=cr(t,c.serviceName),E=await pr(()=>H(o,c.file,"Playwright task file")),w=await e.writeTaskWorkspace({artifactDirectory:f,baseUrl:m,browserFile:r.browserExecutable,endpoints:t.target.endpoints,inputs:I,invocationId:t.invocationId,managedModuleEntrypoints:{authoring:r.authoringEntrypoints.authoring,cli:r.family.cli,playwright:r.family.playwright.entrypoint,playwrightCore:r.family.playwrightCore.entrypoint,playwrightTest:r.family.playwrightTest.entrypoint,root:r.authoringEntrypoints.root,rootPackageJson:r.authoringEntrypoints.rootPackageJson},moduleFile:r.family.playwrightTest.entrypoint,orchestration:c.orchestration,rootDirectory:o,serviceName:c.serviceName,sourceFile:E,taskDirectory:_,taskId:c.id});i.defer(async()=>{try{await e.removeOwnedDirectory(w.attachmentDirectory)}catch(d){if(!(d instanceof Error&&"code"in d&&d.code==="ENOTDIR"))throw d}}),p.push({artifactDirectory:f,inputs:I,task:c,preparationFailure:null,prepared:{baseUrl:m,sourceFile:E,workspace:w}})}catch(m){const E=(await ur([_,f],e)).map(w=>w.error);p.push({artifactDirectory:f,inputs:I,task:c,preparationFailure:playwrightPreparationFailure(m,E),prepared:null})}}return Object.freeze({installation:r,invocationEnvironment:l.environment,rootDirectory:o,tasks:Object.freeze(p)})}async function nr(t,r,e,i){const a={...await rr(t.workspace.cacheDirectory,e.inputs.environment),...ar(t,r,e)};try{A(a)}catch{delete a.NODE_COMPILE_CACHE}const s=e.task.deadlineMs-Date.now();if(s<=0)return T(e.task.id,"failed",i,W(),B(e.task.timeoutMs,y));const n=await J({args:[r.installation.family.cli,"test",X(e.prepared.sourceFile),"--config",e.prepared.workspace.configFile],cwd:r.rootDirectory,displayLabel:"Playwright",env:a,executable:process.execPath,signal:t.signal,timeoutMs:s});let l;try{l=await Z({identity:{engineId:"playwright",invocationId:t.invocationId,taskId:e.task.id,sourceFile:e.prepared.sourceFile},artifactDirectory:e.artifactDirectory,outputDirectory:e.prepared.workspace.outputDirectory,reportFile:e.prepared.workspace.reportFile,rootDirectory:r.rootDirectory,sourceFile:e.prepared.sourceFile,stderr:n.stderr,stdout:n.stdout})}catch(g){return T(e.task.id,"failed",i,N(n),playwrightEvidenceCollectionFailure(n,g))}const p=classifyPlaywrightProcessResult(n,l,e.task.timeoutMs,G(t.signal.reason));return T(e.task.id,p.status,i,l.evidence,p.failure)}export function classifyPlaywrightProcessResult(t,r,e,i){const o=F(t,e,i,y);if(o!==null)return o;if(r.outputLimit!==null){const n=r.outputLimit;return{failure:{code:"PLAYWRIGHT_OUTPUT_ARTIFACT_LIMIT",details:{...n},message:`Playwright output exceeded its ${n.limit.toString()} ${n.kind==="bytes"?"byte aggregate":"entry"} artifact limit`},status:"failed"}}const a=r.attachmentFailures.find(n=>n.code==="PLAYWRIGHT_ATTACHMENT_LIMIT");if(a!==void 0)return{failure:a,status:"failed"};if(r.report.failure!==null){const n=r.report.failure;return{...r.report,failure:{...n,details:{...n.details,evidenceErrors:r.reportError===null?0:1,...C(t)}}}}const s=r.attachmentFailures.at(0);return s!==void 0?{failure:s,status:"failed"}:t.exitCode!==0?x(t,y,R(t.stderr)??R(t.stdout)):r.reportError!==null?{failure:{...P(r.reportError,"PLAYWRIGHT_REPORT_INVALID"),classification:"infra"},status:"failed"}:r.report}export function playwrightEvidenceCollectionFailure(t,r){const e=h(r);return b(t,y,e)??{...P(e,"PLAYWRIGHT_EVIDENCE_FAILED"),classification:"infra"}}function ar(t,r,e){const i={...r.invocationEnvironment},o=e.task,a=e.prepared.workspace.inputBootstrapFile,s=e.prepared.workspace.resolutionBootstrapFile;return Object.assign(i,{BASE_URL:e.prepared.baseUrl,FORCE_COLOR:"0",NO_COLOR:"1",PLAYWRIGHT_BROWSERS_PATH:r.installation.registry.canonicalValue,PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD:"1",TESTKIT_ACTIVE:"1",TESTKIT_INTERNAL_FILE_DEADLINE_MS:String(o.deadlineMs),TESTKIT_INTERNAL_FILE_TIMEOUT_SECONDS:String(Math.max(1,Math.ceil(o.timeoutMs/1e3))),TESTKIT_INVOCATION_ID:t.invocationId,TESTKIT_LEASE_DIR:o.orchestration.lease.directory,TESTKIT_LEASE_ID:o.orchestration.lease.id,TESTKIT_MODE:o.orchestration.mode,...o.orchestration.runtimeId===void 0?{}:{TESTKIT_RUNTIME_ID:o.orchestration.runtimeId},TESTKIT_TEST_FILE:o.file,TESTKIT_TEST_ID:o.id}),i.NODE_OPTIONS=sr(s,a),A(i),i}function sr(t,r){return["--experimental-strip-types",`--require=${JSON.stringify(t)}`,`--import=${JSON.stringify(K(r).href)}`].join(" ")}export function playwrightSpawnEnvironmentBytes(t){return Object.entries(t).reduce((r,[e,i])=>r+(process.platform==="win32"?(e.length+i.length+2)*2:Buffer.byteLength(e,"utf8")+Buffer.byteLength(i,"utf8")+2),0)}function A(t){if(Object.keys(t).length>k)throw new Error(`Playwright physical spawn environment exceeds its ${k.toString()} entry limit`);if(playwrightSpawnEnvironmentBytes(t)>PLAYWRIGHT_SPAWN_ENVIRONMENT_MAX_BYTES)throw new Error(`Playwright physical spawn environment exceeds its ${PLAYWRIGHT_SPAWN_ENVIRONMENT_MAX_BYTES.toString()} byte limit`)}function cr(t,r){const e=t.target.endpoints[r];if(typeof e!="string"||e.trim().length===0)throw new Error(`Playwright task service ${r} does not have a published endpoint`);let i;try{i=new URL(e)}catch(o){throw new Error(`Playwright task service ${r} endpoint is not a valid URL`,{cause:o})}if(i.protocol!=="http:"&&i.protocol!=="https:")throw new Error(`Playwright task service ${r} endpoint must use http or https`);return i.href}function lr(t,r){return{...q({engineId:"playwright",installationKind:"root-playwright-family",profile:t.profile}),browserName:"chromium",packages:{playwright:r.family.playwright.version,playwrightCore:r.family.playwrightCore.version,playwrightTest:r.family.playwrightTest.version},registryMode:r.registry.mode}}export function playwrightPreparationFailure(t,r){if(r.length===0)return{...P(t,"PLAYWRIGHT_PREPARE_FAILED"),classification:t instanceof L||t instanceof z?"product":"infra"};const e=h(t);return{classification:"infra",code:"PLAYWRIGHT_PREPARE_CLEANUP_FAILED",details:{cleanupFailures:r.map(i=>i.message),preparationFailure:e.message},message:`Playwright task preparation failed and its partial workspace could not be removed: ${r.map(i=>i.message).join("; ")}`,...e.stack===void 0?{}:{stack:e.stack}}}async function pr(t){try{return await t()}catch(r){throw new L(r)}}async function ur(t,r){const e=[];for(const i of t)try{await r.removeOwnedDirectory(i)}catch(o){e.push({directory:i,error:h(o)})}return e}
@@ -3,6 +3,7 @@ export declare const MAX_ATTACHMENT_BYTES: number;
3
3
  export declare const MAX_TOTAL_ATTACHMENT_BYTES: number;
4
4
  export interface PlaywrightReporterOptions extends NativeReportOptions {
5
5
  readonly attachmentDirectory: string;
6
+ readonly failOnFlakyTests: boolean;
6
7
  }
7
8
  export type PlaywrightAttachmentError = {
8
9
  readonly kind: "individual" | "total";
@@ -1,4 +1,4 @@
1
- import{ownedRuntimeInputModuleUrl as w}from"../../runtime-input-file.js";import b from"node:crypto";import n from"node:fs/promises";import o from"node:path";import{fileURLToPath as j,pathToFileURL as g}from"node:url";import{managedNodeModuleResolutionBootstrapSource as P}from"../../managed-module-resolution.js";import{materializePlaywrightRuntimeInput as v,playwrightRuntimeInputBootstrapSource as T}from"../runtime/runtime-input.js";import{nativeReporterFile as k}from"../../adapters/native-report.js";export class PlaywrightConsumerConfigurationError extends Error{constructor(r){const t=r instanceof Error?r:new Error(String(r));super(t.message,{cause:t}),this.name="PlaywrightConsumerConfigurationError"}}export function playwrightExactFileFilter(e){return d(e)}export async function writePlaywrightTaskWorkspace(e){let r;try{F(e.inputs.supportModules),r=await D(e.rootDirectory,e.inputs)}catch(m){throw new PlaywrightConsumerConfigurationError(m)}await n.mkdir(e.taskDirectory,{recursive:!0}),await n.mkdir(e.artifactDirectory,{recursive:!0});const t=b.randomUUID(),i=o.join(e.taskDirectory,`playwright-${t}.config.mjs`),a=o.join(e.taskDirectory,`playwright-${t}.input-bootstrap.mjs`),l=o.join(e.taskDirectory,`playwright-${t}.resolution-bootstrap.cjs`),f=o.join(e.taskDirectory,"testkit-playwright-report.json"),p=o.join(e.artifactDirectory,"playwright-output");await n.mkdir(p);const c=o.join(e.artifactDirectory,"playwright-attachments");await n.mkdir(c,{mode:448,recursive:!1});const u=await v({baseUrl:e.baseUrl,directory:e.taskDirectory,endpoints:e.endpoints,inputs:e.inputs,invocationId:e.invocationId,orchestration:e.orchestration,taskId:e.taskId}),y=k("playwright");return await h(l,playwrightResolutionBootstrapSource(e.managedModuleEntrypoints,{consumerRoots:[e.rootDirectory],trustedImporterFiles:[i,a],trustedInternalEntrypoints:[e.moduleFile,y,j(w)]})),await h(a,T({digest:u.digest,file:u.file,sizeBytes:u.sizeBytes,invocationId:e.invocationId,taskId:e.taskId})),await h(i,x({baseConfigFile:r,baseUrl:e.baseUrl,browserFile:e.browserFile,inputBootstrapFile:a,outputDirectory:p,reporterFile:y,report:{attachmentDirectory:c,identity:{engineId:"playwright",invocationId:e.invocationId,taskId:e.taskId,sourceFile:e.sourceFile},reportFile:f,workspaceDirectory:e.taskDirectory},rootDirectory:e.rootDirectory,sourceFile:e.sourceFile})),{attachmentDirectory:c,configFile:i,inputBootstrapFile:a,outputDirectory:p,reportFile:f,resolutionBootstrapFile:l}}export function playwrightResolutionBootstrapSource(e,r){const t={authoring:s(e.authoring,"authoring"),cli:s(e.cli,"cli"),playwright:s(e.playwright,"playwright"),playwrightCore:s(e.playwrightCore,"playwrightCore"),playwrightTest:s(e.playwrightTest,"playwrightTest"),root:s(e.root,"root"),rootPackageJson:s(e.rootPackageJson,"rootPackageJson")},i={"@playwright/test":t.playwrightTest,playwright:t.playwright,"playwright-core":t.playwrightCore};return P({consumerRoots:r.consumerRoots,direct:{"@gfdelarue/testkit":t.root,"@gfdelarue/testkit/package.json":t.rootPackageJson,"@gfdelarue/testkit/playwright":t.authoring},directBridgeRoots:[o.dirname(t.root)],engineLabel:"Playwright",forbiddenConsumerPackagePrefixes:[],packageAnchors:i,reservedPackageNames:["@gfdelarue/testkit"],stateSymbol:"@gfdelarue/testkit/playwright-resolution-hooks/v1",trustedImporterFiles:r.trustedImporterFiles,trustedInternalEntrypoints:r.trustedInternalEntrypoints,verifiedParentlessPackageFiles:{playwright:["lib/common/process.js"]},verifiedProcessEntrypoints:[t.cli]})}function s(e,r){if(!o.isAbsolute(e))throw new Error(`Resolved Playwright installation ${r} entrypoint must be absolute`);return e}function x(e){return`
1
+ import{ownedRuntimeInputModuleUrl as w}from"../../runtime-input-file.js";import b from"node:crypto";import n from"node:fs/promises";import o from"node:path";import{fileURLToPath as j,pathToFileURL as g}from"node:url";import{managedNodeModuleResolutionBootstrapSource as P}from"../../managed-module-resolution.js";import{materializePlaywrightRuntimeInput as v,playwrightRuntimeInputBootstrapSource as T}from"../runtime/runtime-input.js";import{nativeReporterFile as k}from"../../adapters/native-report.js";export class PlaywrightConsumerConfigurationError extends Error{constructor(r){const t=r instanceof Error?r:new Error(String(r));super(t.message,{cause:t}),this.name="PlaywrightConsumerConfigurationError"}}export function playwrightExactFileFilter(e){return d(e)}export async function writePlaywrightTaskWorkspace(e){let r;try{x(e.inputs.supportModules),r=await O(e.rootDirectory,e.inputs)}catch(m){throw new PlaywrightConsumerConfigurationError(m)}await n.mkdir(e.taskDirectory,{recursive:!0}),await n.mkdir(e.artifactDirectory,{recursive:!0});const t=b.randomUUID(),i=o.join(e.taskDirectory,`playwright-${t}.config.mjs`),a=o.join(e.taskDirectory,`playwright-${t}.input-bootstrap.mjs`),l=o.join(e.taskDirectory,`playwright-${t}.resolution-bootstrap.cjs`),f=o.join(e.taskDirectory,"testkit-playwright-report.json"),p=o.join(e.artifactDirectory,"playwright-output");await n.mkdir(p);const c=o.join(e.artifactDirectory,"playwright-attachments");await n.mkdir(c,{mode:448,recursive:!1});const u=await v({baseUrl:e.baseUrl,directory:e.taskDirectory,endpoints:e.endpoints,inputs:e.inputs,invocationId:e.invocationId,orchestration:e.orchestration,taskId:e.taskId}),y=k("playwright");return await h(l,playwrightResolutionBootstrapSource(e.managedModuleEntrypoints,{consumerRoots:[e.rootDirectory],trustedImporterFiles:[i,a],trustedInternalEntrypoints:[e.moduleFile,y,j(w)]})),await h(a,T({digest:u.digest,file:u.file,sizeBytes:u.sizeBytes,invocationId:e.invocationId,taskId:e.taskId})),await h(i,F({baseConfigFile:r,baseUrl:e.baseUrl,browserFile:e.browserFile,inputBootstrapFile:a,outputDirectory:p,reporterFile:y,report:{attachmentDirectory:c,identity:{engineId:"playwright",invocationId:e.invocationId,taskId:e.taskId,sourceFile:e.sourceFile},reportFile:f,workspaceDirectory:e.taskDirectory},rootDirectory:e.rootDirectory,sourceFile:e.sourceFile})),{attachmentDirectory:c,configFile:i,inputBootstrapFile:a,outputDirectory:p,reportFile:f,resolutionBootstrapFile:l}}export function playwrightResolutionBootstrapSource(e,r){const t={authoring:s(e.authoring,"authoring"),cli:s(e.cli,"cli"),playwright:s(e.playwright,"playwright"),playwrightCore:s(e.playwrightCore,"playwrightCore"),playwrightTest:s(e.playwrightTest,"playwrightTest"),root:s(e.root,"root"),rootPackageJson:s(e.rootPackageJson,"rootPackageJson")},i={"@playwright/test":t.playwrightTest,playwright:t.playwright,"playwright-core":t.playwrightCore};return P({consumerRoots:r.consumerRoots,direct:{"@gfdelarue/testkit":t.root,"@gfdelarue/testkit/package.json":t.rootPackageJson,"@gfdelarue/testkit/playwright":t.authoring},directBridgeRoots:[o.dirname(t.root)],engineLabel:"Playwright",forbiddenConsumerPackagePrefixes:[],packageAnchors:i,reservedPackageNames:["@gfdelarue/testkit"],stateSymbol:"@gfdelarue/testkit/playwright-resolution-hooks/v1",trustedImporterFiles:r.trustedImporterFiles,trustedInternalEntrypoints:r.trustedInternalEntrypoints,verifiedParentlessPackageFiles:{playwright:["lib/common/process.js"]},verifiedProcessEntrypoints:[t.cli]})}function s(e,r){if(!o.isAbsolute(e))throw new Error(`Resolved Playwright installation ${r} entrypoint must be absolute`);return e}function F(e){return`
2
2
  import path from "node:path";
3
3
  import { createRequire } from "node:module";
4
4
 
@@ -92,7 +92,7 @@ function normalizeReporters(value) {
92
92
  : value;
93
93
  return [
94
94
  ...entries.map(normalizeReporterEntry).filter(Boolean),
95
- [OPTIONS.reporterFile, OPTIONS.report],
95
+ [OPTIONS.reporterFile, { ...OPTIONS.report, failOnFlakyTests: Boolean(base.failOnFlakyTests) }],
96
96
  ];
97
97
  }
98
98
 
@@ -164,4 +164,4 @@ export default {
164
164
  fullyParallel: false,
165
165
  workers: 1,
166
166
  };
167
- `}function d(e){return`^${e.split(/[\\/]+/u).map(r=>r.replace(/[.*+?^${}()|[\]\\]/gu,"\\$&")).join("[\\\\/]")}$`}async function D(e,r){if(!Object.hasOwn(r.supportModules,"playwrightConfig"))return null;const t=r.supportModules.playwrightConfig;if(!o.isAbsolute(t))throw new Error("supportModules.playwrightConfig must be an absolute path");return I(e,t,"supportModules.playwrightConfig")}function F(e){const r=Object.keys(e).filter(t=>t!=="playwrightConfig");if(r.length>0)throw new Error(`Playwright engine does not support support module(s): ${r.join(", ")}`)}async function I(e,r,t){const i=o.resolve(e,r),a=await n.realpath(i);if(!O(e,a))throw new Error(`${t} must stay within the consumer root`);if(!(await n.stat(a)).isFile())throw new Error(`${t} must be a regular file`);return a}function O(e,r){const t=o.relative(e,r);return t===""||!t.startsWith("..")&&!o.isAbsolute(t)}async function h(e,r){const t=await n.open(e,"wx",384);try{await t.writeFile(r,"utf8")}finally{await t.close()}}
167
+ `}function d(e){return`^${e.split(/[\\/]+/u).map(r=>r.replace(/[.*+?^${}()|[\]\\]/gu,"\\$&")).join("[\\\\/]")}$`}async function O(e,r){if(!Object.hasOwn(r.supportModules,"playwrightConfig"))return null;const t=r.supportModules.playwrightConfig;if(!o.isAbsolute(t))throw new Error("supportModules.playwrightConfig must be an absolute path");return D(e,t,"supportModules.playwrightConfig")}function x(e){const r=Object.keys(e).filter(t=>t!=="playwrightConfig");if(r.length>0)throw new Error(`Playwright engine does not support support module(s): ${r.join(", ")}`)}async function D(e,r,t){const i=o.resolve(e,r),a=await n.realpath(i);if(!I(e,a))throw new Error(`${t} must stay within the consumer root`);if(!(await n.stat(a)).isFile())throw new Error(`${t} must be a regular file`);return a}function I(e,r){const t=o.relative(e,r);return t===""||!t.startsWith("..")&&!o.isAbsolute(t)}async function h(e,r){const t=await n.open(e,"wx",384);try{await t.writeFile(r,"utf8")}finally{await t.close()}}
@@ -1 +1 @@
1
- import P from"node:fs/promises";import d from"node:path";import{evidenceArtifact as A,evidenceFileAccess as J,readStableJsonFile as V,snapshotStableEvidenceFile as z,stableEvidenceFromBuffer as K,writeOwnedEvidenceFile as D}from"../../adapters/evidence-files.js";import{materializePlaywrightFailureDetails as Q}from"./failure-details.js";import{isMissingFileError as Z,isPathWithin as _,nonNegativeInteger as v,nonNegativeNumber as B,optionalNonEmptyString as $,optionalNonNegativeInteger as C,portableRelativePath as Y,portableReportPath as H,requireNonEmptyString as b,requireRecord as S,toError as q}from"./evidence-validation.js";import{readNativeReport as tt}from"../../adapters/native-report.js";import{attachmentLimitError as et,attachmentIoError as it,MAX_ATTACHMENT_BYTES as G,MAX_TOTAL_ATTACHMENT_BYTES as rt}from"./attachments.js";const nt=64*1024*1024,W=1e4,F=512*1024*1024,at=2*1024*1024,X=128,st=".testkit-playwright-evidence",ot=/(?:^|\n)(?:Error:\s*)?No tests found(?:\.|\n|$)/u;class M extends Error{limit;constructor(t){super(gt(t)),this.limit=t,this.name="PlaywrightOutputArtifactLimitError"}}export async function collectPlaywrightEvidence(e,t=J){await P.mkdir(e.artifactDirectory,{recursive:!0});const c=await P.realpath(e.artifactDirectory),s=await P.realpath(e.rootDirectory),a=d.join(c,st);await P.mkdir(a,{mode:448,recursive:!1});const i=[],l=[];for(const o of["stdout","stderr"]){const g=e[o];if(g.length===0)continue;i.push({stream:o,text:g});const y=K(Buffer.from(g,"utf8")),E=await D(a,`${o}.log`,y.contents);l.push(A(E,y,"text/plain; charset=utf-8","engine.process-output",`playwright-${o}`,`${y.sizeBytes.toString()} bytes`))}let u=null,p=null,h=new Set,f=[];try{const o=await V(e.reportFile,nt,"Testkit native Playwright report",t);if(o.file===null)u=new Error("Playwright did not produce its Testkit native report");else{p=lt(tt(o.value,e.identity).result);const g=await D(a,"testkit-playwright-report.json",o.file.contents);l.push(A(g,o.file,"application/json","engine.testkit-playwright-report","testkit-playwright-report","Testkit native Playwright report")),i.push(...p.logs);const y=await Q(a,p.failureDetails);y!==null&&l.push(y);const E=await dt(c,a,s,p.attachments,t);l.push(...E.artifacts),h=E.sourcePaths,f=E.failures}}catch(o){u=q(o)}let r={artifacts:[],bytes:0,entries:0},n=null;try{r=await ht(c,a,e.outputDirectory,h,t),l.push(...r.artifacts)}catch(o){if(!(o instanceof M))throw o;n=o.limit,i.push({stream:"engine",text:o.message})}const m=ct(p,s,e.sourceFile),w=n??r;return{attachmentFailures:f,evidence:{artifacts:l,logs:i,metrics:[...Object.entries(p?.stats??{}).map(([o,g])=>({name:o==="duration"?"playwright.duration":`playwright.tests.${o}`,unit:o==="duration"?"milliseconds":"tests",value:g})),...["bytes","entries"].map(o=>({name:`playwright.output.${o}`,unit:o,value:w[o]}))],...p===null?{}:{native:{playwright:{attachmentFailures:f.map(o=>({...o})),specs:p.specs,stats:p.stats}}}},outputLimit:n,report:m,reportError:u}}function ct(e,t,c){if(e===null)return{failure:null,status:"failed"};const s=e.specs.filter((i,l)=>d.relative(d.normalize(c),e.sourceFiles[l])===""),a=e.failureMessages.length>0&&e.failureMessages.every(i=>ot.test(i));return e.stats.unexpected>0||e.failureMessages.length>0&&(!a||s.length>0)?{failure:{...e.failureMessages.some(i=>/browserType\.launch(?:PersistentContext)?:/u.test(i)&&/Executable doesn't exist|Host system is missing dependencies|error while loading shared libraries|Failed to launch/u.test(i))?{classification:"infra"}:{},code:"PLAYWRIGHT_SPECS_FAILED",details:{stats:e.stats,specs:e.specs.slice(0,50)},message:e.failureMessages.at(0)??`${e.stats.unexpected.toString()} Playwright test(s) failed`},status:"failed"}:s.length>0?{failure:null,status:s.every(i=>i.status==="skipped")?"skipped":"passed"}:{failure:{code:"PLAYWRIGHT_NO_TESTS",details:{file:Y(t,c),reportedSpecs:e.specs.slice(0,50),stats:e.stats},message:"Selected Playwright task did not execute any tests"},status:"failed"}}function lt(e){const t=S(e,"Playwright native report result"),c=mt(t.stats),s=N(t.errors,"errors"),a=[],i=[],l=[],u=new Set,p={expected:0,flaky:0,skipped:0,unexpected:0};for(const r of k(t.specs,"specs")){const n=S(r,"Playwright native report spec"),m=b(n.id,"Playwright native spec.id");if(u.has(m))throw new Error("Playwright native report contains a duplicate test identity");u.add(m);const w=b(n.title,"Playwright native spec.title"),o=N(n.parents,"spec.parents");o.length>0&&(o[0]=H(o[0]));const g=H(b(n.file,"Playwright native spec.file")),y=b(n.sourceFile,"Playwright native spec.sourceFile");if(!d.isAbsolute(y)&&!d.win32.isAbsolute(y))throw new Error("Playwright native spec.sourceFile must be absolute");const E=x(n.projectName,"spec.projectName"),T=n.status;if(T!=="expected"&&T!=="flaky"&&T!=="skipped"&&T!=="unexpected")throw new Error("Playwright native spec.status is invalid");p[T]+=1;const I=N(n.failureMessages,"spec.failureMessages"),L=n.failureStack===null?null:x(n.failureStack,"spec.failureStack"),j=C(n.line,"Playwright native spec.line"),O=C(n.column,"Playwright native spec.column");T==="unexpected"&&(s.push(...I),l.push({kind:"playwright-spec",key:w,title:w,count:1,message:I[0]??`Playwright reported "${w}" as an unexpected failure`,...o.length===0?{}:{suitePath:o},location:{path:g,...j===null?{}:{line:j},...O===null?{}:{column:O}},...L===null?{}:{stack:L}})),i.push(y),a.push({durationMs:B(n.durationMs,"Playwright native spec.durationMs"),retries:v(n.retries,"Playwright native spec.retries"),file:g,projectName:E,status:T,title:[...o,w].filter(Boolean).join(" > ")})}for(const r of["expected","flaky","skipped","unexpected"])if(c[r]!==p[r])throw new Error("Playwright native report stats do not match its cases");const h=k(t.logs,"logs").map(r=>{const n=S(r,"Playwright native report log");if(n.stream!=="stdout"&&n.stream!=="stderr")throw new Error("Playwright native report log stream is invalid");return{stream:n.stream,text:x(n.text,"log.text")}});return{attachments:k(t.attachments,"attachments").map((r,n)=>ut(r,n,u)),failureDetails:l,failureMessages:s,logs:wt(h),sourceFiles:i,specs:a,stats:c}}function k(e,t){if(!Array.isArray(e))throw new Error(`Playwright native report ${t} must be an array`);return e}function N(e,t){return k(e,t).map(c=>x(c,t))}function x(e,t){if(typeof e!="string")throw new Error(`Playwright native report ${t} must be a string`);return e}function ut(e,t,c){const s=S(e,`Playwright attachment[${t.toString()}]`),a=b(s.specId,"Playwright attachment.specId");if(!c.has(a))throw new Error("Playwright native attachment references an unknown test identity");const i=$(s.path),l=s.error===void 0?void 0:pt(s.error);if(s.body!==void 0||i===null&&l===void 0)throw new Error("Playwright attachment must contain a path or an error");return{contentType:$(s.contentType)??"application/octet-stream",name:$(s.name)??`attachment-${t.toString()}`,specId:a,...i===null?{}:{path:i},...l===void 0?{}:{error:l},retry:v(s.retry,"Playwright attachment.retry"),specTitle:b(s.specTitle,"Playwright attachment.specTitle")}}function pt(e){const t=S(e,"Playwright attachment.error");if(t.kind==="io")return{kind:"io",message:b(t.message,"Playwright attachment.error.message")};const c=t.kind;if(c!=="individual"&&c!=="total")throw new Error("Playwright attachment.error has an invalid kind");const s=c==="individual"?G:rt,a=v(t.observed,"Playwright attachment.error.observed");if(t.limit!==s||a<=s)throw new Error("Playwright attachment.error has an invalid limit");return{kind:c,limit:s,observed:a}}function R(e,t){const{name:c,specId:s,specTitle:a,retry:i}=e,l=t.kind==="io"?t.message:`exceeds its ${t.limit.toString()} byte ${t.kind} limit (observed ${t.observed.toString()} bytes)`;return{code:t.kind==="io"?"PLAYWRIGHT_ATTACHMENT_FAILED":"PLAYWRIGHT_ATTACHMENT_LIMIT",...t.kind==="io"?{classification:"infra"}:{},details:{name:c,specId:s,specTitle:a,retry:i,...t},message:`Playwright attachment "${c}" (${a}, retry ${i.toString()}): ${l}`}}async function dt(e,t,c,s,a){const i=[],l=[],u=new Set;let p=0;const h=d.join(t,"attachments");s.length>0&&await P.mkdir(h,{mode:448,recursive:!1});for(const[f,r]of s.entries())try{if(r.error!==void 0){r.path!==void 0&&u.add(d.resolve(c,r.path)),l.push(R(r,r.error));continue}const n=await yt(r.path,c,e,a);u.add(n.file);const m=v(Number(n.identity.size),"Playwright attachment size"),w=et(m,p+m);if(w!==null){l.push(R(r,w));continue}const o=await z(n.file,G,"Playwright attachment",a,n.identity,h,`${f.toString().padStart(4,"0")}-${U(r.name)}${Pt(r)}`);p+=o.identity.sizeBytes,i.push({path:o.filePath,contentType:r.contentType,kind:"engine.playwright-attachment",name:r.name,summary:`${r.specTitle} retry ${r.retry.toString()}`,metadata:{...o.identity,specId:r.specId,specTitle:r.specTitle,retry:r.retry}})}catch(n){l.push(R(r,it(n)))}return{artifacts:i,failures:l,sourcePaths:u}}async function ht(e,t,c,s,a){try{const i=await a.lstat(c);if(!i.isDirectory()||i.isSymbolicLink())throw new Error("Playwright output directory must be a non-symbolic-link directory");const l=await P.realpath(c);if(!_(e,l))throw new Error("Playwright output directory escaped the artifact root");const u=await ft(l,s,a),p=[],h=d.join(t,"output");u.candidates.length>0&&await P.mkdir(h,{mode:448,recursive:!1});for(const[f,r]of u.candidates.entries()){const n=await z(r.file,F,"Playwright output file",a,r.identity,h,`${f.toString().padStart(5,"0")}-${U(d.basename(r.file))}${Et(r.file)}`);p.push(A(n.filePath,n.identity,vt(r.file),"engine.playwright-output",r.relativePath,"Playwright output"))}return{artifacts:p,bytes:u.bytes,entries:u.entries}}catch(i){if(Z(i))return{artifacts:[],bytes:0,entries:0};throw i}}async function ft(e,t,c){const s=[];let a=0,i=0;const l=[{depth:0,directory:e}];for(;l.length>0;){const u=l.pop();if(u===void 0)continue;if(u.depth>X)throw new Error(`Playwright output exceeds the ${X.toString()} level directory depth limit`);const p=u.directory,h=await P.readdir(p,{withFileTypes:!0});h.sort((f,r)=>f.name.localeCompare(r.name));for(const f of h){if(i+=1,i>W)throw new M({bytes:a,entries:i,kind:"entries",limit:W,observed:i});const r=d.join(p,f.name),n=await c.lstat(r);if(n.isSymbolicLink())throw new Error("Playwright output must not contain symbolic links");if(n.isDirectory()){l.push({depth:u.depth+1,directory:r});continue}if(!n.isFile())continue;if(n.size<0n||n.size>BigInt(Number.MAX_SAFE_INTEGER))throw new Error("Playwright output file has an invalid size");const m=Number(n.size);if(a+=m,!Number.isSafeInteger(a)||a>F)throw new M({bytes:a,entries:i,kind:"bytes",limit:F,observed:a});t.has(r)||s.push({file:r,identity:n,relativePath:Y(e,r)})}}return{bytes:a,candidates:s,entries:i}}function gt(e){return e.kind==="bytes"?`Playwright output exceeds its ${e.limit.toString()} byte aggregate limit (observed ${e.observed.toString()} bytes)`:`Playwright output exceeds its ${e.limit.toString()} entry limit (observed ${e.observed.toString()} entries)`}async function yt(e,t,c,s){if(e===void 0)throw new Error("Playwright path attachment did not declare a path");const a=d.resolve(t,e),i=await s.lstat(a);if(!i.isFile()||i.isSymbolicLink())throw new Error("Playwright attachment path must be a regular non-symbolic-link file");const l=await P.realpath(a);if(!_(t,l)&&!_(c,l))throw new Error("Playwright attachment path must stay within the consumer or artifact root");return{file:l,identity:i}}function mt(e){const t=S(e,"Playwright report.stats");return{expected:v(t.expected,"Playwright stats.expected"),unexpected:v(t.unexpected,"Playwright stats.unexpected"),flaky:v(t.flaky,"Playwright stats.flaky"),skipped:v(t.skipped,"Playwright stats.skipped"),duration:B(t.duration,"Playwright stats.duration")}}function wt(e){let t=at;const c=[];for(const s of e){if(t<=0)break;const i=Buffer.from(s.text,"utf8").subarray(0,t).toString("utf8");i.length>0&&c.push({...s,text:i}),t-=Buffer.byteLength(i)}return t<=0&&c.push({stream:"engine",text:"Playwright report log evidence was truncated at its bounded limit"}),c}function Pt(e){if(e.path!==void 0){const t=d.extname(e.path);if(/^\.[a-z0-9]{1,12}$/iu.test(t))return t}return e.contentType==="application/json"?".json":e.contentType.startsWith("text/")?".txt":e.contentType==="image/png"?".png":e.contentType==="image/jpeg"?".jpg":".bin"}function vt(e){const t=d.extname(e).toLowerCase();return t===".json"?"application/json":t===".zip"?"application/zip":t===".png"?"image/png":t===".jpg"||t===".jpeg"?"image/jpeg":t===".webm"?"video/webm":[".log",".txt",".md"].includes(t)?"text/plain; charset=utf-8":"application/octet-stream"}function Et(e){const t=d.extname(e).toLowerCase();return/^\.[a-z0-9]{1,12}$/u.test(t)?t:".bin"}function U(e){const t=e.trim().toLowerCase().replace(/[^a-z0-9._-]+/gu,"-").replace(/^-+|-+$/gu,"").slice(0,80);return t.length>0?t:"attachment"}
1
+ import w from"node:fs/promises";import g from"node:path";import{evidenceArtifact as F,evidenceFileAccess as q,readStableJsonFile as tt,snapshotStableEvidenceFile as C,stableEvidenceFromBuffer as et,writeOwnedEvidenceFile as Y}from"../../adapters/evidence-files.js";import{materializePlaywrightFailureDetails as it}from"./failure-details.js";import{isMissingFileError as rt,isPathWithin as $,nonNegativeInteger as E,nonNegativeNumber as H,optionalNonEmptyString as N,optionalNonNegativeInteger as G,portableRelativePath as U,portableReportPath as W,requireNonEmptyString as b,requireRecord as S,toError as nt}from"./evidence-validation.js";import{readNativeReport as at}from"../../adapters/native-report.js";import{attachmentLimitError as st,attachmentIoError as ot,MAX_ATTACHMENT_BYTES as X,MAX_TOTAL_ATTACHMENT_BYTES as ct}from"./attachments.js";const lt=64*1024*1024,J=1e4,M=512*1024*1024,ut=2*1024*1024,V=128,pt=".testkit-playwright-evidence",dt=/(?:^|\n)(?:Error:\s*)?No tests found(?:\.|\n|$)/u,ht=["failed","interrupted","passed","timedout"];class R extends Error{limit;constructor(e){super(vt(e)),this.limit=e,this.name="PlaywrightOutputArtifactLimitError"}}export async function collectPlaywrightEvidence(t,e=q){await w.mkdir(t.artifactDirectory,{recursive:!0});const i=await w.realpath(t.artifactDirectory),c=await w.realpath(t.rootDirectory),n=g.join(i,pt);await w.mkdir(n,{mode:448,recursive:!1});const a=[],s=[];for(const r of["stdout","stderr"]){const l=t[r];if(l.length===0)continue;a.push({stream:r,text:l});const m=et(Buffer.from(l,"utf8")),y=await Y(n,`${r}.log`,m.contents);s.push(F(y,m,"text/plain; charset=utf-8","engine.process-output",`playwright-${r}`,`${m.sizeBytes.toString()} bytes`))}let p=null,d=null,f=new Set,h=[];try{const r=await tt(t.reportFile,lt,"Testkit native Playwright report",e);if(r.file===null)p=new Error("Playwright did not produce its Testkit native report");else{d=gt(at(r.value,t.identity).result,t.sourceFile);const l=await Y(n,"testkit-playwright-report.json",r.file.contents);s.push(F(l,r.file,"application/json","engine.testkit-playwright-report","testkit-playwright-report","Testkit native Playwright report")),a.push(...d.logs);const m=await it(n,d.failureDetails);m!==null&&s.push(m);const y=await wt(i,n,c,d.attachments,e);s.push(...y.artifacts),f=y.sourcePaths,h=y.failures}}catch(r){p=nt(r)}let o={artifacts:[],bytes:0,entries:0},u=null;try{o=await Pt(i,n,t.outputDirectory,f,e),s.push(...o.artifacts)}catch(r){if(!(r instanceof R))throw r;u=r.limit,a.push({stream:"engine",text:r.message})}const P=ft(d,c,t.sourceFile,h.length),k=u??o;return{attachmentFailures:h,evidence:{artifacts:s,logs:a,metrics:[...Object.entries(d?.stats??{}).map(([r,l])=>({name:r==="duration"?"playwright.duration":`playwright.tests.${r}`,unit:r==="duration"?"milliseconds":"tests",value:l})),...["bytes","entries"].map(r=>({name:`playwright.output.${r}`,unit:r,value:k[r]}))],...d===null?{}:{native:{playwright:{attachmentFailures:h.map(r=>({...r})),specs:d.specs,stats:d.stats}}}},outputLimit:u,report:P,reportError:p}}function ft(t,e,i,c){if(t===null)return{failure:null,status:"failed"};const n=t.specs.filter((s,p)=>K(i,t.sourceFiles[p])),a=t.failureMessages.length>0&&t.failureMessages.every(s=>dt.test(s));return t.stats.unexpected>0||t.failureMessages.length>0&&(!a||n.length>0)?{failure:{...t.failureMessages.some(s=>/browserType\.launch(?:PersistentContext)?:/u.test(s)&&/Executable doesn't exist|Host system is missing dependencies|error while loading shared libraries|Failed to launch/u.test(s))?{classification:"infra"}:{},code:"PLAYWRIGHT_SPECS_FAILED",details:{attachmentFailures:c,failOnFlakyTests:t.failOnFlakyTests,reportErrors:t.reportErrors,runStatus:t.runStatus,stats:t.stats,specs:t.specs.slice(0,50)},message:t.failureMessages.at(0)??`${t.stats.unexpected.toString()} Playwright test(s) failed`},status:"failed"}:n.length>0?{failure:null,status:n.every(s=>s.status==="skipped")?"skipped":"passed"}:{failure:{code:"PLAYWRIGHT_NO_TESTS",details:{file:U(e,i),reportedSpecs:t.specs.slice(0,50),stats:t.stats},message:"Selected Playwright task did not execute any tests"},status:"failed"}}function K(t,e){return g.relative(g.normalize(t),e)===""}function gt(t,e){const i=S(t,"Playwright native report result"),c=ht.find(r=>r===i.status);if(c===void 0)throw new Error("Playwright native report status is invalid");if(typeof i.failOnFlakyTests!="boolean")throw new Error("Playwright native report failOnFlakyTests must be a boolean");const n=i.failOnFlakyTests,a=bt(i.stats),s=O(i.errors,"errors"),p=s.length,d=[],f=[],h=[],o=new Set,u={expected:0,flaky:0,skipped:0,unexpected:0};for(const r of A(i.specs,"specs")){const l=S(r,"Playwright native report spec"),m=b(l.id,"Playwright native spec.id");if(o.has(m))throw new Error("Playwright native report contains a duplicate test identity");o.add(m);const y=b(l.title,"Playwright native spec.title"),v=O(l.parents,"spec.parents");v.length>0&&(v[0]=W(v[0]));const L=W(b(l.file,"Playwright native spec.file")),x=b(l.sourceFile,"Playwright native spec.sourceFile");if(!g.isAbsolute(x)&&!g.win32.isAbsolute(x))throw new Error("Playwright native spec.sourceFile must be absolute");const Z=_(l.projectName,"spec.projectName"),T=l.status;if(T!=="expected"&&T!=="flaky"&&T!=="skipped"&&T!=="unexpected")throw new Error("Playwright native spec.status is invalid");u[T]+=1;const j=O(l.failureMessages,"spec.failureMessages"),z=l.failureStack===null?null:_(l.failureStack,"spec.failureStack"),D=G(l.line,"Playwright native spec.line"),B=G(l.column,"Playwright native spec.column");T==="unexpected"&&(s.push(...j),h.push({kind:K(e,x)?"playwright-spec":"playwright-other-file-spec",key:[...v.slice(1),y].filter(Boolean).join(" "),title:y,count:1,message:j[0]??`Playwright reported "${y}" as an unexpected failure`,...v.length===0?{}:{suitePath:v},location:{path:L,...D===null?{}:{line:D},...B===null?{}:{column:B}},...z===null?{}:{stack:z}})),f.push(x),d.push({durationMs:H(l.durationMs,"Playwright native spec.durationMs"),retries:E(l.retries,"Playwright native spec.retries"),file:L,projectName:Z,status:T,title:[...v,y].filter(Boolean).join(" > ")})}for(const r of["expected","flaky","skipped","unexpected"])if(a[r]!==u[r])throw new Error("Playwright native report stats do not match its cases");const P=A(i.logs,"logs").map(r=>{const l=S(r,"Playwright native report log");if(l.stream!=="stdout"&&l.stream!=="stderr")throw new Error("Playwright native report log stream is invalid");return{stream:l.stream,text:_(l.text,"log.text")}});return{attachments:A(i.attachments,"attachments").map((r,l)=>yt(r,l,o)),failOnFlakyTests:n,failureDetails:h,failureMessages:s,logs:St(P),reportErrors:p,runStatus:c,sourceFiles:f,specs:d,stats:a}}function A(t,e){if(!Array.isArray(t))throw new Error(`Playwright native report ${e} must be an array`);return t}function O(t,e){return A(t,e).map(i=>_(i,e))}function _(t,e){if(typeof t!="string")throw new Error(`Playwright native report ${e} must be a string`);return t}function yt(t,e,i){const c=S(t,`Playwright attachment[${e.toString()}]`),n=b(c.specId,"Playwright attachment.specId");if(!i.has(n))throw new Error("Playwright native attachment references an unknown test identity");const a=N(c.path),s=c.error===void 0?void 0:mt(c.error);if(c.body!==void 0||a===null&&s===void 0)throw new Error("Playwright attachment must contain a path or an error");return{contentType:N(c.contentType)??"application/octet-stream",name:N(c.name)??`attachment-${e.toString()}`,specId:n,...a===null?{}:{path:a},...s===void 0?{}:{error:s},retry:E(c.retry,"Playwright attachment.retry"),specTitle:b(c.specTitle,"Playwright attachment.specTitle")}}function mt(t){const e=S(t,"Playwright attachment.error");if(e.kind==="io")return{kind:"io",message:b(e.message,"Playwright attachment.error.message")};const i=e.kind;if(i!=="individual"&&i!=="total")throw new Error("Playwright attachment.error has an invalid kind");const c=i==="individual"?X:ct,n=E(e.observed,"Playwright attachment.error.observed");if(e.limit!==c||n<=c)throw new Error("Playwright attachment.error has an invalid limit");return{kind:i,limit:c,observed:n}}function I(t,e){const{name:i,specId:c,specTitle:n,retry:a}=t,s=e.kind==="io"?e.message:`exceeds its ${e.limit.toString()} byte ${e.kind} limit (observed ${e.observed.toString()} bytes)`;return{code:e.kind==="io"?"PLAYWRIGHT_ATTACHMENT_FAILED":"PLAYWRIGHT_ATTACHMENT_LIMIT",...e.kind==="io"?{classification:"infra"}:{},details:{name:i,specId:c,specTitle:n,retry:a,...e},message:`Playwright attachment "${i}" (${n}, retry ${a.toString()}): ${s}`}}async function wt(t,e,i,c,n){const a=[],s=[],p=new Set;let d=0;const f=g.join(e,"attachments");c.length>0&&await w.mkdir(f,{mode:448,recursive:!1});for(const[h,o]of c.entries())try{if(o.error!==void 0){o.path!==void 0&&p.add(g.resolve(i,o.path)),s.push(I(o,o.error));continue}const u=await Tt(o.path,i,t,n);p.add(u.file);const P=E(Number(u.identity.size),"Playwright attachment size"),k=st(P,d+P);if(k!==null){s.push(I(o,k));continue}const r=await C(u.file,X,"Playwright attachment",n,u.identity,f,`${h.toString().padStart(4,"0")}-${Q(o.name)}${kt(o)}`);d+=r.identity.sizeBytes,a.push({path:r.filePath,contentType:o.contentType,kind:"engine.playwright-attachment",name:o.name,summary:`${o.specTitle} retry ${o.retry.toString()}`,metadata:{...r.identity,specId:o.specId,specTitle:o.specTitle,retry:o.retry}})}catch(u){s.push(I(o,ot(u)))}return{artifacts:a,failures:s,sourcePaths:p}}async function Pt(t,e,i,c,n){try{const a=await n.lstat(i);if(!a.isDirectory()||a.isSymbolicLink())throw new Error("Playwright output directory must be a non-symbolic-link directory");const s=await w.realpath(i);if(!$(t,s))throw new Error("Playwright output directory escaped the artifact root");const p=await Et(s,c,n),d=[],f=g.join(e,"output");p.candidates.length>0&&await w.mkdir(f,{mode:448,recursive:!1});for(const[h,o]of p.candidates.entries()){const u=await C(o.file,M,"Playwright output file",n,o.identity,f,`${h.toString().padStart(5,"0")}-${Q(g.basename(o.file))}${At(o.file)}`);d.push(F(u.filePath,u.identity,xt(o.file),"engine.playwright-output",o.relativePath,"Playwright output"))}return{artifacts:d,bytes:p.bytes,entries:p.entries}}catch(a){if(rt(a))return{artifacts:[],bytes:0,entries:0};throw a}}async function Et(t,e,i){const c=[];let n=0,a=0;const s=[{depth:0,directory:t}];for(;s.length>0;){const p=s.pop();if(p===void 0)continue;if(p.depth>V)throw new Error(`Playwright output exceeds the ${V.toString()} level directory depth limit`);const d=p.directory,f=await w.readdir(d,{withFileTypes:!0});f.sort((h,o)=>h.name.localeCompare(o.name));for(const h of f){if(a+=1,a>J)throw new R({bytes:n,entries:a,kind:"entries",limit:J,observed:a});const o=g.join(d,h.name),u=await i.lstat(o);if(u.isSymbolicLink())throw new Error("Playwright output must not contain symbolic links");if(u.isDirectory()){s.push({depth:p.depth+1,directory:o});continue}if(!u.isFile())continue;if(u.size<0n||u.size>BigInt(Number.MAX_SAFE_INTEGER))throw new Error("Playwright output file has an invalid size");const P=Number(u.size);if(n+=P,!Number.isSafeInteger(n)||n>M)throw new R({bytes:n,entries:a,kind:"bytes",limit:M,observed:n});e.has(o)||c.push({file:o,identity:u,relativePath:U(t,o)})}}return{bytes:n,candidates:c,entries:a}}function vt(t){return t.kind==="bytes"?`Playwright output exceeds its ${t.limit.toString()} byte aggregate limit (observed ${t.observed.toString()} bytes)`:`Playwright output exceeds its ${t.limit.toString()} entry limit (observed ${t.observed.toString()} entries)`}async function Tt(t,e,i,c){if(t===void 0)throw new Error("Playwright path attachment did not declare a path");const n=g.resolve(e,t),a=await c.lstat(n);if(!a.isFile()||a.isSymbolicLink())throw new Error("Playwright attachment path must be a regular non-symbolic-link file");const s=await w.realpath(n);if(!$(e,s)&&!$(i,s))throw new Error("Playwright attachment path must stay within the consumer or artifact root");return{file:s,identity:a}}function bt(t){const e=S(t,"Playwright report.stats");return{expected:E(e.expected,"Playwright stats.expected"),unexpected:E(e.unexpected,"Playwright stats.unexpected"),flaky:E(e.flaky,"Playwright stats.flaky"),skipped:E(e.skipped,"Playwright stats.skipped"),duration:H(e.duration,"Playwright stats.duration")}}function St(t){let e=ut;const i=[];for(const c of t){if(e<=0)break;const a=Buffer.from(c.text,"utf8").subarray(0,e).toString("utf8");a.length>0&&i.push({...c,text:a}),e-=Buffer.byteLength(a)}return e<=0&&i.push({stream:"engine",text:"Playwright report log evidence was truncated at its bounded limit"}),i}function kt(t){if(t.path!==void 0){const e=g.extname(t.path);if(/^\.[a-z0-9]{1,12}$/iu.test(e))return e}return t.contentType==="application/json"?".json":t.contentType.startsWith("text/")?".txt":t.contentType==="image/png"?".png":t.contentType==="image/jpeg"?".jpg":".bin"}function xt(t){const e=g.extname(t).toLowerCase();return e===".json"?"application/json":e===".zip"?"application/zip":e===".png"?"image/png":e===".jpg"||e===".jpeg"?"image/jpeg":e===".webm"?"video/webm":[".log",".txt",".md"].includes(e)?"text/plain; charset=utf-8":"application/octet-stream"}function At(t){const e=g.extname(t).toLowerCase();return/^\.[a-z0-9]{1,12}$/u.test(e)?e:".bin"}function Q(t){const e=t.trim().toLowerCase().replace(/[^a-z0-9._-]+/gu,"-").replace(/^-+|-+$/gu,"").slice(0,80);return e.length>0?e:"attachment"}
@@ -1 +1 @@
1
- import f from"node:path";import v from"node:fs/promises";import T from"node:crypto";import{writeNativeReport as j}from"../../adapters/native-report.js";import{writeOwnedEvidenceFile as D}from"../../adapters/evidence-files.js";import{attachmentIoError as M,attachmentLimitError as A}from"./attachments.js";export default class F{options;config;suite;errors=[];constructor(o){this.options=o}onBegin(o,l){this.config=o,this.suite=l}onError(o){this.errors.push(P(o))}async onEnd(o){const l=this.suite.allTests(),e={duration:o.duration,expected:0,flaky:0,skipped:0,unexpected:0},u=[],p=[];let m=0;const d=[];for(const r of l){const y=r.outcome();e[y]+=1;let g=0,h=0,w=[],b=null;for(const n of r.results){g+=n.duration,h=Math.max(h,n.retry);for(const t of["stdout","stderr"])for(const s of n[t])u.push({stream:t,text:typeof s=="string"?s:s.toString("utf8")});for(const t of n.attachments){const s={name:t.name,contentType:t.contentType,...t.path===void 0?{}:{path:t.path},specId:r.id,specTitle:r.title,retry:n.retry};try{let a=t.body?.byteLength;if(a===void 0){if(t.path===void 0)throw new Error("Attachment has no body or path");const c=await v.lstat(t.path);if(!c.isFile()||c.isSymbolicLink())throw new Error("Attachment path must be a regular non-symbolic-link file");a=c.size}const k=A(a,m+a);if(k!==null){p.push({...s,error:k});continue}let E=t.path;if(t.body!==void 0){const c=this.options.attachmentDirectory;if(await v.realpath(c)!==f.resolve(c))throw new Error("Attachment directory must not resolve through symbolic links");E=await D(c,T.randomUUID(),t.body)}p.push({...s,path:E}),m+=a}catch(a){p.push({...s,error:M(a)})}}n.errors.length>0&&(w=n.errors.map(P),b=n.errors.find(t=>t.stack)?.stack??null)}d.push({id:r.id,title:r.title,parents:I(r,this.config.rootDir),file:x(this.config.rootDir,r.location.file),sourceFile:r.location.file,projectName:r.parent.project()?.name??"",line:r.location.line,column:r.location.column,status:y,durationMs:g,retries:h,failureMessages:w,failureStack:b})}await j(this.options,{stats:e,specs:d,logs:u,attachments:p,errors:this.errors},64*1024*1024)}}function I(i,o){const l=[];for(let e=i.parent;e&&e.type!=="project"&&e.type!=="root";e=e.parent)l.unshift(e.type==="file"&&e.location?x(o,e.location.file):e.title);return l}function x(i,o){return f.relative(i,o).split(f.sep).join("/")}function P(i){return i.message?.trim()||i.stack?.trim()||i.value?.trim()||"Playwright reported an error"}
1
+ import f from"node:path";import E from"node:fs/promises";import x from"node:crypto";import{writeNativeReport as D}from"../../adapters/native-report.js";import{writeOwnedEvidenceFile as P}from"../../adapters/evidence-files.js";import{attachmentIoError as j,attachmentLimitError as M}from"./attachments.js";export default class A{options;config;suite;errors=[];constructor(e){this.options=e}onBegin(e,r){this.config=e,this.suite=r}onError(e){this.errors.push(F(e))}async onEnd(e){const r=this.suite.allTests(),o={duration:e.duration,expected:0,flaky:0,skipped:0,unexpected:0},u=[],p=[];let m=0;const d=[];for(const i of r){const y=i.outcome();o[y]+=1;let g=0,h=0,w=[],k=null;for(const s of i.results){g+=s.duration,h=Math.max(h,s.retry);for(const t of["stdout","stderr"])for(const a of s[t])u.push({stream:t,text:typeof a=="string"?a:a.toString("utf8")});for(const t of s.attachments){const a={name:t.name,contentType:t.contentType,...t.path===void 0?{}:{path:t.path},specId:i.id,specTitle:i.title,retry:s.retry};try{let c=t.body?.byteLength;if(c===void 0){if(t.path===void 0)throw new Error("Attachment has no body or path");const l=await E.lstat(t.path);if(!l.isFile()||l.isSymbolicLink())throw new Error("Attachment path must be a regular non-symbolic-link file");c=l.size}const b=M(c,m+c);if(b!==null){p.push({...a,error:b});continue}let v=t.path;if(t.body!==void 0){const l=this.options.attachmentDirectory;if(await E.realpath(l)!==f.resolve(l))throw new Error("Attachment directory must not resolve through symbolic links");v=await P(l,x.randomUUID(),t.body)}p.push({...a,path:v}),m+=c}catch(c){p.push({...a,error:j(c)})}}s.errors.length>0&&(w=s.errors.map(F),k=s.errors.find(t=>t.stack)?.stack??null)}d.push({id:i.id,title:i.title,parents:L(i,this.config.rootDir),file:T(this.config.rootDir,i.location.file),sourceFile:I(i,this.config.rootDir),projectName:i.parent.project()?.name??"",line:i.location.line,column:i.location.column,status:y,durationMs:g,retries:h,failureMessages:w,failureStack:k})}await D(this.options,{status:e.status,failOnFlakyTests:this.options.failOnFlakyTests,stats:o,specs:d,logs:u,attachments:p,errors:this.errors},64*1024*1024)}}function I(n,e){for(let r=n.parent;r;r=r.parent)if(r.type==="file")return f.resolve(e,r.title);return n.location.file}function L(n,e){const r=[];for(let o=n.parent;o&&o.type!=="project"&&o.type!=="root";o=o.parent)r.unshift(o.type==="file"&&o.location?T(e,o.location.file):o.title);return r}function T(n,e){return f.relative(n,e).split(f.sep).join("/")}function F(n){return n.message?.trim()||n.stack?.trim()||n.value?.trim()||"Playwright reported an error"}
@@ -1,7 +1,7 @@
1
1
  import type { RunArtifactDocument } from "./reporting-types.js";
2
2
  import type { NormalizedArtifactPublicationConfig, NormalizedArtifactRetentionConfig } from "./artifact-config.js";
3
- import { type RunBundleArtifact, type RunBundleCleanup, type RunBundleManifest, type RunBundlePublication, type RunBundleRetentionDecision, type RunBundleSummary, type RunBundleVerification, type VerifiedRunBundleUpload, type SealRunBundleOptions } from "./contracts.js";
4
- export type { RunBundleArchive, RunBundleArtifact, RunBundleCleanup, RunBundleLatestProjection, RunBundleManifest, RunBundleManifestEntry, RunBundlePublication, RunBundleRedaction, RunBundleRetentionDecision, RunBundleStorage, RunBundleSummary, RunBundleVerification, VerifiedRunBundleUpload, SealRunBundleOptions, } from "./contracts.js";
3
+ import { type RunBundleArtifact, type RunBundleCleanup, type RunBundleManifest, type RunBundlePublication, type RunBundleRetentionDecision, type RunBundleSummary, type RunBundleVerification, type SealRunBundleOptions } from "./contracts.js";
4
+ export type { RunBundleArchive, RunBundleArtifact, RunBundleCleanup, RunBundleLatestProjection, RunBundleManifest, RunBundleManifestEntry, RunBundlePublication, RunBundleRedaction, RunBundleRetentionDecision, RunBundleStorage, RunBundleSummary, RunBundleVerification, SealRunBundleOptions, } from "./contracts.js";
5
5
  interface PublicationReceiptInput extends RunBundlePublication {
6
6
  bytesUploaded: number;
7
7
  }
@@ -23,6 +23,5 @@ export declare function listRunBundles(productDir: string, options?: {
23
23
  file?: null | string;
24
24
  }): RunBundleSummary[];
25
25
  export declare function verifyRunBundle(productDir: string, runId: unknown): RunBundleVerification;
26
- export declare function prepareVerifiedRunBundleUpload(productDir: string, requestedRunId?: null | string): VerifiedRunBundleUpload;
27
26
  export declare function publishRunBundle(productDir: string, runId: unknown, publication: NormalizedArtifactPublicationConfig | null, dependencies?: Readonly<RunBundlePublicationDependencies>): RunBundlePublication;
28
27
  export declare function applyRunBundleRetention(productDir: string, currentRunId: unknown, retention?: Partial<NormalizedArtifactRetentionConfig>, dependencies?: Readonly<RunBundleRetentionDependencies>): RunBundleRetentionDecision;
@@ -1 +1 @@
1
- import{recordWork as J}from"../kernel/work-metrics.js";import{optionalString as A}from"../kernel/unknown-values.js";import{readRunProvenance as ue}from"./read-provenance.js";import{parseRunBundleArtifact as le,parseRunBundleManifest as Y,readRunBundleArtifact as fe}from"./read-bundle.js";import{readRunDocument as de}from"./read-run.js";import me from"crypto";import c from"fs";import u from"path";import{redactSensitiveText as G}from"../kernel/redaction.js";import{BUNDLE_SCHEMA_VERSION as Q,OBJECTS_DIRNAME as X,PROVENANCE_SCHEMA_VERSION as pe,RETENTION_DECISION_SCHEMA_VERSION as he,RUNS_DIRNAME as ge}from"./contracts.js";import{bundleRelativePath as P,errorMessage as k,hashFile as z,isObjectRecord as $,normalizePath as Z,normalizeRunId as I,objectRecords as ee,optionalStringArray as D,optionalUnknownArray as ye,readJsonFile as O,requirePublicationPathOutsideResults as M,requireRunId as ve,runIncludesFile as Se,resolveManifestEntryPath as U,resolveRunBundleDirectory as x,resolveRunsRoot as te,resultsRoot as q,sanitizeSegment as Re,sha256 as C,stableJson as W,treeBytes as ne,walkFiles as re,writeBufferAtomic as je,writeJson as N}from"./storage.js";import{resolveCiProvenance as be}from"./ci-provenance.js";const K=["artifacts","logs","setup"],we=/(?:PASSWORD|TOKEN|SECRET|PRIVATE_KEY|AUTH|COOKIE|SESSION|API_KEY)/iu,Ie=["package.json","package-lock.json","npm-shrinkwrap.json","pnpm-lock.yaml","yarn.lock","testkit.config.ts","testkit.config.mts","testkit.config.js","testkit.config.mjs"];export function sealRunBundle(e,n,t={},s={}){const r=n,o=ve(r),i=te(e,!0),a=u.dirname(i),f=u.join(i,o),m=s.cleanupActiveRunResults??De,p=loadVerifiedRunBundleArtifactIfExists(e,o);if(p!==null)return{...p,archive:{...p.archive,cleanup:m(a)}};const d=u.join(i,`.${o}.${String(process.pid)}.${String(Date.now())}.${me.randomBytes(4).toString("hex")}.tmp`);c.mkdirSync(d,{recursive:!0});try{const R=_e(t.env??process.env),g=Ee({productDir:e,resultsDir:a,runId:o,tempDir:d,sensitiveValues:R}),h=Be(r,o,R),y=de(h.artifact,"run artifact");g.redaction.structuredReplacements=h.replacements,g.redaction.replacements+=h.replacements;const v=$e(e,y,o,{...t,redaction:g.redaction}),j={schemaVersion:Q,runId:o,status:"complete",canonicalRunPath:P(o,"run.json"),manifestPath:P(o,"manifest.json"),provenancePath:P(o,"provenance.json"),integrity:"sha256",publication:{configured:!!t.archiveConfig?.publication,status:t.archiveConfig?.publication?"pending":"not_configured"},redaction:g.redaction};y.archive=j,N(u.join(d,"run.json"),y),N(u.join(d,"provenance.json"),v);const b=xe(d),T=C(Buffer.from(W(b))),E={schemaVersion:Q,runId:o,createdAt:new Date().toISOString(),algorithm:"sha256",files:b,storage:{fileCount:b.length,logicalBytes:b.reduce((_,F)=>_+F.bytes,0),objectBytesWritten:g.objectBytesWritten,deduplicatedBytes:g.deduplicatedBytes},redaction:g.redaction,digest:T};return N(u.join(d,"manifest.json"),E),c.renameSync(d,f),{...y,archive:{...j,cleanup:m(a),manifestDigest:E.digest,storage:E.storage}}}catch(R){try{c.rmSync(d,{recursive:!0,force:!0})}catch{}try{Ce(e)}catch{}throw R}}export function loadVerifiedRunBundleArtifactIfExists(e,n){const t=I(n),s=x(e,t);if(!c.existsSync(s))return null;const r=verifyRunBundle(e,t);if(!r.ok)throw new Error(`Immutable run bundle ${t} already exists but failed integrity verification: ${r.errors.join("; ")}`);return loadRunBundleArtifact(e,t)}export function loadRunBundleArtifact(e,n){return se(e,n).artifact}function se(e,n){const t=I(n),s=fe(e,t),r=loadRunBundleManifest(e,t);return{manifest:r,artifact:{...s,archive:{...s.archive,manifestDigest:r.digest,storage:r.storage}}}}export function loadRunBundleManifest(e,n){const t=I(n),s=x(e,t),r=u.join(s,"manifest.json");if(!c.existsSync(r))throw new Error(`No immutable Testkit run manifest found for ${t}`);return Y(O(r),r,t)}export function listRunBundles(e,n={}){return ie(e,n).map(({artifact:t,manifest:s,...r})=>r)}function ie(e,n={}){const t=te(e,!1);if(!c.existsSync(t))return[];const s=c.readdirSync(t,{withFileTypes:!0});return J("artifacts.runDirectoriesRead",1),J("artifacts.runEntriesVisited",s.length),s.filter(o=>o.isDirectory()&&!o.name.startsWith(".")).map(o=>{try{const{artifact:i,manifest:a}=se(e,o.name);return n.file&&!Se(i,n.file)?null:{artifact:i,manifest:a,runId:o.name,status:i.run.status,startedAt:i.run.startedAt,finishedAt:i.run.finishedAt??null,manifestDigest:i.archive.manifestDigest??null,canonicalRunPath:i.archive.canonicalRunPath}}catch{return null}}).filter(o=>o!==null).sort((o,i)=>(i.finishedAt??i.startedAt??"").localeCompare(o.finishedAt??o.startedAt??""))}export function verifyRunBundle(e,n){const t=I(n);try{return V(x(e,t),t)}catch(s){return{ok:!1,runId:t,errors:[k(s)],checkedFiles:0}}}export function prepareVerifiedRunBundleUpload(e,n=null){const t=n??listRunBundles(e).at(0)?.runId??null;if(t===null)throw new Error("No immutable Testkit run bundle is available to publish");const s=verifyRunBundle(e,t);if(!s.ok)throw new Error(`Immutable run bundle ${t} failed integrity verification: ${s.errors.join("; ")}`);const r=loadRunBundleManifest(e,t),o=x(e,t);return{runId:t,manifest:r,files:r.files.map(i=>({...i,absolutePath:U(o,i.path)}))}}export function publishRunBundle(e,n,t,s={}){const r=I(n),o=s.writePublicationReceipt??Pe;if(!t)return o(e,r,{status:"not_configured",required:!1,target:null,bytesUploaded:0});const i=u.resolve(e,t.directory);try{M(e,i)}catch(d){return o(e,r,{status:"failed",required:t.required,target:i,error:k(d),bytesUploaded:0})}const a=Re(u.basename(e)),f=u.join(i,a),m=u.join(f,r),p=u.join(f,`.${r}.${String(process.pid)}.${String(Date.now())}.tmp`);try{const d=x(e,r);if(c.mkdirSync(f,{recursive:!0}),M(e,f),c.existsSync(m)){const h=c.lstatSync(m);if(h.isSymbolicLink()||!h.isDirectory())throw new Error("Publication target must be a regular bundle directory");M(e,m);const y=V(d,r);if(!y.ok)throw new Error(`Source run bundle failed integrity verification: ${y.errors.join("; ")}`);const v=V(m,r);if(!v.ok)throw new Error(`Publication target failed integrity verification: ${v.errors.join("; ")}`);if(y.manifestDigest!==v.manifestDigest)throw new Error("Publication target already exists with a different manifest digest");return o(e,r,{status:"complete",required:t.required,target:m,manifestDigest:y.manifestDigest,reused:!0,bundleBytes:ne(d),bytesUploaded:0})}const R=ne(d);c.cpSync(d,p,{recursive:!0,preserveTimestamps:!0});const g=V(p,r);if(!g.ok)throw new Error(g.errors.join("; "));return c.renameSync(p,m),o(e,r,{status:"complete",required:t.required,target:m,manifestDigest:g.manifestDigest,reused:!1,bundleBytes:R,bytesUploaded:R})}catch(d){return c.rmSync(p,{recursive:!0,force:!0}),o(e,r,{status:"failed",required:t.required,target:m,error:k(d),bytesUploaded:0})}}export function applyRunBundleRetention(e,n,t={},s={}){const r=I(n),o=s.removeObject??ce,i=s.removeRunBundle??Ne,a=t.maxRuns??null,f=t.maxAgeDays??null,m=new Set(t.preserveSuites||[]),p=ie(e),d=new Set([r]);for(const l of p)Oe(l.artifact,m)&&d.add(l.runId);const R=new Set(a?p.slice(0,a).map(l=>l.runId):p.map(l=>l.runId)),g=f==null?null:Date.now()-f*24*60*60*1e3,h=[];for(const l of p){if(d.has(l.runId))continue;const S=g!=null&&Date.parse(l.finishedAt||l.startedAt||"")<g,w=!R.has(l.runId);if(!S&&!w)continue;const B=[];S&&B.push("max_age"),w&&B.push("max_runs"),h.push({path:u.join(q(e),ge,l.runId),reasons:B,runId:l.runId})}const y=new Map(p.map(l=>[l.runId,l.manifest.files.filter(S=>S.path.startsWith("files/")).map(S=>S.sha256)])),v=oe(e),j=[],b=[];for(const l of h){let S=null;try{i(l.path)}catch(B){S=H(B)}const w=!c.existsSync(l.path);!w&&S===null&&(S="Run bundle still exists after removal completed"),w&&j.push({reasons:l.reasons,runId:l.runId}),S!==null&&b.push({...l,error:S,removed:w})}const T=new Set(j.map(l=>l.runId)),E=new Set(p.filter(l=>!T.has(l.runId)).flatMap(l=>y.get(l.runId)??[])),_=ae(v,E,o),F=new Date().toISOString();return Ve({currentRunId:r,decidedAt:F,garbageCollection:_,policy:{maxRuns:a,maxAgeDays:f,preserveSuites:[...m].sort()},protectedRunIds:[...d].sort(),removed:j,removalErrors:b})}function Ee({resultsDir:e,tempDir:n,sensitiveValues:t}){let s=0,r=0,o=0,i=0,a=0;const f=u.join(e,X,"sha256");c.mkdirSync(f,{recursive:!0});for(const m of K){const p=u.join(e,m);if(c.existsSync(p))for(const d of re(p)){const R=u.relative(p,d),g=c.readFileSync(d),h=Te(g,d,t);h.scanned&&(o+=1),h.replacements>0&&(i+=1,a+=h.replacements);const y=C(h.buffer),v=u.join(f,y);c.existsSync(v)?r+=h.buffer.length:(je(v,h.buffer),c.chmodSync(v,292),s+=h.buffer.length);const j=u.join(n,"files",m,R);c.mkdirSync(u.dirname(j),{recursive:!0});try{c.linkSync(v,j)}catch{c.copyFileSync(v,j)}}}return{objectBytesWritten:s,deduplicatedBytes:r,redaction:{policyVersion:1,status:"applied",textualFilesScanned:o,filesRedacted:i,replacements:a,sensitiveEnvironmentKeys:t.map(m=>m.key).sort(),valuesStored:!1}}}function Be(e,n,t){const s=new Map(K.map(o=>[`.testkit/results/${o}/`,`${P(n,`files/${o}`)}/`])),r=L(e,s);return ke(Ae(r),t)}function Ae(e){if(!$(e))return e;const n={...e};if($(n.product)){const t={...n.product};delete t.directory,n.product=t}if($(n.git)){const t={...n.git};delete t.remoteUrl,delete t.repoRoot,delete t.worktreePath,n.git=t}return delete n.host,n}function L(e,n){if(Array.isArray(e))return e.map(s=>L(s,n));if($(e))return Object.fromEntries(Object.entries(e).map(([s,r])=>[s,L(r,n)]));if(typeof e!="string")return e;let t=e;for(const[s,r]of n)t.startsWith(s)&&(t=`${r}${t.slice(s.length)}`);return t}function ke(e,n){let t=0;function s(r){if(Array.isArray(r))return r.map(s);if($(r))return Object.fromEntries(Object.entries(r).map(([i,a])=>[i,s(a)]));if(typeof r!="string")return r;let o=G(r);o!==r&&(t+=1);for(const i of n){if(!o.includes(i.value))continue;const a=o.split(i.value);t+=a.length-1,o=a.join("<redacted>")}return o}return{artifact:s(e),replacements:t}}function $e(e,n,t,s){const r=D(n.run.fileNames),o=[...new Set([...Ie,...r])].map(i=>ze(e,i)).filter(i=>i!==null);return{schemaVersion:pe,runId:t,ci:be(s.env??process.env),source:{git:n.git??null,files:o},runtime:{controller:{testkitVersion:A(n.run.testkitVersion),node:process.versions.node,platform:process.platform,architecture:process.arch},runtimeStats:ye(n.run.runtimeStats)},scenario:{types:D(n.run.types),suiteSelectors:D(n.run.suiteSelectors),fileNames:r,serviceFilter:A(n.run.serviceFilter),planningDigest:C(Buffer.from(W(n.planning??null))),declaredEnvironmentKeys:[...new Set(s.declaredEnvironmentKeys??[])].sort()},redaction:s.redaction}}function xe(e){return re(e).map(n=>({path:Z(u.relative(e,n)),bytes:c.statSync(n).size,sha256:z(n)})).sort((n,t)=>n.path.localeCompare(t.path))}function V(e,n){const t=u.join(e,"manifest.json"),s=Y(O(t),t,n);le(O(u.join(e,"run.json")),u.join(e,"run.json"),n);const r=[];for(const i of s.files){let a;try{a=U(e,i.path)}catch(m){r.push(k(m));continue}if(!c.existsSync(a)){r.push(`missing ${i.path}`);continue}const f=c.lstatSync(a);if(f.isSymbolicLink()){r.push(`symbolic link not allowed ${i.path}`);continue}if(!f.isFile()){r.push(`not a regular file ${i.path}`);continue}f.size!==i.bytes&&r.push(`size mismatch ${i.path}`),z(a)!==i.sha256&&r.push(`checksum mismatch ${i.path}`)}C(Buffer.from(W(s.files)))!==s.digest&&r.push("manifest digest mismatch"),s.storage.fileCount!==s.files.length&&r.push("manifest file count mismatch");for(const i of["run.json","provenance.json"])s.files.some(a=>a.path===i)||r.push(`manifest must include ${i}`);if(r.length===0){const i=U(e,"provenance.json");ue(O(i),i,n)}return{runId:n,checkedFiles:s.files.length,storage:s.storage,ok:r.length===0,errors:r,manifestDigest:s.digest}}function Pe(e,n,t){const s={schemaVersion:1,runId:n,recordedAt:new Date().toISOString(),...t};return N(u.join(q(e),"publications",`${n}.json`),s),s}function Oe(e,n){return n.size===0?!1:ee(e.services).some(t=>ee(t.suites).some(s=>{const r=A(t.name)??"",o=A(s.name)??"",i=A(s.testType)??"";return[o,`${i}:${o}`,`${r}/${i}:${o}`].some(f=>n.has(f))}))}function Ce(e){const n=new Set;for(const t of listRunBundles(e)){const s=loadRunBundleManifest(e,t.runId);for(const r of s.files)r.path.startsWith("files/")&&n.add(r.sha256)}return ae(oe(e),n,ce)}function oe(e){const n=u.join(q(e),X,"sha256");return c.existsSync(n)?c.readdirSync(n,{withFileTypes:!0}).filter(t=>t.isFile()).map(t=>{const s=u.join(n,t.name);return{bytes:c.statSync(s).size,digest:t.name,path:s}}):[]}function ae(e,n,t){const s=[];let r=0,o=0;for(const i of e){if(n.has(i.digest))continue;let a=null;try{t(i.path)}catch(m){a=H(m)}!c.existsSync(i.path)?(r+=1,o+=i.bytes):a===null&&(a="Object still exists after removal completed"),a!==null&&s.push({error:a,path:i.path})}return{failures:s,reclaimedBytes:o,removedObjects:r,status:s.length===0?"complete":"failed"}}function Ne(e){c.rmSync(e,{recursive:!0,force:!0})}function ce(e){c.rmSync(e,{force:!0})}function Ve({currentRunId:e,decidedAt:n,garbageCollection:t,policy:s,protectedRunIds:r,removed:o,removalErrors:i}){const a=[...i.map(f=>`remove ${f.runId}: ${f.error}`),...t.failures.map(f=>`garbage collect ${f.path}: ${f.error}`)];return{currentRunId:e,decidedAt:n,garbageCollection:t,policy:s,protectedRunIds:r,removed:o,removalErrors:i,schemaVersion:he,status:a.length===0?"complete":"failed",...a.length===0?{}:{error:a.join("; ")}}}function Te(e,n,t){if(!Fe(e,n)){for(const i of t)if(e.includes(Buffer.from(i.value)))throw new Error(`Refusing to archive binary file containing sensitive environment value: ${n}`);return{buffer:e,scanned:!1,replacements:0}}const s=e.toString("utf8");let r=G(s),o=r===s?0:1;for(const i of t){if(!r.includes(i.value))continue;const a=r.split(i.value);o+=a.length-1,r=a.join("<redacted>")}return{buffer:Buffer.from(r),scanned:!0,replacements:o}}function _e(e){const n=[];for(const[t,s]of Object.entries(e))we.test(t)&&typeof s=="string"&&s.length>=8&&n.push({key:t,value:s});return n}function Fe(e,n){if(e.includes(0))return!1;const t=u.extname(n).toLowerCase();return![".zip",".gz",".png",".jpg",".jpeg",".webm",".pdf"].includes(t)}function ze(e,n){const t=u.resolve(e,n);return!t.startsWith(`${u.resolve(e)}${u.sep}`)||!c.existsSync(t)||!c.statSync(t).isFile()?null:{path:Z(u.relative(e,t)),bytes:c.statSync(t).size,sha256:z(t)}}function De(e){const n=[];for(const t of[...K.map(s=>u.join(e,s)),u.join(e,"live.json")])try{c.rmSync(t,{recursive:!0,force:!0})}catch(s){n.push(`${t}: ${H(s)}`)}return n.length===0?{status:"complete"}:{error:n.join("; "),status:"failed"}}function H(e){try{return k(e)}catch{return"Unknown post-seal archive operation failure"}}
1
+ import{recordWork as H}from"../kernel/work-metrics.js";import{optionalString as A}from"../kernel/unknown-values.js";import{readRunProvenance as ue}from"./read-provenance.js";import{parseRunBundleArtifact as le,parseRunBundleManifest as J,readRunBundleArtifact as fe}from"./read-bundle.js";import{readRunDocument as de}from"./read-run.js";import me from"crypto";import c from"fs";import u from"path";import{redactSensitiveText as Y}from"../kernel/redaction.js";import{BUNDLE_SCHEMA_VERSION as G,OBJECTS_DIRNAME as Q,PROVENANCE_SCHEMA_VERSION as pe,RETENTION_DECISION_SCHEMA_VERSION as he,RUNS_DIRNAME as ge}from"./contracts.js";import{bundleRelativePath as x,errorMessage as k,hashFile as F,isObjectRecord as $,normalizePath as X,normalizeRunId as w,objectRecords as Z,optionalStringArray as z,optionalUnknownArray as ye,readJsonFile as P,requirePublicationPathOutsideResults as M,requireRunId as ve,runIncludesFile as Se,resolveManifestEntryPath as ee,resolveRunBundleDirectory as O,resolveRunsRoot as te,resultsRoot as U,sanitizeSegment as Re,sha256 as C,stableJson as q,treeBytes as ne,walkFiles as re,writeBufferAtomic as je,writeJson as N}from"./storage.js";import{resolveCiProvenance as be}from"./ci-provenance.js";const W=["artifacts","logs","setup"],Ie=/(?:PASSWORD|TOKEN|SECRET|PRIVATE_KEY|AUTH|COOKIE|SESSION|API_KEY)/iu,we=["package.json","package-lock.json","npm-shrinkwrap.json","pnpm-lock.yaml","yarn.lock","testkit.config.ts","testkit.config.mts","testkit.config.js","testkit.config.mjs"];export function sealRunBundle(e,n,t={},s={}){const r=n,o=ve(r),i=te(e,!0),a=u.dirname(i),f=u.join(i,o),m=s.cleanupActiveRunResults??ze,p=loadVerifiedRunBundleArtifactIfExists(e,o);if(p!==null)return{...p,archive:{...p.archive,cleanup:m(a)}};const d=u.join(i,`.${o}.${String(process.pid)}.${String(Date.now())}.${me.randomBytes(4).toString("hex")}.tmp`);c.mkdirSync(d,{recursive:!0});try{const R=Te(t.env??process.env),g=Ee({productDir:e,resultsDir:a,runId:o,tempDir:d,sensitiveValues:R}),h=Be(r,o,R),y=de(h.artifact,"run artifact");g.redaction.structuredReplacements=h.replacements,g.redaction.replacements+=h.replacements;const v=$e(e,y,o,{...t,redaction:g.redaction}),j={schemaVersion:G,runId:o,status:"complete",canonicalRunPath:x(o,"run.json"),manifestPath:x(o,"manifest.json"),provenancePath:x(o,"provenance.json"),integrity:"sha256",publication:{configured:!!t.archiveConfig?.publication,status:t.archiveConfig?.publication?"pending":"not_configured"},redaction:g.redaction};y.archive=j,N(u.join(d,"run.json"),y),N(u.join(d,"provenance.json"),v);const b=xe(d),D=C(Buffer.from(q(b))),E={schemaVersion:G,runId:o,createdAt:new Date().toISOString(),algorithm:"sha256",files:b,storage:{fileCount:b.length,logicalBytes:b.reduce((T,_)=>T+_.bytes,0),objectBytesWritten:g.objectBytesWritten,deduplicatedBytes:g.deduplicatedBytes},redaction:g.redaction,digest:D};return N(u.join(d,"manifest.json"),E),c.renameSync(d,f),{...y,archive:{...j,cleanup:m(a),manifestDigest:E.digest,storage:E.storage}}}catch(R){try{c.rmSync(d,{recursive:!0,force:!0})}catch{}try{Ce(e)}catch{}throw R}}export function loadVerifiedRunBundleArtifactIfExists(e,n){const t=w(n),s=O(e,t);if(!c.existsSync(s))return null;const r=verifyRunBundle(e,t);if(!r.ok)throw new Error(`Immutable run bundle ${t} already exists but failed integrity verification: ${r.errors.join("; ")}`);return loadRunBundleArtifact(e,t)}export function loadRunBundleArtifact(e,n){return se(e,n).artifact}function se(e,n){const t=w(n),s=fe(e,t),r=loadRunBundleManifest(e,t);return{manifest:r,artifact:{...s,archive:{...s.archive,manifestDigest:r.digest,storage:r.storage}}}}export function loadRunBundleManifest(e,n){const t=w(n),s=O(e,t),r=u.join(s,"manifest.json");if(!c.existsSync(r))throw new Error(`No immutable Testkit run manifest found for ${t}`);return J(P(r),r,t)}export function listRunBundles(e,n={}){return ie(e,n).map(({artifact:t,manifest:s,...r})=>r)}function ie(e,n={}){const t=te(e,!1);if(!c.existsSync(t))return[];const s=c.readdirSync(t,{withFileTypes:!0});return H("artifacts.runDirectoriesRead",1),H("artifacts.runEntriesVisited",s.length),s.filter(o=>o.isDirectory()&&!o.name.startsWith(".")).map(o=>{try{const{artifact:i,manifest:a}=se(e,o.name);return n.file&&!Se(i,n.file)?null:{artifact:i,manifest:a,runId:o.name,status:i.run.status,startedAt:i.run.startedAt,finishedAt:i.run.finishedAt??null,manifestDigest:i.archive.manifestDigest??null,canonicalRunPath:i.archive.canonicalRunPath}}catch{return null}}).filter(o=>o!==null).sort((o,i)=>(i.finishedAt??i.startedAt??"").localeCompare(o.finishedAt??o.startedAt??""))}export function verifyRunBundle(e,n){const t=w(n);try{return V(O(e,t),t)}catch(s){return{ok:!1,runId:t,errors:[k(s)],checkedFiles:0}}}export function publishRunBundle(e,n,t,s={}){const r=w(n),o=s.writePublicationReceipt??Pe;if(!t)return o(e,r,{status:"not_configured",required:!1,target:null,bytesUploaded:0});const i=u.resolve(e,t.directory);try{M(e,i)}catch(d){return o(e,r,{status:"failed",required:t.required,target:i,error:k(d),bytesUploaded:0})}const a=Re(u.basename(e)),f=u.join(i,a),m=u.join(f,r),p=u.join(f,`.${r}.${String(process.pid)}.${String(Date.now())}.tmp`);try{const d=O(e,r);if(c.mkdirSync(f,{recursive:!0}),M(e,f),c.existsSync(m)){const h=c.lstatSync(m);if(h.isSymbolicLink()||!h.isDirectory())throw new Error("Publication target must be a regular bundle directory");M(e,m);const y=V(d,r);if(!y.ok)throw new Error(`Source run bundle failed integrity verification: ${y.errors.join("; ")}`);const v=V(m,r);if(!v.ok)throw new Error(`Publication target failed integrity verification: ${v.errors.join("; ")}`);if(y.manifestDigest!==v.manifestDigest)throw new Error("Publication target already exists with a different manifest digest");return o(e,r,{status:"complete",required:t.required,target:m,manifestDigest:y.manifestDigest,reused:!0,bundleBytes:ne(d),bytesUploaded:0})}const R=ne(d);c.cpSync(d,p,{recursive:!0,preserveTimestamps:!0});const g=V(p,r);if(!g.ok)throw new Error(g.errors.join("; "));return c.renameSync(p,m),o(e,r,{status:"complete",required:t.required,target:m,manifestDigest:g.manifestDigest,reused:!1,bundleBytes:R,bytesUploaded:R})}catch(d){return c.rmSync(p,{recursive:!0,force:!0}),o(e,r,{status:"failed",required:t.required,target:m,error:k(d),bytesUploaded:0})}}export function applyRunBundleRetention(e,n,t={},s={}){const r=w(n),o=s.removeObject??ce,i=s.removeRunBundle??Ne,a=t.maxRuns??null,f=t.maxAgeDays??null,m=new Set(t.preserveSuites||[]),p=ie(e),d=new Set([r]);for(const l of p)Oe(l.artifact,m)&&d.add(l.runId);const R=new Set(a?p.slice(0,a).map(l=>l.runId):p.map(l=>l.runId)),g=f==null?null:Date.now()-f*24*60*60*1e3,h=[];for(const l of p){if(d.has(l.runId))continue;const S=g!=null&&Date.parse(l.finishedAt||l.startedAt||"")<g,I=!R.has(l.runId);if(!S&&!I)continue;const B=[];S&&B.push("max_age"),I&&B.push("max_runs"),h.push({path:u.join(U(e),ge,l.runId),reasons:B,runId:l.runId})}const y=new Map(p.map(l=>[l.runId,l.manifest.files.filter(S=>S.path.startsWith("files/")).map(S=>S.sha256)])),v=oe(e),j=[],b=[];for(const l of h){let S=null;try{i(l.path)}catch(B){S=L(B)}const I=!c.existsSync(l.path);!I&&S===null&&(S="Run bundle still exists after removal completed"),I&&j.push({reasons:l.reasons,runId:l.runId}),S!==null&&b.push({...l,error:S,removed:I})}const D=new Set(j.map(l=>l.runId)),E=new Set(p.filter(l=>!D.has(l.runId)).flatMap(l=>y.get(l.runId)??[])),T=ae(v,E,o),_=new Date().toISOString();return Ve({currentRunId:r,decidedAt:_,garbageCollection:T,policy:{maxRuns:a,maxAgeDays:f,preserveSuites:[...m].sort()},protectedRunIds:[...d].sort(),removed:j,removalErrors:b})}function Ee({resultsDir:e,tempDir:n,sensitiveValues:t}){let s=0,r=0,o=0,i=0,a=0;const f=u.join(e,Q,"sha256");c.mkdirSync(f,{recursive:!0});for(const m of W){const p=u.join(e,m);if(c.existsSync(p))for(const d of re(p)){const R=u.relative(p,d),g=c.readFileSync(d),h=De(g,d,t);h.scanned&&(o+=1),h.replacements>0&&(i+=1,a+=h.replacements);const y=C(h.buffer),v=u.join(f,y);c.existsSync(v)?r+=h.buffer.length:(je(v,h.buffer),c.chmodSync(v,292),s+=h.buffer.length);const j=u.join(n,"files",m,R);c.mkdirSync(u.dirname(j),{recursive:!0});try{c.linkSync(v,j)}catch{c.copyFileSync(v,j)}}}return{objectBytesWritten:s,deduplicatedBytes:r,redaction:{policyVersion:1,status:"applied",textualFilesScanned:o,filesRedacted:i,replacements:a,sensitiveEnvironmentKeys:t.map(m=>m.key).sort(),valuesStored:!1}}}function Be(e,n,t){const s=new Map(W.map(o=>[`.testkit/results/${o}/`,`${x(n,`files/${o}`)}/`])),r=K(e,s);return ke(Ae(r),t)}function Ae(e){if(!$(e))return e;const n={...e};if($(n.product)){const t={...n.product};delete t.directory,n.product=t}if($(n.git)){const t={...n.git};delete t.remoteUrl,delete t.repoRoot,delete t.worktreePath,n.git=t}return delete n.host,n}function K(e,n){if(Array.isArray(e))return e.map(s=>K(s,n));if($(e))return Object.fromEntries(Object.entries(e).map(([s,r])=>[s,K(r,n)]));if(typeof e!="string")return e;let t=e;for(const[s,r]of n)t.startsWith(s)&&(t=`${r}${t.slice(s.length)}`);return t}function ke(e,n){let t=0;function s(r){if(Array.isArray(r))return r.map(s);if($(r))return Object.fromEntries(Object.entries(r).map(([i,a])=>[i,s(a)]));if(typeof r!="string")return r;let o=Y(r);o!==r&&(t+=1);for(const i of n){if(!o.includes(i.value))continue;const a=o.split(i.value);t+=a.length-1,o=a.join("<redacted>")}return o}return{artifact:s(e),replacements:t}}function $e(e,n,t,s){const r=z(n.run.fileNames),o=[...new Set([...we,...r])].map(i=>Fe(e,i)).filter(i=>i!==null);return{schemaVersion:pe,runId:t,ci:be(s.env??process.env),source:{git:n.git??null,files:o},runtime:{controller:{testkitVersion:A(n.run.testkitVersion),node:process.versions.node,platform:process.platform,architecture:process.arch},runtimeStats:ye(n.run.runtimeStats)},scenario:{types:z(n.run.types),suiteSelectors:z(n.run.suiteSelectors),fileNames:r,serviceFilter:A(n.run.serviceFilter),planningDigest:C(Buffer.from(q(n.planning??null))),declaredEnvironmentKeys:[...new Set(s.declaredEnvironmentKeys??[])].sort()},redaction:s.redaction}}function xe(e){return re(e).map(n=>({path:X(u.relative(e,n)),bytes:c.statSync(n).size,sha256:F(n)})).sort((n,t)=>n.path.localeCompare(t.path))}function V(e,n){const t=u.join(e,"manifest.json"),s=J(P(t),t,n);le(P(u.join(e,"run.json")),u.join(e,"run.json"),n);const r=[];for(const i of s.files){let a;try{a=ee(e,i.path)}catch(m){r.push(k(m));continue}if(!c.existsSync(a)){r.push(`missing ${i.path}`);continue}const f=c.lstatSync(a);if(f.isSymbolicLink()){r.push(`symbolic link not allowed ${i.path}`);continue}if(!f.isFile()){r.push(`not a regular file ${i.path}`);continue}f.size!==i.bytes&&r.push(`size mismatch ${i.path}`),F(a)!==i.sha256&&r.push(`checksum mismatch ${i.path}`)}C(Buffer.from(q(s.files)))!==s.digest&&r.push("manifest digest mismatch"),s.storage.fileCount!==s.files.length&&r.push("manifest file count mismatch");for(const i of["run.json","provenance.json"])s.files.some(a=>a.path===i)||r.push(`manifest must include ${i}`);if(r.length===0){const i=ee(e,"provenance.json");ue(P(i),i,n)}return{runId:n,checkedFiles:s.files.length,storage:s.storage,ok:r.length===0,errors:r,manifestDigest:s.digest}}function Pe(e,n,t){const s={schemaVersion:1,runId:n,recordedAt:new Date().toISOString(),...t};return N(u.join(U(e),"publications",`${n}.json`),s),s}function Oe(e,n){return n.size===0?!1:Z(e.services).some(t=>Z(t.suites).some(s=>{const r=A(t.name)??"",o=A(s.name)??"",i=A(s.testType)??"";return[o,`${i}:${o}`,`${r}/${i}:${o}`].some(f=>n.has(f))}))}function Ce(e){const n=new Set;for(const t of listRunBundles(e)){const s=loadRunBundleManifest(e,t.runId);for(const r of s.files)r.path.startsWith("files/")&&n.add(r.sha256)}return ae(oe(e),n,ce)}function oe(e){const n=u.join(U(e),Q,"sha256");return c.existsSync(n)?c.readdirSync(n,{withFileTypes:!0}).filter(t=>t.isFile()).map(t=>{const s=u.join(n,t.name);return{bytes:c.statSync(s).size,digest:t.name,path:s}}):[]}function ae(e,n,t){const s=[];let r=0,o=0;for(const i of e){if(n.has(i.digest))continue;let a=null;try{t(i.path)}catch(m){a=L(m)}!c.existsSync(i.path)?(r+=1,o+=i.bytes):a===null&&(a="Object still exists after removal completed"),a!==null&&s.push({error:a,path:i.path})}return{failures:s,reclaimedBytes:o,removedObjects:r,status:s.length===0?"complete":"failed"}}function Ne(e){c.rmSync(e,{recursive:!0,force:!0})}function ce(e){c.rmSync(e,{force:!0})}function Ve({currentRunId:e,decidedAt:n,garbageCollection:t,policy:s,protectedRunIds:r,removed:o,removalErrors:i}){const a=[...i.map(f=>`remove ${f.runId}: ${f.error}`),...t.failures.map(f=>`garbage collect ${f.path}: ${f.error}`)];return{currentRunId:e,decidedAt:n,garbageCollection:t,policy:s,protectedRunIds:r,removed:o,removalErrors:i,schemaVersion:he,status:a.length===0?"complete":"failed",...a.length===0?{}:{error:a.join("; ")}}}function De(e,n,t){if(!_e(e,n)){for(const i of t)if(e.includes(Buffer.from(i.value)))throw new Error(`Refusing to archive binary file containing sensitive environment value: ${n}`);return{buffer:e,scanned:!1,replacements:0}}const s=e.toString("utf8");let r=Y(s),o=r===s?0:1;for(const i of t){if(!r.includes(i.value))continue;const a=r.split(i.value);o+=a.length-1,r=a.join("<redacted>")}return{buffer:Buffer.from(r),scanned:!0,replacements:o}}function Te(e){const n=[];for(const[t,s]of Object.entries(e))Ie.test(t)&&typeof s=="string"&&s.length>=8&&n.push({key:t,value:s});return n}function _e(e,n){if(e.includes(0))return!1;const t=u.extname(n).toLowerCase();return![".zip",".gz",".png",".jpg",".jpeg",".webm",".pdf"].includes(t)}function Fe(e,n){const t=u.resolve(e,n);return!t.startsWith(`${u.resolve(e)}${u.sep}`)||!c.existsSync(t)||!c.statSync(t).isFile()?null:{path:X(u.relative(e,t)),bytes:c.statSync(t).size,sha256:F(t)}}function ze(e){const n=[];for(const t of[...W.map(s=>u.join(e,s)),u.join(e,"live.json")])try{c.rmSync(t,{recursive:!0,force:!0})}catch(s){n.push(`${t}: ${L(s)}`)}return n.length===0?{status:"complete"}:{error:n.join("; "),status:"failed"}}function L(e){try{return k(e)}catch{return"Unknown post-seal archive operation failure"}}
@@ -138,14 +138,6 @@ export interface RunBundleVerification {
138
138
  runId: string;
139
139
  storage?: null | RunBundleStorage;
140
140
  }
141
- export interface RunBundleUploadFile extends RunBundleManifestEntry {
142
- absolutePath: string;
143
- }
144
- export interface VerifiedRunBundleUpload {
145
- files: RunBundleUploadFile[];
146
- manifest: RunBundleManifest;
147
- runId: string;
148
- }
149
141
  export interface SealRunBundleOptions {
150
142
  archiveConfig?: Partial<NormalizedArtifactArchiveConfig>;
151
143
  declaredEnvironmentKeys?: readonly string[];
@@ -1,6 +1,6 @@
1
1
  export { normalizeArtifactArchiveConfig, type NormalizedArtifactArchiveConfig, type NormalizedArtifactPublicationConfig, type NormalizedArtifactRetentionConfig, } from "./artifact-config.js";
2
2
  export { finalizeLiveRunArtifact, loadTimings, persistTaskArtifacts, persistTaskFileArtifacts, persistTaskOutputArtifacts, resetResultArtifacts, saveTimings, writeLiveRunArtifact, writeRunArtifact, type ArtifactTask, type PersistedArtifactReference, type WriteRunArtifactOptions, } from "./artifacts.js";
3
- export { applyRunBundleRetention, listRunBundles, loadRunBundleArtifact, loadRunBundleManifest, loadVerifiedRunBundleArtifactIfExists, prepareVerifiedRunBundleUpload, publishRunBundle, sealRunBundle, verifyRunBundle, type RunBundleVerification, type VerifiedRunBundleUpload, } from "./bundles.js";
3
+ export { applyRunBundleRetention, listRunBundles, loadRunBundleArtifact, loadRunBundleManifest, loadVerifiedRunBundleArtifactIfExists, publishRunBundle, sealRunBundle, verifyRunBundle, type RunBundleVerification, } from "./bundles.js";
4
4
  export { collectArtifactEntries, loadCurrentRunArtifact, loadLatestRunArtifact, loadRunArtifact, type CollectedArtifactEntry, type RunArtifactFileSubject, type RunArtifactPayload, } from "./reading.js";
5
5
  export { DEFAULT_REPORTED_ARCHIVE_CONFIG, type BuildLiveRunArtifactOptions, type BuildRunArtifactOptions, type ReportedRunArtifact, type ReportedRunService, type ReportingArtifactReference, type ReportingDiagnostic, type ReportingExecution, type ReportingFailureDetail, type ReportingFailureLocation, type ReportingFailureRequest, type ReportingFailureResponse, type ReportingFileSummary, type ReportingMetadata, type ReportingOperationTiming, type ReportingOperationTimingInput, type ReportingRecord, type ReportingRuntimeBatch, type ReportingRuntimeReset, type ReportingServiceLogReference, type ReportingServiceSummary, type ReportingSetupLogReference, type ReportingSetupOperation, type ReportingSuiteSelector, type ReportingSuiteSummary, type RunArtifactDocument, type RunArtifactFile, type RunArtifactRun, type RunArtifactService, type RunArtifactSuite, type RunArtifactSummary, type RunServiceResult, } from "./reporting-types.js";
6
6
  export { copyRegularFileSecurely, MAX_PERSISTED_FILE_ARTIFACT_BYTES, type SecureFileCopyOptions, type SecureFileCopyResult, } from "./secure-file-copy.js";