@gfdelarue/testkit 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/README.md +590 -165
  2. package/bin/testkit-windows-owner-arm64.exe +0 -0
  3. package/bin/testkit-windows-owner-x64.exe +0 -0
  4. package/dist/app/bugs/batch.js +1 -0
  5. package/dist/app/bugs/ci-report.js +2 -2
  6. package/dist/app/bugs/ci-summary.js +3 -3
  7. package/dist/app/bugs/draft.js +2 -2
  8. package/dist/app/bugs/file.js +1 -1
  9. package/dist/app/bugs/guide.js +1 -1
  10. package/dist/app/bugs/record.js +2 -2
  11. package/dist/app/bugs/run-reading.js +1 -1
  12. package/dist/app/bugs/store.js +1 -1
  13. package/dist/app/campaigns/index.js +2 -2
  14. package/dist/app/campaigns/summary.js +1 -1
  15. package/dist/app/cleanup/index.js +1 -1
  16. package/dist/app/db-schema/prepare.js +1 -1
  17. package/dist/app/db-schema/refresh.js +1 -1
  18. package/dist/app/db-schema/verify.js +1 -1
  19. package/dist/app/destroy/index.js +1 -1
  20. package/dist/app/discover/index.js +2 -2
  21. package/dist/app/environments/config-driven.js +1 -1
  22. package/dist/app/environments/file-setup.js +1 -1
  23. package/dist/app/environments/routing.js +1 -1
  24. package/dist/app/environments/static-values.js +1 -1
  25. package/dist/app/environments/supplied.js +1 -1
  26. package/dist/app/inspect/index.js +1 -1
  27. package/dist/app/lint/native.js +1 -1
  28. package/dist/app/lint/options.js +1 -1
  29. package/dist/app/lint/shared.js +1 -1
  30. package/dist/app/run/engine-orchestration.js +1 -1
  31. package/dist/app/run/environment.js +1 -1
  32. package/dist/app/run/live-run.js +1 -1
  33. package/dist/app/run/orchestrator.js +1 -1
  34. package/dist/app/run/planning.js +1 -1
  35. package/dist/app/run/request.js +1 -1
  36. package/dist/app/run/results.js +1 -1
  37. package/dist/app/run/run-finalization.js +1 -1
  38. package/dist/app/run/run-finisher.js +1 -0
  39. package/dist/app/run/setup/runtime-node-setup-protocol.js +1 -1
  40. package/dist/app/run/setup/runtime-node-setup-worker.js +1 -1
  41. package/dist/app/run/setup/runtime-node-setup.js +2 -2
  42. package/dist/app/run/task-batches.js +1 -1
  43. package/dist/app/run/worker-loop.js +1 -1
  44. package/dist/app/status/model.js +1 -1
  45. package/dist/app/status/runs.js +1 -1
  46. package/dist/app/stop/index.js +1 -1
  47. package/dist/app/tracker/index.js +1 -1
  48. package/dist/app/tracker/token.js +2 -2
  49. package/dist/app/typecheck/index.js +4 -4
  50. package/dist/cli/command-catalog.js +1 -1
  51. package/dist/cli/command-flags.js +1 -1
  52. package/dist/cli/commands/artifacts.js +2 -2
  53. package/dist/cli/commands/bug/file.js +3 -2
  54. package/dist/cli/commands/discover.js +1 -1
  55. package/dist/cli/commands/guard/unsafe-entrypoint.js +1 -1
  56. package/dist/cli/commands/run.js +2 -2
  57. package/dist/cli/commands/typecheck.js +1 -1
  58. package/dist/cli/commands/workspace.js +1 -1
  59. package/dist/cli/framework/help.js +5 -5
  60. package/dist/cli/framework/parser.js +1 -1
  61. package/dist/cli/json-errors.js +1 -1
  62. package/dist/cli/renderers/bug/text.js +1 -1
  63. package/dist/cli/renderers/run/formatting.js +1 -1
  64. package/dist/config/authoring/database-fixture-discovery.js +1 -0
  65. package/dist/config/authoring/index.js +1 -1
  66. package/dist/config/loader/config-loader.js +1 -1
  67. package/dist/config/loader/decode-discovery.js +1 -1
  68. package/dist/config/loader/discover-tests.js +1 -1
  69. package/dist/config/loader/discovery-result.js +1 -0
  70. package/dist/config/loader/discovery.js +2 -2
  71. package/dist/config/loader/file-metadata.js +1 -1
  72. package/dist/config/loader/groups.js +2 -1
  73. package/dist/config/loader/index.js +1 -1
  74. package/dist/config/loader/load.js +1 -1
  75. package/dist/config/loader/managed-configs.js +1 -1
  76. package/dist/config/loader/owned-discovery.js +1 -1
  77. package/dist/config/loader/runtime-isolation.js +1 -1
  78. package/dist/config/loader/skip-config.js +1 -1
  79. package/dist/config/loader/test-selection.js +1 -1
  80. package/dist/database/admin.js +5 -2
  81. package/dist/database/call-caps.js +1 -0
  82. package/dist/database/cleanup.js +1 -1
  83. package/dist/database/configured-steps.js +1 -1
  84. package/dist/database/constants.js +1 -1
  85. package/dist/database/docker-endpoint.js +1 -0
  86. package/dist/database/docker-metadata.js +2 -2
  87. package/dist/database/docker.js +5 -5
  88. package/dist/database/index.js +2 -1
  89. package/dist/database/lifecycle-cleanup.js +1 -1
  90. package/dist/database/local-postgres.js +4 -1
  91. package/dist/database/naming.js +1 -1
  92. package/dist/database/operation-deadline.js +1 -0
  93. package/dist/database/postgres/connection-check.js +1 -1
  94. package/dist/database/postgres/index.js +2 -2
  95. package/dist/database/postgres/owned-exec.js +27 -0
  96. package/dist/database/postgres/protocol.js +2 -2
  97. package/dist/database/provider.js +1 -0
  98. package/dist/database/roles.js +2 -2
  99. package/dist/database/schema-operations.js +5 -1
  100. package/dist/database/schema-source.js +3 -3
  101. package/dist/database/steps.js +1 -1
  102. package/dist/database/template-steps.js +4 -4
  103. package/dist/domain/identifiers.js +1 -1
  104. package/dist/domain/retired-test-types.js +1 -1
  105. package/dist/domain/suite-selection.js +1 -1
  106. package/dist/domain/test-types.js +1 -1
  107. package/dist/engines/adapters/evidence-files.js +1 -1
  108. package/dist/engines/adapters/execution.js +1 -1
  109. package/dist/engines/catalog.js +1 -1
  110. package/dist/engines/definitions.js +1 -1
  111. package/dist/engines/environment/index.js +1 -1
  112. package/dist/engines/installations/node-vitest.js +1 -1
  113. package/dist/engines/installations/playwright.js +1 -1
  114. package/dist/engines/managed-module-resolution.js +6 -2
  115. package/dist/engines/node-vitest/adapter/adapter.js +1 -1
  116. package/dist/engines/node-vitest/adapter/driver.js +2 -2
  117. package/dist/engines/node-vitest/adapter/evidence.js +2 -2
  118. package/dist/engines/node-vitest/adapter/workspace.js +8 -24
  119. package/dist/engines/node-vitest/inventory/index.js +2 -2
  120. package/dist/engines/node-vitest/inventory/list-files.js +3 -3
  121. package/dist/engines/node-vitest/runtime/data/index.js +1 -1
  122. package/dist/engines/node-vitest/runtime/data/report.js +1 -1
  123. package/dist/engines/node-vitest/runtime/data/scenario.js +2 -2
  124. package/dist/engines/node-vitest/runtime/runtime-input.js +1 -1
  125. package/dist/engines/playwright/adapter/adapter.js +1 -1
  126. package/dist/engines/playwright/adapter/browser-network-evidence.js +4 -0
  127. package/dist/engines/playwright/adapter/config.js +16 -6
  128. package/dist/engines/playwright/adapter/evidence.js +2 -1
  129. package/dist/engines/playwright/adapter/failure-details.js +2 -2
  130. package/dist/engines/playwright/adapter/reporter.js +1 -1
  131. package/dist/engines/playwright/runtime/browser-network.js +1 -0
  132. package/dist/engines/playwright/runtime/browser-traffic.js +1 -0
  133. package/dist/engines/playwright/runtime/chromium-environment.js +1 -0
  134. package/dist/engines/playwright/runtime/emulation.js +1 -0
  135. package/dist/engines/playwright/runtime/index.js +1 -1
  136. package/dist/engines/playwright/runtime/managed-test.js +1 -0
  137. package/dist/engines/playwright/runtime/runtime-input.js +2 -2
  138. package/dist/environment/lifecycle-controller.js +1 -1
  139. package/dist/environment/lifecycle.js +1 -1
  140. package/dist/environment/maintenance.js +1 -1
  141. package/dist/environment/run-environment.js +1 -1
  142. package/dist/environment/run-stopping.js +1 -1
  143. package/dist/environment/runtime-manager.js +1 -1
  144. package/dist/evidence/artifact-config.js +1 -1
  145. package/dist/evidence/artifacts.js +5 -2
  146. package/dist/evidence/bundles.js +2 -1
  147. package/dist/evidence/compressed-artifacts.js +1 -0
  148. package/dist/evidence/environment-record.js +1 -1
  149. package/dist/evidence/index.js +1 -1
  150. package/dist/evidence/live-run-summary.js +1 -0
  151. package/dist/evidence/local-paths.js +1 -0
  152. package/dist/evidence/maintenance.js +1 -0
  153. package/dist/evidence/operation-timings.js +1 -1
  154. package/dist/evidence/read-run.js +1 -1
  155. package/dist/evidence/redaction.js +4 -4
  156. package/dist/evidence/run-evidence.js +1 -0
  157. package/dist/evidence/setup-progress.js +1 -1
  158. package/dist/evidence/storage-inventory.js +1 -1
  159. package/dist/evidence/storage.js +2 -2
  160. package/dist/kernel/known-keys.js +1 -0
  161. package/dist/kernel/redaction-targets.js +1 -1
  162. package/dist/kernel/redaction.js +7 -4
  163. package/dist/process/engine-environment-keys.js +1 -0
  164. package/dist/process/environment-overlay.js +1 -1
  165. package/dist/process/environment.js +1 -1
  166. package/dist/process/execa-bundle.json +3 -3
  167. package/dist/process/files/evidence-writes.js +1 -0
  168. package/dist/process/files/product-dir.js +1 -1
  169. package/dist/process/host-process.js +1 -1
  170. package/dist/process/index.js +1 -1
  171. package/dist/process/lifecycle-fence.js +1 -1
  172. package/dist/process/lock-progress.js +2 -0
  173. package/dist/process/locks.js +4 -4
  174. package/dist/process/process-identity.js +1 -1
  175. package/dist/process/process.js +35 -35
  176. package/dist/process/recovery.js +1 -1
  177. package/dist/process/runs/manifest.js +1 -1
  178. package/dist/process/runs/ownership.js +1 -1
  179. package/dist/process/runs/stop.js +1 -1
  180. package/dist/process/shutdown.js +1 -1
  181. package/dist/process/windows-native.js +1 -1
  182. package/dist/repo/fingerprint-policy.js +1 -1
  183. package/dist/repo/history.js +7 -4
  184. package/dist/repo/managed-git.js +1 -1
  185. package/dist/repo/state.js +4 -4
  186. package/dist/steps/configured-step-testkit-modules.js +1 -1
  187. package/dist/steps/environment.js +1 -1
  188. package/dist/toolchains/index.js +2 -2
  189. package/dist/toolchains/node-provisioning.js +2 -2
  190. package/dist/toolchains/vitest/index.js +1 -1
  191. package/dist/tracker/gitea.js +1 -1
  192. package/dist/tracker/github.js +1 -1
  193. package/dist/tracker/http.js +1 -1
  194. package/dist/tracker/ownership.js +1 -0
  195. package/dist/tracker/redaction.js +1 -1
  196. package/dist/tracker/responses.js +1 -1
  197. package/dist/types/config.d.ts +7 -86
  198. package/dist/types/environment.d.ts +6 -0
  199. package/dist/types/node-vitest-data.d.ts +2 -218
  200. package/dist/types/playwright.d.ts +21 -0
  201. package/dist/types/workspace.d.ts +30 -14
  202. package/docs/bug-records.md +19 -18
  203. package/docs/engines.md +115 -21
  204. package/docs/release-notes.md +2846 -0
  205. package/docs/test-types.md +19 -69
  206. package/package.json +15 -6
  207. package/testkit-config-schema.md +101 -170
  208. package/dist/app/lint/rules/data.js +0 -1
  209. package/dist/config/authoring/runtime.js +0 -1
  210. package/dist/config/loader/task-policy.js +0 -1
  211. package/dist/engines/node-vitest/runtime/contract/index.js +0 -1
  212. package/dist/engines/node-vitest/runtime/data/http.js +0 -1
  213. package/dist/engines/node-vitest/runtime/data/runner.js +0 -1
  214. package/dist/environment/lifecycle-artifacts.js +0 -1
  215. package/dist/types/node-vitest-contract.d.ts +0 -75
@@ -1 +1 @@
1
- import{Args as i,Flags as e}from"./framework/definitions.js";import{defineCommand as t}from"./framework/command.js";import{runArgs as n,runFlags as o,sharedFlags as r,suppliedEnvironmentFlags as l,testSelectionFlags as a,vitestSelectionFlags as s}from"./command-flags.js";import{publicTestTypeListText as u}from"../domain/test-types.js";export const commandCatalog=Object.freeze({artifacts:t({summary:"List, verify, or inspect artifacts from immutable Testkit runs",enableJsonFlag:!0,args:{file:i.string({description:"Optional test file selector",required:!1})},flags:{...a,kind:e.string({description:"Limit artifacts to one kind"}),format:e.string({description:"Output format",options:["list","text"],default:"list"}),run:e.string({description:"Read one immutable run ID instead of latest"}),list:e.boolean({description:"List retained immutable runs",default:!1}),verify:e.boolean({description:"Verify the selected run manifest and checksums",default:!1})}}),"bug file":t({summary:"Run a recorded bug's test and file the bug as an issue",enableJsonFlag:!0,args:{id:i.string({description:"Draft id that `testkit bug record` printed",required:!0})},flags:{dir:e.string({description:"Product directory"}),...l,...s}}),"bug guide":t({summary:"Print the bug record format, the severities, and the category lists",enableJsonFlag:!0}),"bug record":t({summary:"Record a confirmed bug as a local draft",enableJsonFlag:!0,args:{file:i.string({description:"Bug record JSON file, or - to read standard input",required:!0})},flags:{dir:e.string({description:"Product directory"}),...s}}),"campaign finish":t({summary:"Finish the active campaign and write its pull request summary",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"})}}),"campaign start":t({summary:"Start a bug-hunting campaign and print the agent's brief",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"}),id:e.string({description:"Campaign id, instead of one made from the target and the date"}),target:e.string({description:"What the agent should hunt, in one line of at most 200 characters",required:!0})}}),"campaign status":t({summary:"Show the active campaign, its recorded bugs, and the area map coverage",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"})}}),"ci report":t({summary:"Post the last run as a commit status, a step summary, and one pull request comment",enableJsonFlag:!0,flags:{context:e.string({description:"Commit status context, instead of testkit"}),dir:e.string({description:"Product directory"})}}),cleanup:t({summary:"Clean stale local testkit state",enableJsonFlag:!0,flags:{...r,"dry-run":e.boolean({description:"Show cleanup actions without deleting state",default:!1}),cache:e.string({description:"Clean cache state: runtime, bundles, or all",multiple:!0,options:["runtime","bundles","all"]}),resources:e.boolean({description:"Clean Testkit-owned Docker resources that are stale or orphaned",default:!1})}}),"db schema refresh":t({summary:"Refresh a source database schema cache",enableJsonFlag:!0,flags:r}),"db schema verify":t({summary:"Verify local schema replay against the source database schema",enableJsonFlag:!0,flags:{...r,"skip-schema-source-verify":e.boolean({description:"Skip the check that local schema replay matches the source database schema",default:!1})}}),destroy:t({summary:"Destroy local testkit state",enableJsonFlag:!0,flags:r}),discover:t({summary:"Discover managed tests and report their metadata",enableJsonFlag:!0,flags:{...a,...s,type:e.string({char:"t",multiple:!0,description:`Filter by suite type(s): ${u({includeAll:!0})}`}),suite:e.string({char:"s",multiple:!0,description:"Filter by suite selector(s)"}),file:e.string({char:"f",multiple:!0,description:"Filter by exact file path(s)"}),"runnable-only":e.boolean({description:"Show only files not skipped by repo rules",default:!1}),strict:e.boolean({description:"Fail instead of reporting discovery/config diagnostics",default:!1}),output:e.string({description:"Write machine-readable discovery JSON to a file"}),"output-mode":e.string({description:"Human-readable output mode",options:["compact","verbose"],default:"compact"})}}),doctor:t({summary:"Run built-in config, discovery, and hygiene checks",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"}),typecheck:e.boolean({description:"Include generated TypeScript checks",default:!0,allowNo:!0})}}),"guard unsafe-entrypoint":t({summary:"Fail a local script that must be run through Testkit",flags:{command:e.string({char:"c",description:"Disabled command name shown in the error message",required:!0}),use:e.string({char:"u",description:"Replacement command shown in the error message",required:!0})}}),lint:t({summary:"Run built-in Testkit repository hygiene checks",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"})}}),"report prepare":t({summary:"Prepare a local report with optional template, bug records, and selected run evidence",enableJsonFlag:!0,args:{directory:i.string({description:"New bundle directory",required:!0})},flags:{dir:e.string({description:"Product directory containing bug records and runs"}),template:e.string({description:"Copy this file unchanged to report.md"}),instructions:e.string({description:"Print this repository-owned drafting guide for the author"}),bug:e.string({description:"Include this recorded bug ID as context",multiple:!0}),run:e.string({description:"Immutable run ID containing the selected artifacts"}),artifact:e.string({description:"Exact artifact ref.path from testkit artifacts --json",multiple:!0})}}),"report publish":t({summary:"Publish a prepared directory at one Git revision without changing your checkout",enableJsonFlag:!0,args:{directory:i.string({description:"Directory containing only the files to publish",required:!0})},flags:{dir:e.string({description:"Product directory used to resolve provider credentials"}),repository:e.string({description:"Destination GitHub or Gitea repository URL, or owner/name",required:!0}),branch:e.string({description:"Destination branch",required:!0}),path:e.string({description:"Bundle directory within the destination repository",required:!0}),message:e.string({description:"Publication commit message"})}}),"run status":t({summary:"Show active Testkit run progress",enableJsonFlag:!0,flags:{...r,run:e.string({description:"Specific run id to show"}),full:e.boolean({description:"Include the full live status payload in JSON output"})}}),"run stop":t({summary:"Stop active Testkit run work",enableJsonFlag:!0,flags:{...r,run:e.string({description:"Specific run id to stop; defaults to the newest active run"}),all:e.boolean({description:"Stop all active runs for this product",default:!1}),reason:e.string({description:"Operator-visible reason recorded with cancelled runs and dirty slots"})}}),run:t({summary:"Run test suites",enableJsonFlag:!0,args:n,flags:o}),"setup playwright":t({summary:"Install the managed Playwright Chromium runtime",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"}),force:e.boolean({default:!1,description:"Force Playwright to repair the browser installation"}),"with-deps":e.boolean({default:!1,description:"Install required host dependencies with Chromium"})}}),status:t({summary:"Show local testkit state",enableJsonFlag:!0,flags:r}),stop:t({summary:"Stop all Testkit-owned runs",enableJsonFlag:!0,flags:{...r,reason:e.string({description:"Operator-visible reason recorded with cancelled runs"})}}),typecheck:t({summary:"Typecheck testkit config, helpers, and suites",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"}),...s}}),workspace:t({summary:"Inspect Testkit workstation data for a product",enableJsonFlag:!0,flags:{...a,"target-limit":e.integer({description:"Maximum number of quick-run target previews",default:24}),"no-status":e.boolean({description:"Skip runtime status collection",default:!1})}})});
1
+ import{Args as i,Flags as e}from"./framework/definitions.js";import{defineCommand as t}from"./framework/command.js";import{runArgs as o,runFlags as l,schemaOperationFlags as n,sharedFlags as r,suppliedEnvironmentFlags as u,testSelectionFlags as s,vitestSelectionFlags as a}from"./command-flags.js";import{publicTestTypeListText as c}from"../domain/test-types.js";export const commandCatalog=Object.freeze({artifacts:t({summary:"List, verify, or inspect artifacts from immutable Testkit runs",enableJsonFlag:!0,args:{file:i.string({description:"Optional test file selector",required:!1})},flags:{...s,kind:e.string({description:"Limit artifacts to one kind"}),format:e.string({description:"Output format",options:["list","text"],default:"list"}),run:e.string({description:"Read one immutable run ID instead of latest"}),list:e.boolean({description:"List retained immutable runs",default:!1}),verify:e.boolean({description:"Verify the selected run manifest and checksums",default:!1})}}),"bug file":t({summary:"Run a recorded bug's test and file the bug as an issue",enableJsonFlag:!0,args:{id:i.string({description:"Draft id that `testkit bug record` printed; with --batch, any number of them",multiple:!0})},flags:{batch:e.boolean({description:"File the named drafts, or every unfiled draft when none is named, and report each one",default:!1}),dir:e.string({description:"Product directory"}),...u,...a}}),"bug guide":t({summary:"Print the bug record format, the severities, and the category lists",enableJsonFlag:!0}),"bug record":t({summary:"Record a confirmed bug as a local draft",enableJsonFlag:!0,args:{file:i.string({description:"Bug record JSON file, or - to read standard input",required:!0})},flags:{dir:e.string({description:"Product directory"}),...a}}),"campaign finish":t({summary:"Finish the active campaign and write its pull request summary",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"})}}),"campaign start":t({summary:"Start a bug-hunting campaign and print the agent's brief",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"}),id:e.string({description:"Campaign id, instead of one made from the target and the date"}),target:e.string({description:"What the agent should hunt, in one line of at most 200 characters",required:!0})}}),"campaign status":t({summary:"Show the active campaign, its recorded bugs, and the area map coverage",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"})}}),"ci report":t({summary:"Post the last run as a commit status, a step summary, and one pull request comment",enableJsonFlag:!0,flags:{context:e.string({description:"Commit status context, instead of testkit"}),dir:e.string({description:"Product directory"})}}),cleanup:t({summary:"Clean stale local testkit state",enableJsonFlag:!0,flags:{...r,"dry-run":e.boolean({description:"Show cleanup actions without deleting state",default:!1}),cache:e.string({description:"Clean cache state: runtime, bundles, or all",multiple:!0,options:["runtime","bundles","all"]}),resources:e.boolean({description:"Clean Testkit-owned Docker resources that are stale or orphaned",default:!1})}}),"db schema refresh":t({summary:"Refresh a source database schema cache",enableJsonFlag:!0,flags:{...r,...n}}),"db schema verify":t({summary:"Verify local schema replay against the source database schema",enableJsonFlag:!0,flags:{...r,...n,"skip-schema-source-verify":e.boolean({description:"Skip the check that local schema replay matches the source database schema",default:!1})}}),destroy:t({summary:"Remove runtime state, databases, and Docker resources; run results and other .testkit files stay",enableJsonFlag:!0,flags:r}),discover:t({summary:"Discover managed tests and report their metadata",enableJsonFlag:!0,flags:{...s,...a,type:e.string({char:"t",multiple:!0,description:`Filter by suite type(s): ${c({includeAll:!0})}`}),suite:e.string({char:"s",multiple:!0,description:"Filter by suite selector(s)"}),file:e.string({char:"f",multiple:!0,description:"Filter by exact file path(s)"}),"runnable-only":e.boolean({description:"Show only files not skipped by repo rules",default:!1}),strict:e.boolean({description:"Fail instead of reporting discovery/config diagnostics",default:!1}),output:e.string({description:"Write machine-readable discovery JSON to a file"}),"output-mode":e.string({description:"Human-readable output mode",options:["compact","verbose"],default:"compact"})}}),doctor:t({summary:"Run built-in config, discovery, and hygiene checks",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"}),typecheck:e.boolean({description:"Include generated TypeScript checks",default:!0,allowNo:!0})}}),"guard unsafe-entrypoint":t({summary:"Fail a local script that must be run through Testkit",flags:{command:e.string({char:"c",description:"Disabled command name shown in the error message",required:!0}),use:e.string({char:"u",description:"Replacement command shown in the error message",required:!0})}}),lint:t({summary:"Run built-in Testkit repository hygiene checks",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"})}}),"report prepare":t({summary:"Prepare a local report with optional template, bug records, and selected run evidence",enableJsonFlag:!0,args:{directory:i.string({description:"New bundle directory",required:!0})},flags:{dir:e.string({description:"Product directory containing bug records and runs"}),template:e.string({description:"Copy this file unchanged to report.md"}),instructions:e.string({description:"Print this repository-owned drafting guide for the author"}),bug:e.string({description:"Include this recorded bug ID as context",multiple:!0}),run:e.string({description:"Immutable run ID containing the selected artifacts"}),artifact:e.string({description:"Exact artifact ref.path from testkit artifacts --json",multiple:!0})}}),"report publish":t({summary:"Publish a prepared directory at one Git revision without changing your checkout",enableJsonFlag:!0,args:{directory:i.string({description:"Directory containing only the files to publish",required:!0})},flags:{dir:e.string({description:"Product directory used to resolve provider credentials"}),repository:e.string({description:"Destination GitHub or Gitea repository URL, or owner/name",required:!0}),branch:e.string({description:"Destination branch",required:!0}),path:e.string({description:"Bundle directory within the destination repository",required:!0}),message:e.string({description:"Publication commit message"})}}),"run status":t({summary:"Show active Testkit run progress",enableJsonFlag:!0,flags:{...r,run:e.string({description:"Specific run id to show"}),full:e.boolean({description:"Include the full live status payload in JSON output"})}}),"run stop":t({summary:"Stop active Testkit run work",enableJsonFlag:!0,flags:{...r,run:e.string({description:"Specific run id to stop; defaults to the newest active run"}),all:e.boolean({description:"Stop all active runs for this product",default:!1}),reason:e.string({description:"Operator-visible reason recorded with cancelled runs and dirty slots"})}}),run:t({summary:"Run test suites",enableJsonFlag:!0,args:o,flags:l}),"setup playwright":t({summary:"Install the managed Playwright Chromium runtime",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"}),force:e.boolean({default:!1,description:"Force Playwright to repair the browser installation"}),"with-deps":e.boolean({default:!1,description:"Install required host dependencies with Chromium"})}}),status:t({summary:"Show local testkit state",enableJsonFlag:!0,flags:r}),stop:t({summary:"Stop all Testkit-owned runs",enableJsonFlag:!0,flags:{...r,reason:e.string({description:"Operator-visible reason recorded with cancelled runs"})}}),typecheck:t({summary:"Typecheck testkit config, helpers, and suites",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"}),group:s.group,...a}}),workspace:t({summary:"Inspect Testkit workstation data for a product",enableJsonFlag:!0,flags:{...s,"target-limit":e.integer({description:"Maximum number of quick-run target previews",default:24}),"no-status":e.boolean({description:"Skip runtime status collection",default:!1})}})});
@@ -1 +1 @@
1
- import{RETIRED_TEST_TYPES as o,rejectRetiredTestType as p}from"../domain/retired-test-types.js";import{publicTestTypeList as c,publicTestTypeListText as n}from"../domain/test-types.js";import{CliUsageError as l}from"./framework/errors.js";import{Args as u,Flags as e}from"./framework/index.js";export const sharedFlags={dir:e.string({description:"Explicit product directory"}),service:e.string({description:"Limit the operation to one service"})},testSelectionFlags={dir:sharedFlags.dir,group:e.string({description:"Limit the operation to one test group"}),service:e.string({description:"Deprecated alias for --group"})};export function selectedGroup(s,i){const{group:r,service:t}=s;if(t===void 0)return r??null;if(r!==void 0&&r!==t)throw new l(`--group ${r} and --service ${t} select different groups; pass only --group`);return i(`--service selects a test group on this command and will be removed; use --group ${t}`),t}export const vitestSelectionFlags={"vitest-config":e.string({description:"Run plain test files with this Vitest config, relative to the product directory"}),"vitest-project":e.string({description:"Run plain test files from this Vitest project only"}),"vitest-runner":e.string({description:"Run plain test files with the repository's Vitest (repo) or Testkit's own (managed)",options:["repo","managed"]})},suppliedEnvironmentFlags={"base-url":e.string({description:"Supply the default endpoint, BASE_URL, instead of provisioning it"}),endpoint:e.string({multiple:!0,description:"Supply a named endpoint as NAME=URL"}),database:e.string({multiple:!0,description:"Supply a named PostgreSQL database as NAME=URL"}),env:e.string({multiple:!0,description:"Supply an environment value as NAME=VALUE"}),"env-from":e.string({multiple:!0,description:"Supply an environment value from a host variable as NAME=HOST_KEY"}),"environment-file":e.string({description:"Supply values, prepared setup data and handle descriptors from a version 1 environment file"})};export function runTypeArgument(s=o){return u.string({check:i=>{p(i,`Type "${i}"`,s)},description:`Optional suite type shortcut: ${n({includeAll:!0})}`,required:!1,options:c({includeAll:!0})})}export const runArgs={type:runTypeArgument()},runFlags={...testSelectionFlags,...suppliedEnvironmentFlags,...vitestSelectionFlags,type:e.string({char:"t",multiple:!0,description:`Run specific suite type(s): ${n({includeAll:!0})}`}),suite:e.string({char:"s",multiple:!0,description:"Run specific suite(s)"}),file:e.string({char:"f",multiple:!0,description:"Run specific file(s)"}),"task-concurrency":e.string({description:"Maximum number of concurrent test tasks for the whole run"}),workers:e.string({description:"Deprecated alias for --task-concurrency; removed in 0.3.0"}),"file-timeout-seconds":e.string({description:"Per-file wall-clock timeout in seconds"}),"allow-empty":e.boolean({description:"Permit a successful run when no runnable files execute",default:!1}),"ignore-skip-rules":e.boolean({description:"Run files even if testkit.config.ts marks them skipped",default:!1}),"skip-schema-source-verify":e.boolean({description:"Skip the check that local schema replay matches the source database schema",default:!1}),"output-mode":e.string({description:"Reporter mode",options:["compact","debug","events"]}),debug:e.boolean({description:"Alias for --output-mode debug",default:!1})};
1
+ import{RETIRED_TEST_TYPES as s,rejectRetiredTestType as n}from"../domain/retired-test-types.js";import{publicTestTypeList as o,publicTestTypeListText as r}from"../domain/test-types.js";import{CliUsageError as p}from"./framework/errors.js";import{Args as c,Flags as e}from"./framework/index.js";export const sharedFlags={dir:e.string({description:"Explicit product directory"}),service:e.string({description:"Limit the operation to one service"})},schemaOperationFlags={"timeout-seconds":e.string({description:"Seconds the operation may run, not counting configured template steps (default 600). A longer limit lengthens the caps on Testkit's own database calls in proportion"})},testSelectionFlags={dir:sharedFlags.dir,group:e.string({description:"Limit the operation to one test group"}),service:e.string({description:"Removed in 0.6.0; use --group"})};export function selectedGroup(t){if(t.service!==void 0)throw new p(`Testkit 0.6.0 removed --service from run, discover, artifacts, and workspace; use --group ${t.service}`);return t.group??null}export const vitestSelectionFlags={"vitest-config":e.string({description:"Run plain test files with this Vitest config, relative to the product directory"}),"vitest-project":e.string({description:"Run plain test files from this Vitest project only"}),"vitest-runner":e.string({description:"Run plain test files with the repository's Vitest (repo) or Testkit's own (managed)",options:["repo","managed"]})},suppliedEnvironmentFlags={"base-url":e.string({description:"Supply the default endpoint, BASE_URL, instead of provisioning it"}),endpoint:e.string({multiple:!0,description:"Supply a named endpoint as NAME=URL"}),database:e.string({multiple:!0,description:"Supply a named PostgreSQL database as NAME=URL"}),env:e.string({multiple:!0,description:"Supply an environment value as NAME=VALUE"}),"env-from":e.string({multiple:!0,description:"Supply an environment value from a host variable as NAME=HOST_KEY"}),"environment-file":e.string({description:"Supply values, prepared setup data and handle descriptors from a version 1 environment file"})};export function runTypeArgument(t=s){return c.string({check:i=>{n(i,`Type "${i}"`,t)},description:`Optional suite type shortcut: ${r({includeAll:!0})}`,required:!1,options:o({includeAll:!0})})}export const runArgs={type:runTypeArgument()},runFlags={...testSelectionFlags,...suppliedEnvironmentFlags,...vitestSelectionFlags,type:e.string({char:"t",multiple:!0,description:`Run specific suite type(s): ${r({includeAll:!0})}`}),suite:e.string({char:"s",multiple:!0,description:"Run specific suite(s)"}),file:e.string({char:"f",multiple:!0,description:"Run specific file(s)"}),"task-concurrency":e.string({description:"Maximum number of concurrent test tasks for the whole run"}),workers:e.string({description:"Deprecated alias for --task-concurrency"}),"file-timeout-seconds":e.string({description:"Per-file wall-clock timeout in seconds"}),"allow-empty":e.boolean({description:"Permit a successful run when no runnable files execute",default:!1}),"ignore-skip-rules":e.boolean({description:"Run files even if testkit.config.ts marks them skipped",default:!1}),"skip-schema-source-verify":e.boolean({description:"Skip the check that local schema replay matches the source database schema",default:!1}),"output-mode":e.string({description:"Reporter mode",options:["compact","debug","events"]}),debug:e.boolean({description:"Alias for --output-mode debug",default:!1})};
@@ -1,3 +1,3 @@
1
- import{asUnknownRecord as c}from"../../kernel/unknown-values.js";import d from"node:fs";import{inspectArtifacts as p}from"../../app/inspect/artifacts.js";import{selectedGroup as l}from"../command-flags.js";export default function m({args:i,flags:e,json:n,log:o,notice:s}){const r=p({dir:e.dir,mode:e.list?"runs":e.verify?"verify":"artifacts",file:i.file||null,runId:e.run||null,group:l(e,s),kind:e.kind,includePayload:n||e.format==="text"});if(r.mode==="runs"){const{runs:t}=r;if(n)return{ok:!0,runs:t};t.length===0&&o("No immutable Testkit runs found.");for(const u of t)o(`${u.runId} ${u.status||"unknown"} ${u.finishedAt||u.startedAt||"unknown-time"}`);return{ok:!0,count:t.length}}const a=r.runId;if(r.mode==="verify"){const{verification:t}=r;return t.ok||(process.exitCode=1),n?{ok:t.ok,verification:t}:(o(t.ok?`Verified ${a??"unknown"}: ${String(t.checkedFiles)} files (${t.manifestDigest??"unknown-digest"})`:`Integrity verification failed for ${a??"unknown"}: ${t.errors.join("; ")}`),{ok:t.ok,verification:t})}const{entries:f}=r;if(n)return{ok:!0,runId:a,artifacts:f.map(t=>({file:t.file.path,group:t.group.name,suite:{engineId:t.suite.engineId,name:t.suite.name,profileId:t.suite.profileId,testType:t.suite.testType},ref:t.artifactRef,payload:t.payload}))};if(e.format==="text")o(h(f));else for(const t of k(f))o(t);return{ok:!0,runId:a,count:f.length}}function k(i){if(i.length===0)return["No artifacts matched."];const e=[];for(const n of i)e.push(`${n.artifactRef.name}${n.artifactRef.kind?` [${n.artifactRef.kind}]`:""}`),n.artifactRef.summary&&e.push(` ${n.artifactRef.summary}`),e.push(` ${n.artifactRef.path}`);return e}function h(i){return i.length===0?"No artifacts matched.":i.map(e=>$(e)).join(`
1
+ import{asUnknownRecord as s}from"../../kernel/unknown-values.js";import d from"node:fs";import{inspectArtifacts as c}from"../../app/inspect/artifacts.js";import{selectedGroup as p}from"../command-flags.js";export default function l({args:i,flags:e,json:n,log:o}){const r=c({dir:e.dir,mode:e.list?"runs":e.verify?"verify":"artifacts",file:i.file||null,runId:e.run||null,group:p(e),kind:e.kind,includePayload:n||e.format==="text"});if(r.mode==="runs"){const{runs:t}=r;if(n)return{ok:!0,runs:t};t.length===0&&o("No immutable Testkit runs found.");for(const u of t)o(`${u.runId} ${u.status||"unknown"} ${u.finishedAt||u.startedAt||"unknown-time"}`);return{ok:!0,count:t.length}}const a=r.runId;if(r.mode==="verify"){const{verification:t}=r;return t.ok||(process.exitCode=1),n?{ok:t.ok,verification:t}:(o(t.ok?`Verified ${a??"unknown"}: ${String(t.checkedFiles)} files (${t.manifestDigest??"unknown-digest"})`:`Integrity verification failed for ${a??"unknown"}: ${t.errors.join("; ")}`),{ok:t.ok,verification:t})}const{entries:f}=r;if(n)return{ok:!0,runId:a,artifacts:f.map(t=>({file:t.file.path,group:t.group.name,suite:{engineId:t.suite.engineId,name:t.suite.name,profileId:t.suite.profileId,testType:t.suite.testType},ref:t.artifactRef,payload:t.payload}))};if(e.format==="text")o(k(f));else for(const t of m(f))o(t);return{ok:!0,runId:a,count:f.length}}function m(i){if(i.length===0)return["No artifacts matched."];const e=[];for(const n of i)e.push(`${n.artifactRef.name}${n.artifactRef.kind?` [${n.artifactRef.kind}]`:""}`),n.artifactRef.summary&&e.push(` ${n.artifactRef.summary}`),e.push(` ${n.artifactRef.path}`);return e}function k(i){return i.length===0?"No artifacts matched.":i.map(e=>h(e)).join(`
2
2
 
3
- `)}function $(i){if(typeof i.payload?.text=="string")return i.payload.text.trimEnd();const e=c(i.payload?.data);return typeof e?.text=="string"?e.text.trimEnd():i.artifactRef.contentType==="application/json"&&d.existsSync(i.absolutePath)?d.readFileSync(i.absolutePath,"utf8").trimEnd():JSON.stringify(i.payload?.data??i.payload??null,null,2)}
3
+ `)}function h(i){if(typeof i.payload?.text=="string")return i.payload.text.trimEnd();const e=s(i.payload?.data);return typeof e?.text=="string"?e.text.trimEnd():i.artifactRef.contentType==="application/json"&&d.existsSync(i.absolutePath)?d.readFileSync(i.absolutePath,"utf8").trimEnd():JSON.stringify(i.payload?.data??i.payload??null,null,2)}
@@ -1,2 +1,3 @@
1
- import{bugRerun as c,fileBug as p,prepareBugFiling as f}from"../../../app/bugs/file.js";import{readRunArtifact as l}from"../../../app/bugs/run-reading.js";import{buildRunRequest as m}from"../../../app/run/request.js";import{executeRunRequest as v}from"../../operations/run/operation.js";import{renderAlreadyFiledBug as w,renderBugFileResult as R,renderBugFileRun as y}from"../../renderers/bug/text.js";export default async function b({args:o,flags:e,json:t,log:i}){const n={baseUrl:e["base-url"],database:e.database,endpoint:e.endpoint,env:e.env,envFrom:e["env-from"],environmentFile:e["environment-file"]},r=await f({cwd:process.cwd(),dir:e.dir,env:process.env,id:o.id,supplied:n});if(r.status==="already-filed")return t||i(w(r.issue)),{ok:!0,created:!1,draft:r.draft,issue:r.issue};t||i(y(r));const u=await c(r,e,n),d=await m({allowEmpty:!1,dir:e.dir,file:u.files,ignoreSkipRules:!1,skipSchemaSourceVerify:!1,supplied:n,vitest:u.ready.draft.vitest},null,process.cwd(),process.cwd(),process.env),s=await v(d,{json:t,outputMode:"compact"}),a=await p(u.ready,l(s.runArtifact,r.checkout.productPath));return t||i(R(a).join(`
2
- `)),{ok:!0,created:!0,...a}}
1
+ import{bugBatchReport as R,fileBugBatch as y,planBugBatchRuns as b,prepareBugBatch as k,rejectedBugBatchDraft as F}from"../../../app/bugs/batch.js";import{bugRerun as S,fileBug as q,prepareBugFiling as A}from"../../../app/bugs/file.js";import{readRunArtifact as j,readRunArtifactFile as E}from"../../../app/bugs/run-reading.js";import{buildRunRequest as g}from"../../../app/run/request.js";import{CliUsageError as w}from"../../framework/errors.js";import{executeRunRequest as B}from"../../operations/run/operation.js";import{renderAlreadyFiledBug as M,renderBugBatchResult as P,renderBugBatchRun as U,renderBugFileResult as x,renderBugFileRun as V}from"../../renderers/bug/text.js";export default async function C(o){const{args:t,flags:e,json:a,log:s}=o,r={baseUrl:e["base-url"],database:e.database,endpoint:e.endpoint,env:e.env,envFrom:e["env-from"],environmentFile:e["environment-file"]};if(e.batch)return D(o,r);if(t.id.length===0)throw new w("Missing required argument id");if(t.id.length>1)throw new w(`Unexpected argument ${JSON.stringify(t.id[1])}. Pass --batch to file several drafts.`);const[c]=t.id,i=await A({cwd:process.cwd(),dir:e.dir,env:process.env,id:c,supplied:r});if(i.status==="already-filed")return a||s(M(i.issue)),{ok:!0,created:!1,draft:i.draft,issue:i.issue};a||s(V(i));const n=await S(i,e,r),f=await g({allowEmpty:!1,dir:e.dir,file:n.files,ignoreSkipRules:!1,skipSchemaSourceVerify:!1,supplied:r,vitest:n.ready.draft.vitest},null,process.cwd(),process.cwd(),process.env),d=await B(f,{json:a,outputMode:"compact"}),u=await q(n.ready,j(d.runArtifact,i.checkout.productPath));return a||s(x(u).join(`
2
+ `)),{ok:!0,created:!0,...u}}async function D({args:o,flags:t,json:e,log:a,setExitCode:s},r){const c=await k({cwd:process.cwd(),dir:t.dir,env:process.env,ids:o.id,supplied:r}),i=await b(c,t,r),n=[...c.settled,...i.rejected],f=[];for(const{drafts:u,files:m,vitest:v}of i.runs){e||a(U(u,m));try{const{runArtifact:l}=await J(m,v,t.dir,r,e),{productPath:p}=c.checkout;f.push(...u.map(h=>({ready:h,run:E(l,p,h.draft.test.file)})))}catch(l){n.push(...u.map(p=>F(p.draft.id,l)))}}n.push(...await y(f));const d=R(c,n);return d.ok||s(1),e||a(P(d.drafts).join(`
3
+ `)),d}async function J(o,t,e,a,s){const r=await g({allowEmpty:!1,dir:e,file:o,ignoreSkipRules:!1,skipSchemaSourceVerify:!1,supplied:a,vitest:t},null,process.cwd(),process.cwd(),process.env);return B(r,{json:s,outputMode:"compact"})}
@@ -1 +1 @@
1
- import{executeDiscoverOperation as n}from"../../app/discover/index.js";import{renderDiscoverResult as p}from"../renderers/discover/text.js";import{selectedGroup as c}from"../command-flags.js";export default async function d({flags:t,json:o,log:u,notice:e}){const r=await n({diagnostics:t.strict?"error":"report",dir:t.dir,file:t.file||[],group:c(t,e),notice:e,output:t.output,runnableOnly:t["runnable-only"],suite:t.suite||[],type:t.type||[],vitest:t},process.cwd());if(!o)for(const i of p(r,{outputMode:t["output-mode"]}))u(i);return r}
1
+ import{executeDiscoverOperation as i}from"../../app/discover/index.js";import{renderDiscoverResult as n}from"../renderers/discover/text.js";import{selectedGroup as p}from"../command-flags.js";export default async function c({flags:t,json:r,log:o}){const e=await i({diagnostics:t.strict?"error":"report",dir:t.dir,file:t.file||[],group:p(t),output:t.output,runnableOnly:t["runnable-only"],suite:t.suite||[],type:t.type||[],vitest:t},process.cwd());if(!r)for(const u of n(e,{outputMode:t["output-mode"]}))o(u);return e}
@@ -1,2 +1,2 @@
1
- export default function t({flags:e}){throw Object.assign(new Error(`${e.command} is disabled because DB-touching tests must run through the isolated testkit harness.
1
+ export default function t({flags:e}){throw Object.assign(new Error(`${e.command} is disabled because this repository runs its tests through Testkit.
2
2
  Use ${e.use} instead.`),{exitCode:1})}
@@ -1,2 +1,2 @@
1
- import{applyKnownFailures as m,loadBugPolicy as l}from"../../app/bugs/gate.js";import{buildRunRequest as a}from"../../app/run/request.js";import{selectedGroup as w}from"../command-flags.js";import{executeRunRequest as y}from"../operations/run/operation.js";import{renderKnownFailures as k}from"../renderers/bug/text.js";export default async function v({args:n,flags:e,json:u,log:p,notice:i,setExitCode:c}){const d=w(e,i),t=await a({dir:e.dir,group:d??void 0,taskConcurrency:e["task-concurrency"],workers:e.workers,fileTimeoutSeconds:e["file-timeout-seconds"],type:e.type,suite:e.suite,file:e.file,allowEmpty:e["allow-empty"],ignoreSkipRules:e["ignore-skip-rules"],skipSchemaSourceVerify:e["skip-schema-source-verify"],supplied:{baseUrl:e["base-url"],database:e.database,endpoint:e.endpoint,env:e.env,envFrom:e["env-from"],environmentFile:e["environment-file"]},vitest:e},n.type||null,process.cwd(),process.cwd());t.ownershipNotice!==null&&i(t.ownershipNotice);const s=await l(t.productDir),o=await y(t,{outputMode:e["output-mode"]||"compact",json:u,debug:e.debug}),r=await m({env:process.env,exitCode:o.exitCode,failedOutsideTests:o.failure!==void 0,policy:s,productDir:t.productDir,run:o.runArtifact});return r.knownFailures!==null&&(o.outputMode==="compact"||o.outputMode==="debug")&&p(k(r.knownFailures).join(`
2
- `)),c(r.exitCode),r.knownFailures===null?o:{...o,...r}}
1
+ import{applyKnownFailures as a,loadBugPolicy as m}from"../../app/bugs/gate.js";import{buildRunRequest as l}from"../../app/run/request.js";import{selectedGroup as w}from"../command-flags.js";import{executeRunRequest as y}from"../operations/run/operation.js";import{renderKnownFailures as k}from"../renderers/bug/text.js";export default async function v({args:r,flags:e,json:i,log:u,notice:c,setExitCode:d}){const p=w(e),t=await l({dir:e.dir,group:p??void 0,taskConcurrency:e["task-concurrency"],workers:e.workers,fileTimeoutSeconds:e["file-timeout-seconds"],type:e.type,suite:e.suite,file:e.file,allowEmpty:e["allow-empty"],ignoreSkipRules:e["ignore-skip-rules"],skipSchemaSourceVerify:e["skip-schema-source-verify"],supplied:{baseUrl:e["base-url"],database:e.database,endpoint:e.endpoint,env:e.env,envFrom:e["env-from"],environmentFile:e["environment-file"]},vitest:e},r.type||null,process.cwd(),process.cwd()),s=await m(t.productDir),n=await y(t,{outputMode:e["output-mode"]||"compact",json:i,debug:e.debug}),o=await a({env:process.env,exitCode:n.exitCode,failedOutsideTests:n.failure!==void 0,policy:s,productDir:t.productDir,run:n.runArtifact});return o.knownFailures!==null&&(n.outputMode==="compact"||n.outputMode==="debug")&&u(k(o.knownFailures).join(`
2
+ `)),n.maintenance?.status==="failed"&&c(`Post-run maintenance failed: ${n.maintenance.error??"unknown error"}. The run's result is unchanged.`),d(o.exitCode),o.knownFailures===null?n:{...n,...o}}
@@ -1 +1 @@
1
- import{runTestkitTypecheck as s,TestkitTypecheckError as u}from"../../app/typecheck/index.js";import{runWithCliProcessSignal as f}from"../process-signal.js";import{renderTypecheckResult as m}from"../renderers/typecheck/text.js";export default async function p({flags:r,json:i,log:e,notice:n}){const o=await f(t=>s({dir:r.dir,notice:n,signal:t,vitest:r})).catch(t=>{if(!i&&t instanceof u)for(const c of[t.result.stdout,t.result.stderr])c.trim()&&e(c.trimEnd());throw t});if(!i)for(const t of m(o))e(t);return o}
1
+ import{runTestkitTypecheck as n,TestkitTypecheckError as u}from"../../app/typecheck/index.js";import{runWithCliProcessSignal as s}from"../process-signal.js";import{renderTypecheckResult as f}from"../renderers/typecheck/text.js";export default async function p({flags:r,json:i,log:e}){const o=await s(t=>n({dir:r.dir,group:r.group,signal:t,vitest:r})).catch(t=>{if(!i&&t instanceof u)for(const c of[t.result.stdout,t.result.stderr])c.trim()&&e(c.trimEnd());throw t});if(!i)for(const t of f(o))e(t);return o}
@@ -1 +1 @@
1
- import{inspectWorkspace as a}from"../../app/inspect/index.js";import{selectedGroup as u}from"../command-flags.js";export default async function l({flags:r,json:e,log:t,notice:i}){const s=await a({dir:r.dir,group:u(r,i),notice:i,status:!r["no-status"],targetLimit:r["target-limit"]});if(!e&&(t(`${s.product.name} \xB7 Testkit workspace`),s.discovery&&t(` ${String(s.discovery.summary.groups)} groups \xB7 ${String(s.discovery.summary.suites)} suites \xB7 ${String(s.discovery.summary.files)} files`),s.runs.live?t(` live ${s.runs.live.status} \xB7 ${String(s.runs.live.files.passed)}/${String(s.runs.live.files.total)} files complete`):s.runs.latest&&t(` latest ${s.runs.latest.status} \xB7 ${String(s.runs.latest.files.passed)}/${String(s.runs.latest.files.total)} files passed`),s.warnings.length>0))for(const n of s.warnings)t(` warning: ${n}`);return s}
1
+ import{inspectWorkspace as n}from"../../app/inspect/index.js";import{selectedGroup as a}from"../command-flags.js";export default async function u({flags:i,json:r,log:t}){const s=await n({dir:i.dir,group:a(i),status:!i["no-status"],targetLimit:i["target-limit"]});if(!r&&(t(`${s.product.name} \xB7 Testkit workspace`),s.discovery&&t(` ${String(s.discovery.summary.groups)} groups \xB7 ${String(s.discovery.summary.suites)} suites \xB7 ${String(s.discovery.summary.files)} files`),s.runs.live?t(` live ${s.runs.live.status} \xB7 ${String(s.runs.live.files.passed)}/${String(s.runs.live.files.total)} files complete`):s.runs.latest&&t(` latest ${s.runs.latest.status} \xB7 ${String(s.runs.latest.files.passed)}/${String(s.runs.latest.files.total)} files passed`),s.warnings.length>0))for(const e of s.warnings)t(` warning: ${e}`);return s}
@@ -1,8 +1,8 @@
1
- export function renderVersion(n){return`${n.name}/${n.version} ${process.platform}-${process.arch} node-v${process.versions.node}
2
- `}export function renderRootHelp(n,e){const o=[...e].sort(u);return`${[n.description,"","Usage:"," testkit <command> [options]","","Commands:",...o.map(a=>i(a)),"","Global options:"," -h, --help Show help"," -v, --version Show version"].join(`
1
+ export function renderVersion(e){return`${e.name}/${e.version} ${process.platform}-${process.arch} node-v${process.versions.node}
2
+ `}export function renderRootHelp(e,n){const o=[...n].sort(p);return`${[e.description,"","Usage:"," testkit <command> [options]","","Commands:",...o.map(a=>i(a)),"","Global options:"," -h, --help Show help"," -v, --version Show version"].join(`
3
3
  `)}
4
- `}export function renderTopicHelp(n,e){const o=n.join(" "),c=e.filter(s=>s.id.length>n.length&&n.every((l,r)=>s.id[r]===l)).sort(u);return`${[`Testkit ${o} commands`,"","Usage:",` testkit ${o} <command> [options]`,"","Commands:",...c.map(s=>i(s))].join(`
4
+ `}export function renderTopicHelp(e,n){const o=e.join(" "),m=n.filter(s=>s.id.length>e.length&&e.every((l,r)=>s.id[r]===l)).sort(p);return`${[`Testkit ${o} commands`,"","Usage:",` testkit ${o} <command> [options]`,"","Commands:",...m.map(s=>i(s))].join(`
5
5
  `)}
6
- `}export function renderCommandHelp(n,e){const o=n,c=n.id.join(" "),a=Object.entries(o.args??{}),s=e.filter(t=>t.id.length===n.id.length+1&&n.id.every((m,$)=>t.id[$]===m)).sort(u),l=a.map(([t,m])=>m.required?`<${t}>`:`[${t}]`).join(" "),r=[o.summary??c,"","Usage:",` testkit ${c}${l?` ${l}`:""} [options]`];a.length>0&&r.push("","Arguments:",...a.map(([t,m])=>h(t,m)));const p=Object.entries(o.flags??{});return(p.length>0||o.enableJsonFlag)&&(r.push("","Options:",...p.map(([t,m])=>g(t,m))),o.enableJsonFlag&&r.push(" --json Write one machine-readable JSON document")),r.push(" -h, --help Show help for this command"),s.length>0&&r.push("","Subcommands:",...s.map(t=>i(t))),o.description&&r.push("",o.description),`${r.join(`
6
+ `}export function renderCommandHelp(e,n){const o=e,m=e.id.join(" "),a=Object.entries(o.args??{}),s=n.filter(t=>t.id.length===e.id.length+1&&e.id.every((c,u)=>t.id[u]===c)).sort(p),l=a.map(([t,c])=>{const u=c.multiple?`${t}...`:t;return c.required?`<${u}>`:`[${u}]`}).join(" "),r=[o.summary??m,"","Usage:",` testkit ${m}${l?` ${l}`:""} [options]`];a.length>0&&r.push("","Arguments:",...a.map(([t,c])=>h(t,c)));const d=Object.entries(o.flags??{});return(d.length>0||o.enableJsonFlag)&&(r.push("","Options:",...d.map(([t,c])=>g(t,c))),o.enableJsonFlag&&r.push(" --json Write one machine-readable JSON document")),r.push(" -h, --help Show help for this command"),s.length>0&&r.push("","Subcommands:",...s.map(t=>i(t))),o.description&&r.push("",o.description),`${r.join(`
7
7
  `)}
8
- `}export function commandPath(n){return n.id.join(" ")}function i(n){return` ${n.id.join(" ")} ${n.summary??""}`.trimEnd()}function h(n,e){const o=d(e.options);return` ${n}${e.required?" (required)":""}${o} ${e.description??""}`.trimEnd()}function g(n,e){const o=e.char?`-${e.char}, `:"",c=e.kind==="boolean"&&e.allowNo?`--[no-]${n}`:`--${n}`,a=e.kind==="boolean"?"":` <${e.kind==="integer"?"integer":"value"}>`,s=e.multiple?" (repeatable)":"",l=e.required?" (required)":"",r=e.default===void 0?"":` (default: ${String(e.default)})`;return` ${o}${c}${a}${s}${l}${d(e.options)}${r} ${e.description??""}`.trimEnd()}function d(n){return n?` (${n.join("|")})`:""}function u(n,e){return commandPath(n).localeCompare(commandPath(e))}
8
+ `}export function commandPath(e){return e.id.join(" ")}function i(e){return` ${e.id.join(" ")} ${e.summary??""}`.trimEnd()}function h(e,n){const o=$(n.options);return` ${e}${n.required?" (required)":""}${o} ${n.description??""}`.trimEnd()}function g(e,n){const o=n.char?`-${n.char}, `:"",m=n.kind==="boolean"&&n.allowNo?`--[no-]${e}`:`--${e}`,a=n.kind==="boolean"?"":` <${n.kind==="integer"?"integer":"value"}>`,s=n.multiple?" (repeatable)":"",l=n.required?" (required)":"",r=n.default===void 0?"":` (default: ${String(n.default)})`;return` ${o}${m}${a}${s}${l}${$(n.options)}${r} ${n.description??""}`.trimEnd()}function $(e){return e?` (${e.join("|")})`:""}function p(e,n){return commandPath(e).localeCompare(commandPath(n))}
@@ -1 +1 @@
1
- import"./definitions.js";import{CliUsageError as l}from"./errors.js";export function parseCommandArguments(n,t,e,i){const s=t??{},o=y(s),a=q(s),r=new Set,f=[];let c=!1;for(let u=0;u<n.length;u+=1){const d=n[u];if(c){f.push(d);continue}if(d==="--"){c=!0;continue}if(d==="--json"){if(!i)throw g(d);continue}if(d.startsWith("--")){u=m(n,u,{aliases:o,definitions:s,flags:a,seenFlags:r});continue}if(d.startsWith("-")&&d!=="-"){u=v(n,u,{aliases:o,definitions:s,flags:a,seenFlags:r});continue}f.push(d)}return E(s,a),{args:j(f,e??{}),flags:a}}function m(n,t,e){const i=n[t],s=i.indexOf("="),o=i.slice(2,s<0?void 0:s),a=k(o,e.definitions);if(!a)throw g(`--${o}`);const{definition:r,name:f,negated:c}=a;if(r.kind==="boolean"){if(s>=0)throw new l(`Boolean flag --${o} does not take a value`);if(c&&!r.allowNo)throw g(`--${o}`);return h(f,r,e.seenFlags),e.flags[f]=!c,t}if(c)throw g(`--${o}`);const w=s<0?null:i.slice(s+1),u=p(n,t,`--${f}`,w,r);return h(f,r,e.seenFlags),$(e.flags,f,r,u.values),u.index}function v(n,t,e){const i=n[t],s=i.slice(1,2),o=e.aliases.get(s);if(!o)throw g(`-${s}`);const a=e.definitions[o],r=i.slice(2);if(a.kind==="boolean"){if(r.startsWith("="))throw new l(`Boolean flag -${s} does not take a value`);if(r.length>0)throw g(i);return h(o,a,e.seenFlags),e.flags[o]=!0,t}const f=r.length===0?null:r.startsWith("=")?r.slice(1):r,c=p(n,t,`-${s}`,f,a);return h(o,a,e.seenFlags),$(e.flags,o,a,c.values),c.index}function p(n,t,e,i,s){const o=[];let a=t;if(i!==null){if(i.length===0)throw new l(`Flag ${e} requires a value`);o.push(i)}else{const r=n.at(t+1);if(r===void 0||F(r,s))throw new l(`Flag ${e} requires a value`);o.push(r),a+=1}if(s.multiple)for(;;){const r=n.at(a+1);if(r===void 0||F(r,s))break;o.push(r),a+=1}return{index:a,values:o}}function $(n,t,e,i){if(e.multiple){const o=n[t];if(o!==void 0&&!Array.isArray(o))throw new Error(`Invalid internal default for multiple flag --${t}`);const a=o??[];for(const r of i)a.push(b(t,e,r));n[t]=a;return}const s=i.at(0);if(s===void 0)throw new Error(`Missing parsed value for --${t}`);n[t]=b(t,e,s)}function k(n,t){if(Object.hasOwn(t,n))return{definition:t[n],name:n,negated:!1};if(!n.startsWith("no-"))return null;const e=n.slice(3);return Object.hasOwn(t,e)?{definition:t[e],name:e,negated:!0}:null}function h(n,t,e){if(!t.multiple&&e.has(n))throw new l(`Flag --${n} can only be specified once`);e.add(n)}function F(n,t){return n==="-"||t.kind==="integer"&&/^-\d+$/u.test(n)?!1:n.startsWith("-")}function b(n,t,e){if(t.kind==="integer"){if(!/^-?\d+$/u.test(e))throw new l(`Expected --${n} to be an integer; received ${JSON.stringify(e)}`);const i=Number(e);if(!Number.isSafeInteger(i))throw new l(`Expected --${n} to be a safe integer; received ${JSON.stringify(e)}`);return i}return O(e,t.options,`--${n}`),e}function j(n,t){const e=Object.entries(t);if(n.length>e.length)throw new l(`Unexpected argument ${JSON.stringify(n[e.length])}`);const i={};for(const[s,[o,a]]of e.entries()){const r=n.at(s);if(r===void 0){if(a.required)throw new l(`Missing required argument ${o}`);i[o]=void 0;continue}a.check?.(r),O(r,a.options,o),i[o]=r}return i}function O(n,t,e){if(!(!t||t.includes(n)))throw new l(`Expected ${e} to be one of: ${t.join(", ")}; received ${JSON.stringify(n)}`)}function q(n){const t={};for(const[e,i]of Object.entries(n))t[e]=i.default;return t}function y(n){const t=new Map;for(const[e,i]of Object.entries(n)){if(!i.char)continue;if(!/^[A-Za-z0-9]$/u.test(i.char))throw new Error(`Invalid short flag alias for --${e}: ${i.char}`);const s=t.get(i.char);if(s)throw new Error(`Duplicate short flag alias -${i.char} for --${s} and --${e}`);t.set(i.char,e)}return t}function E(n,t){const e=Object.entries(n).filter(([i,s])=>s.required&&t[i]===void 0).map(([i])=>`--${i}`);if(e.length>0)throw new l(`Missing required flag${e.length===1?"":"s"}: ${e.join(", ")}`)}function g(n){return new l(`Unknown flag: ${n}`)}
1
+ import"./definitions.js";import{CliUsageError as f}from"./errors.js";export function parseCommandArguments(n,t,e,i){const s=t??{},r=y(s),l=j(s),o=new Set,a=[];let u=!1;for(let c=0;c<n.length;c+=1){const d=n[c];if(u){a.push(d);continue}if(d==="--"){u=!0;continue}if(d==="--json"){if(!i)throw g(d);continue}if(d.startsWith("--")){c=O(n,c,{aliases:r,definitions:s,flags:l,seenFlags:o});continue}if(d.startsWith("-")&&d!=="-"){c=v(n,c,{aliases:r,definitions:s,flags:l,seenFlags:o});continue}a.push(d)}return E(s,l),{args:q(a,e??{}),flags:l}}function O(n,t,e){const i=n[t],s=i.indexOf("="),r=i.slice(2,s<0?void 0:s),l=k(r,e.definitions);if(!l)throw g(`--${r}`);const{definition:o,name:a,negated:u}=l;if(o.kind==="boolean"){if(s>=0)throw new f(`Boolean flag --${r} does not take a value`);if(u&&!o.allowNo)throw g(`--${r}`);return h(a,o,e.seenFlags),e.flags[a]=!u,t}if(u)throw g(`--${r}`);const w=s<0?null:i.slice(s+1),c=$(n,t,`--${a}`,w,o);return h(a,o,e.seenFlags),m(e.flags,a,o,c.values),c.index}function v(n,t,e){const i=n[t],s=i.slice(1,2),r=e.aliases.get(s);if(!r)throw g(`-${s}`);const l=e.definitions[r],o=i.slice(2);if(l.kind==="boolean"){if(o.startsWith("="))throw new f(`Boolean flag -${s} does not take a value`);if(o.length>0)throw g(i);return h(r,l,e.seenFlags),e.flags[r]=!0,t}const a=o.length===0?null:o.startsWith("=")?o.slice(1):o,u=$(n,t,`-${s}`,a,l);return h(r,l,e.seenFlags),m(e.flags,r,l,u.values),u.index}function $(n,t,e,i,s){const r=[];let l=t;if(i!==null){if(i.length===0)throw new f(`Flag ${e} requires a value`);r.push(i)}else{const o=n.at(t+1);if(o===void 0||F(o,s))throw new f(`Flag ${e} requires a value`);r.push(o),l+=1}if(s.multiple)for(;;){const o=n.at(l+1);if(o===void 0||F(o,s))break;r.push(o),l+=1}return{index:l,values:r}}function m(n,t,e,i){if(e.multiple){const r=n[t];if(r!==void 0&&!Array.isArray(r))throw new Error(`Invalid internal default for multiple flag --${t}`);const l=r??[];for(const o of i)l.push(b(t,e,o));n[t]=l;return}const s=i.at(0);if(s===void 0)throw new Error(`Missing parsed value for --${t}`);n[t]=b(t,e,s)}function k(n,t){if(Object.hasOwn(t,n))return{definition:t[n],name:n,negated:!1};if(!n.startsWith("no-"))return null;const e=n.slice(3);return Object.hasOwn(t,e)?{definition:t[e],name:e,negated:!0}:null}function h(n,t,e){if(!t.multiple&&e.has(n))throw new f(`Flag --${n} can only be specified once`);e.add(n)}function F(n,t){return n==="-"||t.kind==="integer"&&/^-\d+$/u.test(n)?!1:n.startsWith("-")}function b(n,t,e){if(t.kind==="integer"){if(!/^-?\d+$/u.test(e))throw new f(`Expected --${n} to be an integer; received ${JSON.stringify(e)}`);const i=Number(e);if(!Number.isSafeInteger(i))throw new f(`Expected --${n} to be a safe integer; received ${JSON.stringify(e)}`);return i}return p(e,t.options,`--${n}`),e}function q(n,t){const e=Object.entries(t);if(n.length>e.length&&e.at(-1)?.[1].multiple!==!0)throw new f(`Unexpected argument ${JSON.stringify(n[e.length])}`);const i={};for(const[s,[r,l]]of e.entries()){if(l.multiple){const a=n.slice(s);if(a.length===0&&l.required)throw new f(`Missing required argument ${r}`);for(const u of a)l.check?.(u),p(u,l.options,r);i[r]=a;continue}const o=n.at(s);if(o===void 0){if(l.required)throw new f(`Missing required argument ${r}`);i[r]=void 0;continue}l.check?.(o),p(o,l.options,r),i[r]=o}return i}function p(n,t,e){if(!(!t||t.includes(n)))throw new f(`Expected ${e} to be one of: ${t.join(", ")}; received ${JSON.stringify(n)}`)}function j(n){const t={};for(const[e,i]of Object.entries(n))t[e]=i.default;return t}function y(n){const t=new Map;for(const[e,i]of Object.entries(n)){if(!i.char)continue;if(!/^[A-Za-z0-9]$/u.test(i.char))throw new Error(`Invalid short flag alias for --${e}: ${i.char}`);const s=t.get(i.char);if(s)throw new Error(`Duplicate short flag alias -${i.char} for --${s} and --${e}`);t.set(i.char,e)}return t}function E(n,t){const e=Object.entries(n).filter(([i,s])=>s.required&&t[i]===void 0).map(([i])=>`--${i}`);if(e.length>0)throw new f(`Missing required flag${e.length===1?"":"s"}: ${e.join(", ")}`)}function g(n){return new f(`Unknown flag: ${n}`)}
@@ -1 +1 @@
1
- import{hasErrorCode as i,serializeError as n}from"../kernel/error-serialization.js";export function serializeCliError(r){return n(r)}export function writeStandardOutput(r){return new Promise((o,s)=>{const e=t=>{t?i(t,"EPIPE")?o():s(t):(process.stdout.off("error",e),o())};process.stdout.once("error",e),process.stdout.write(r,e)})}
1
+ import{hasErrorCode as s,serializeError as n}from"../kernel/error-serialization.js";export function serializeCliError(r){return n(r)}export function writeStandardOutput(r){return process.stdout.listeners("error").includes(o)||process.stdout.on("error",o),new Promise((t,i)=>{process.stdout.write(r,e=>{!e||s(e,"EPIPE")?t():i(e)})})}function o(){}
@@ -1 +1 @@
1
- import{formatShare as s}from"../../../app/campaigns/brief.js";const i=12,a=20;export function renderBugRecordResult(e){return[`Recorded ${e.draft.id}`,`Draft: ${e.path}`,...e.redactedFields.length===0?[]:[`Redacted secrets in: ${e.redactedFields.join(", ")}`],...e.replaced?["Replaced the earlier draft for this test."]:[],...e.unreadableDrafts.map(n=>`Testkit cannot read ${n.file}: ${n.problem}`),`Next: commit and push the test, then run \`testkit bug file ${e.draft.id}\`.`]}export function renderAlreadyFiledBug(e){return`Already filed as #${String(e.number)}: ${e.url}`}export function renderBugFileRun(e){return`Running ${e.draft.test.file} at ${e.filing.foundAt.slice(0,i)}. Testkit files the bug when the test fails.`}export function renderBugFileResult(e){return[`Filed #${String(e.issue.number)}: ${e.issue.url}`,e.area===null?"Area: none, because testkit.config.ts has no areas":`Area: ${e.area.id}, ${s(e.area.share)} of source bytes`,e.introducedBy===null?"Introduced by: unknown, because this shallow clone stops before the commit that wrote the faulty lines":`Introduced by: ${e.introducedBy.slice(0,i)}`,`Draft: ${e.path}`]}export function renderKnownFailures(e){const n=e.known.length,t=n-a;return[...e.trackerProblem===null?[]:[`Testkit could not read the open bugs, so every failure counts as new: ${e.trackerProblem}`],e.new.length===0?`Known failures: ${String(n)}. Every failing test has an open bug, so the run passes.`:`New failures: ${String(e.new.length)}. Known failures: ${String(n)}. The run fails on the new failures.`,...e.known.slice(0,a).map(({failure:r,issue:o})=>` #${String(o.issue)} ${r.file}: ${r.title??"failed without naming a test"}`),...t>0?[` and ${String(t)} more`]:[]]}
1
+ import{formatShare as d}from"../../../app/campaigns/brief.js";const r=12,a=20;export function renderBugRecordResult(e){return[`Recorded ${e.draft.id}`,`Draft: ${e.path}`,...e.redactedFields.length===0?[]:[`Redacted secrets in: ${e.redactedFields.join(", ")}`],...e.replaced?["Replaced the earlier draft for this test."]:[],...e.unreadableDrafts.map(t=>`Testkit cannot read ${t.file}: ${t.problem}`),`Next: commit and push the test, then run \`testkit bug file ${e.draft.id}\`.`]}export function renderAlreadyFiledBug(e){return`Already filed as #${String(e.number)}: ${e.url}`}export function renderBugFileRun(e){return`Running ${e.draft.test.file} at ${e.filing.foundAt.slice(0,r)}. Testkit files the bug when the test fails.`}export function renderBugFileResult(e){return[`Filed #${String(e.issue.number)}: ${e.issue.url}`,e.area===null?"Area: none, because testkit.config.ts has no areas":`Area: ${e.area.id}, ${d(e.area.share)} of source bytes`,e.introducedBy===null?"Introduced by: unknown, because this shallow clone stops before the commit that wrote the faulty lines":`Introduced by: ${e.introducedBy.slice(0,r)}`,`Draft: ${e.path}`,...e.listed?[]:[`The tracker had not listed #${String(e.issue.number)} among the open issues after 7.5 seconds, so Testkit could not check whether another checkout filed this test at the same time. Look for a second open issue for this test.`]]}export function renderBugBatchRun(e,t){const n=e.at(0)?.filing.foundAt.slice(0,r)??"";return`Running ${String(t.length)} ${t.length===1?"file":"files"} at ${n} for ${e.map(({draft:i})=>i.id).join(", ")}. Testkit files each bug whose test fails.`}export function renderBugBatchResult(e){const t=n=>e.filter(i=>i.status===n).length;return[...e.map(n=>{switch(n.status){case"filed":return`Filed ${n.id} as #${String(n.issue.number)}: ${n.issue.url}`;case"already-filed":return`Already filed ${n.id} as #${String(n.issue.number)}: ${n.issue.url}`;case"rejected":return`Rejected ${n.id}: ${n.reason}`;case"not-filed":return`Not filed ${n.id}: ${n.reason}`}}),`Filed ${String(t("filed"))}, already filed ${String(t("already-filed"))}, rejected ${String(t("rejected"))}, not filed ${String(t("not-filed"))}.`]}export function renderKnownFailures(e){const t=e.known.length,n=t-a;return[...e.trackerProblem===null?[]:[`Testkit could not read the open bugs, so every failure counts as new: ${e.trackerProblem}`],e.new.length===0?`Known failures: ${String(t)}. Every failing test has an open bug, so the run passes.`:`New failures: ${String(e.new.length)}. Known failures: ${String(t)}. The run fails on the new failures.`,...e.known.slice(0,a).map(({failure:i,issue:s})=>` #${String(s.issue)} ${i.file}: ${i.title??"failed without naming a test"}`),...n>0?[` and ${String(n)} more`]:[]]}
@@ -1 +1 @@
1
- import{sanitizeErrorMessage as c}from"../../../kernel/error-serialization.js";export function formatDuration(e){const t=Math.max(0,Math.round(e/1e3)),i=Math.floor(t/60),r=t%60;return i===0?`${String(r)}s`:`${String(i)}m ${String(r).padStart(2,"0")}s`}function x(e){const t=e.skippedSuiteCount||0,i=e.completedSuiteCount-e.failedSuiteCount-t,r=e.suiteCount-e.completedSuiteCount;let s=`${String(i)}/${String(e.suiteCount)} suites passed`;return(e.totalFileCount||0)>0&&(s+=`, ${String(e.passedFileCount)}/${String(e.totalFileCount)} files passed`),t>0?s+=`, ${String(t)} ${f(t,"suite","suites")} skipped`:(e.skippedFileCount||0)>0&&(s+=`, ${String(e.skippedFileCount)} ${f(e.skippedFileCount,"file","files")} skipped`),r>0?s+=`, ${String(r)} ${f(r,"suite","suites")} not run`:(e.notRunFileCount||0)>0&&(s+=`, ${String(e.notRunFileCount)} ${f(e.notRunFileCount,"file","files")} not run`),s}function A(e){return e.reduce((t,i)=>Math.max(t,i.name.length),4)}export function buildRunSummaryData(e,t,i=null){const r=M(e),s=L(e);return{result:i==="failed"||i==="interrupted"||r.failedServices>0||r.infraFailedServices>0?"FAILED":"PASSED",totalServices:r.totalServices,failedServices:r.failedServices,infraFailedServices:r.infraFailedServices,blockedByRuntimeServices:r.blockedByRuntimeServices,passed:r.passedFiles,failed:r.failedFiles,skipped:r.skippedFiles,notRun:r.notRunFiles,files:r.totalFiles,duration:formatDuration(t),serviceErrors:s}}export function buildDebugRunSummaryLines(e,t,i=null){const r=e.length,s=e.filter(n=>!n.skipped),u=e.filter(n=>n.skipped),d=s.filter(n=>n.failed),p=s.filter(n=>n.infraFailed),m=s.filter(n=>!n.failed&&!n.infraFailed),C=s.reduce((n,o)=>n+o.suiteCount,0),h=s.reduce((n,o)=>n+o.completedSuiteCount,0),S=s.reduce((n,o)=>n+(o.skippedSuiteCount||0),0),R=s.reduce((n,o)=>n+o.failedSuiteCount,0),E=h-R-S,g=s.reduce((n,o)=>n+(o.totalFileCount||0),0),D=s.reduce((n,o)=>n+(o.passedFileCount||0),0),$=s.reduce((n,o)=>n+(o.skippedFileCount||0),0),a=["","\u2550\u2550 Summary \u2550\u2550",[`groups ${String(m.length)}/${String(s.length)} passed`,`suites ${String(E)}/${String(C)} passed`,g>0?`files ${String(D)}/${String(g)} passed`:null,S>0?`suites ${String(S)} skipped`:null,$>0?`files ${String($)} skipped`:null,u.length>0?`${String(u.length)} skipped`:null,`duration ${formatDuration(t)}`].filter(Boolean).join(" \xB7 ")];for(const n of e){const o=j(n)?"SKIP":n.failed?"FAIL":n.infraFailed?"INFRA":"PASS",b=n.skipped?"no matching suites":x(n);if(a.push(`${o.padEnd(4)} ${n.name.padEnd(A(e))} ${b} \xB7 ${formatDuration(n.durationMs)}`),!(!n.failed&&!n.infraFailed)){for(const l of n.suites.filter(F=>F.failed)){const F=l.failedFiles.length>0?` (${l.failedFiles.join(", ")})`:"",y=[l.testType,l.name].filter(Boolean).join(":");a.push(` - ${y}${F} \xB7 ${formatDuration(l.durationMs)}`),l.error&&a.push(` ${l.error}`)}for(const l of n.errors)a.push(` - error: ${l}`)}}return i==="failed"||i==="interrupted"||d.length>0||p.length>0?(a.push("",d.length===0&&p.length===0?"Result: FAILED (run gate)":`Result: FAILED (${I({failedServices:d.length,infraFailedServices:p.length,totalServices:r})})`),a):(a.push("","Result: PASSED"),a)}export function buildFailurePresentation(e){const t=P(e.failureDetails||[]),i=t[0]||null,r=t.map(u=>c((u.message||u.title).trim())),s=[];for(const u of t.slice(1)){const d=v(u.message||u.title,220);d&&!s.includes(d)&&s.push(d)}return{primary:v(B(e,{error:e.suiteError||null},i,r),240),details:s}}function M(e){const t=e.filter(i=>!i.skipped);return{totalServices:e.length,failedServices:t.filter(i=>i.failed).length,infraFailedServices:t.filter(i=>i.infraFailed).length,blockedByRuntimeServices:t.filter(i=>i.blockedByRuntime).length,totalFiles:t.reduce((i,r)=>i+(r.totalFileCount||0),0),passedFiles:t.reduce((i,r)=>i+(r.passedFileCount||0),0),failedFiles:t.reduce((i,r)=>i+(r.failedFileCount||0),0),skippedFiles:t.reduce((i,r)=>i+(r.skippedFileCount||0),0),notRunFiles:t.reduce((i,r)=>i+(r.notRunFileCount||0),0)}}function I(e){const t=[`${String(e.failedServices)}/${String(e.totalServices)} groups failed`];return(e.infraFailedServices||0)>0&&t.push(`${String(e.infraFailedServices)} infra-blocked`),t.join(", ")}function B(e,t,i,r){if(i?.message)return c(i.message.trim());const s=c(e.error||"");return s||(r.length>0?r[0]:s||c(t.error||"Failed"))}function P(e){return[...e].sort((t,i)=>k(t)-k(i)||(t.key||"").localeCompare(i.key||""))}function k(e){return e.location||e.stack?3:e.message?4:5}function v(e,t=180){return e.replace(/\s+/g," ").trim().slice(0,t)}function L(e){return e.reduce((t,i)=>t+i.errors.length,0)}function f(e,t,i){return e===1?t:i}function j(e){return e.skipped?!0:!e.failed&&(e.skippedSuiteCount||0)>0&&(e.skippedSuiteCount||0)===e.suiteCount&&(e.notRunFileCount||0)===0}
1
+ import{stripVTControlCharacters as M}from"node:util";import{sanitizeErrorMessage as I}from"../../../kernel/error-serialization.js";const B=new Intl.Segmenter(void 0,{granularity:"grapheme"}),P=40;export function formatDuration(e){const t=Math.max(0,Math.round(e/1e3)),i=Math.floor(t/60),n=t%60;return i===0?`${String(n)}s`:`${String(i)}m ${String(n).padStart(2,"0")}s`}function x(e){const t=e.skippedSuiteCount||0,i=e.completedSuiteCount-e.failedSuiteCount-t,n=e.suiteCount-e.completedSuiteCount;let s=`${String(i)}/${String(e.suiteCount)} suites passed`;return(e.totalFileCount||0)>0&&(s+=`, ${String(e.passedFileCount)}/${String(e.totalFileCount)} files passed`),t>0?s+=`, ${String(t)} ${f(t,"suite","suites")} skipped`:(e.skippedFileCount||0)>0&&(s+=`, ${String(e.skippedFileCount)} ${f(e.skippedFileCount,"file","files")} skipped`),n>0?s+=`, ${String(n)} ${f(n,"suite","suites")} not run`:(e.notRunFileCount||0)>0&&(s+=`, ${String(e.notRunFileCount)} ${f(e.notRunFileCount,"file","files")} not run`),s}function j(e){return e.reduce((t,i)=>Math.max(t,i.name.length),4)}export function buildRunSummaryData(e,t,i=null){const n=w(e),s=H(e);return{result:i==="failed"||i==="interrupted"||n.failedServices>0||n.infraFailedServices>0?"FAILED":"PASSED",totalServices:n.totalServices,failedServices:n.failedServices,infraFailedServices:n.infraFailedServices,blockedByRuntimeServices:n.blockedByRuntimeServices,passed:n.passedFiles,failed:n.failedFiles,skipped:n.skippedFiles,notRun:n.notRunFiles,files:n.totalFiles,duration:formatDuration(t),serviceErrors:s}}export function buildDebugRunSummaryLines(e,t,i=null){const n=e.length,s=e.filter(r=>!r.skipped),a=e.filter(r=>r.skipped),u=s.filter(r=>r.failed),c=s.filter(r=>r.infraFailed),C=s.filter(r=>!r.failed&&!r.infraFailed),v=s.reduce((r,o)=>r+o.suiteCount,0),R=s.reduce((r,o)=>r+o.completedSuiteCount,0),S=s.reduce((r,o)=>r+(o.skippedSuiteCount||0),0),D=s.reduce((r,o)=>r+o.failedSuiteCount,0),E=R-D-S,F=s.reduce((r,o)=>r+(o.totalFileCount||0),0),y=s.reduce((r,o)=>r+(o.passedFileCount||0),0),$=s.reduce((r,o)=>r+(o.skippedFileCount||0),0),d=["","\u2550\u2550 Summary \u2550\u2550",[`groups ${String(C.length)}/${String(s.length)} passed`,`suites ${String(E)}/${String(v)} passed`,F>0?`files ${String(y)}/${String(F)} passed`:null,S>0?`suites ${String(S)} skipped`:null,$>0?`files ${String($)} skipped`:null,a.length>0?`${String(a.length)} skipped`:null,`duration ${formatDuration(t)}`].filter(Boolean).join(" \xB7 ")];for(const r of e){const o=L(r)?"SKIP":r.failed?"FAIL":r.infraFailed?"INFRA":"PASS",A=r.skipped?"no matching suites":x(r);if(d.push(`${o.padEnd(4)} ${r.name.padEnd(j(e))} ${A} \xB7 ${formatDuration(r.durationMs)}`),!(!r.failed&&!r.infraFailed)){for(const l of r.suites.filter(g=>g.failed)){const g=l.failedFiles.length>0?` (${l.failedFiles.join(", ")})`:"",b=[l.testType,l.name].filter(Boolean).join(":");d.push(` - ${b}${g} \xB7 ${formatDuration(l.durationMs)}`),l.error&&d.push(` ${l.error}`)}for(const l of r.errors)d.push(` - error: ${l}`)}}return i==="failed"||i==="interrupted"||u.length>0||c.length>0?(d.push("",u.length===0&&c.length===0?"Result: FAILED (run gate)":`Result: FAILED (${z({failedServices:u.length,infraFailedServices:c.length,totalServices:n})})`),d):(d.push("","Result: PASSED"),d)}export function buildFailurePresentation(e){const t=O(e.failureDetails||[]),i=t[0]||null,n=t.map(W),s=k(N(e,{error:e.suiteError||null},i,n)),a=new Set([s]),u=[];for(const c of n.slice(1).map(k))!c||a.has(c)||(a.add(c),u.push(h(c,220)));return{primary:h(s,240),details:u}}function w(e){const t=e.filter(i=>!i.skipped);return{totalServices:e.length,failedServices:t.filter(i=>i.failed).length,infraFailedServices:t.filter(i=>i.infraFailed).length,blockedByRuntimeServices:t.filter(i=>i.blockedByRuntime).length,totalFiles:t.reduce((i,n)=>i+(n.totalFileCount||0),0),passedFiles:t.reduce((i,n)=>i+(n.passedFileCount||0),0),failedFiles:t.reduce((i,n)=>i+(n.failedFileCount||0),0),skippedFiles:t.reduce((i,n)=>i+(n.skippedFileCount||0),0),notRunFiles:t.reduce((i,n)=>i+(n.notRunFileCount||0),0)}}function z(e){const t=[`${String(e.failedServices)}/${String(e.totalServices)} groups failed`];return(e.infraFailedServices||0)>0&&t.push(`${String(e.infraFailedServices)} infra-blocked`),t.join(", ")}function N(e,t,i,n){return i?.message&&n[0]?n[0]:p(e.error||"")||n.find(s=>s!=="")||p(t.error||"")||"Failed"}function O(e){return[...e].sort((t,i)=>m(t)-m(i)||(t.key||"").localeCompare(i.key||""))}function m(e){return e.location||e.stack?3:e.message?4:5}function W(e){return p(e.message||"")||p(e.title)}function p(e){return I(M(e).trim())}function k(e){return e.replace(/\s+/g," ").trim()}function h(e,t){const i=Array.from(B.segment(e),({segment:u})=>u);if(i.length<=t)return e;const n=i.lastIndexOf(" ",t),s=t-n<=P?n:t,a=i.length-s-(i[s]===" "?1:0);return`${i.slice(0,s).join("")} \u2026 (${String(a)} more ${f(a,"character","characters")})`}function H(e){return e.reduce((t,i)=>t+i.errors.length,0)}function f(e,t,i){return e===1?t:i}function L(e){return e.skipped?!0:!e.failed&&(e.skippedSuiteCount||0)>0&&(e.skippedSuiteCount||0)===e.suiteCount&&(e.notRunFileCount||0)===0}
@@ -0,0 +1 @@
1
+ export const DATABASE_FIXTURE_DISCOVERY=Object.freeze({roots:Object.freeze([".testkit-fixture"])});
@@ -1 +1 @@
1
- import{clearRepoConfig as I,getRepoConfig as O,registerRepoConfig as $}from"./runtime.js";import{normalizeDatabaseEnvToken as b,normalizeNextPresetEnv as N,normalizePresetEnv as A}from"./preset-env.js";const P=String;export function defineConfig(e){return e||{}}function F(e={}){return{...e,...e.maxConnections?{maxConnections:e.maxConnections}:{},...e.reservedConnections?{reservedConnections:e.reservedConnections}:{},...e.runtimeConnections?{runtimeConnections:e.runtimeConnections}:{}}}function v(e,n="baseUrl"){const t=b(e,"service name",!1);return`{${b(n,"endpoint field",!1)}:${t}}`}function R(e={}){const n=E(e.migrate),t=E(e.seed),r=E(e.verify);return{inputs:Array.isArray(e.inputs)?[...e.inputs]:void 0,migrate:n,seed:t,verify:r}}function z(e,n={}){return{kind:"command",run:e,...U(n)}}function _(e,n={}){return{kind:"sql-file",path:e,...U(n)}}function u(e,n={}){return{kind:"module",target:e,...U(n)}}function U(e={}){return{...e.cwd!==void 0?{cwd:e.cwd}:{},...e.inputs!==void 0?{inputs:[...e.inputs]}:{},...e.args!==void 0?{args:e.args}:{}}}function q(e,n={}){if(typeof e!="string"||e.trim().length===0)throw new Error("database.schema.fromEnv(...) requires a non-empty env var name");return{kind:"env",env:e.trim(),...n}}function f(e){return`@gfdelarue/testkit/config/database-steps#${e}`}function M(e={}){return u(f("verifySeed"),{args:e})}function V(e={}){return u(f("verifyRows"),{args:e})}function X(e={}){return u(f("runSql"),{args:e})}function G(e={}){return u(f("seedRows"),{args:e})}function H(e={}){return u(f("materializePostgresBinding"),{args:e})}function L(e={}){return u(f("updateRows"),{args:e})}function W(){return{kind:"now"}}function J(e){return{kind:"env",name:b(e,"database.values.env(...) name",!1)}}function K(e){return{kind:"json",value:e}}function Q(e){return{kind:"postgres-connection-from-env",prefix:b(e,"database.values.postgresConnectionFromEnv(...) prefix",!1)}}function Y(e={}){const{discovery:n,envFiles:t,template:r,inputs:s,migrate:o,seed:a,verify:d,...c}=e,i=r||D(e)?R({inputs:s,migrate:o,seed:a,verify:d,...r||{}}):void 0;return{discovery:n||{roots:[".testkit-fixture"]},envFiles:t,local:!1,database:F(i?{...c,template:i}:c)}}function D(e={}){return["inputs","migrate","seed","verify"].some(n=>Object.prototype.hasOwnProperty.call(e,n))}function B(e={}){return{...e}}function Z(e={}){return{kind:"tsc",cwd:e.cwd,entry:e.entry||"src/index.ts",tsconfig:e.tsconfig||"tsconfig.json",outDir:e.outDir||"dist",inputs:Array.isArray(e.inputs)?[...e.inputs]:void 0}}function ee(e={}){return{kind:"next",cwd:e.cwd,distDir:e.distDir||"dist",tsconfig:e.tsconfig||"tsconfig.json",inputs:Array.isArray(e.inputs)?[...e.inputs]:void 0}}function ne(e={}){const{baseUrl:n,build:t,buildInputs:r,cwd:s=".",entry:o="src/index.ts",env:a,envFiles:d,outDir:c="dist",port:i,readyPath:g="/health",readyTimeoutMs:S,shutdownTimeoutMs:p,readyUrl:h,runtime:m,start:C,toolchain:k,tsconfig:T="tsconfig.json",...l}=e,y=A(a),x=n||"http://127.0.0.1:{port}",w=t===void 0?Z({cwd:s,entry:o,tsconfig:T,outDir:c,inputs:r}):t,j=C||re(w,o);return{...l,envFiles:d,runtime:{...m||{},build:w,toolchain:k??m?.toolchain},local:{cwd:s,start:j,...i===void 0?{}:{port:i},baseUrl:x,readyUrl:h??`${x}${g}`,readyTimeoutMs:S,...p===void 0?{}:{shutdownTimeoutMs:p},env:y}}}function te(e={}){const{api:n,baseUrl:t,build:r,buildInputs:s,cwd:o=".",dependsOn:a,env:d,envFiles:c,mode:i="dev",port:g,readyTimeoutMs:S,shutdownTimeoutMs:p,readyUrl:h,runtime:m,start:C,toolchain:k,...T}=e,l={...N({api:n}),...A(d)},y=t||"http://127.0.0.1:{port}",x=r===void 0?i==="start"?ee({cwd:o,inputs:s}):null:r,w=C||(i==="start"?"{bin:next} start --port {port}":"{bin:next} dev -p {port}");return{...T,envFiles:c,dependsOn:Array.isArray(a)?[...a]:a,runtime:{...m||{},build:x,toolchain:k??m?.toolchain},local:{cwd:o,start:w,...g===void 0?{}:{port:g},baseUrl:y,readyUrl:h??y,readyTimeoutMs:S,...p===void 0?{}:{shutdownTimeoutMs:p},env:i==="start"?{NEXT_DIST_DIR:l.NEXT_DIST_DIR||".next-testkit/{runtimeId}/dist",NEXT_TSCONFIG_PATH:l.NEXT_TSCONFIG_PATH||".next-testkit/{runtimeId}/tsconfig.json",...l}:l}}}export const app={node:ne,next:te},database={schema:{fromEnv:q},steps:{materializePostgresBinding:H,runSql:X,seedRows:G,updateRows:L,verifyRows:V,verifySeed:M},values:{env:J,json:K,now:W,postgresConnectionFromEnv:Q},postgres(e={}){const{template:n,sourceSchema:t,inputs:r,migrate:s,seed:o,verify:a,...d}=e,c=t===void 0?d:{...d,sourceSchema:t},i=n||D(e)?R({inputs:r,migrate:s,seed:o,verify:a,...n||{}}):void 0;return F(i?{...c,template:i}:c)},fixture:Y},endpoint={baseUrl(e){return v(e,"baseUrl")},readyUrl(e){return v(e,"readyUrl")},publicBaseUrl(e){return v(e,"publicBaseUrl")},privateBaseUrl(e){return v(e,"privateBaseUrl")},serviceContextUrl(e){return v(e,"serviceContextUrl")}},step={command:z,module:u,sqlFile:_},presets={nodeNext(e={}){const n=e.workers;if(e.taskConcurrency!=null&&n!=null&&e.taskConcurrency!==n)throw new Error("presets.nodeNext taskConcurrency and workers must match when both are provided.");return{execution:{taskConcurrency:e.taskConcurrency??n??1,fileTimeoutSeconds:e.fileTimeoutSeconds??120},toolchains:{node:B({install:e.install??"download",...e.node?{node:e.node}:{},...e.npm?{npm:e.npm}:{}})}}}},toolchain={node:B};export{I as clearRepoConfig,O as getRepoConfig,$ as registerRepoConfig};function re(e,n){return e?.kind==="tsc"?`node {prepareDir}/${ie(n||e.entry||"src/index.ts",e.outDir||"dist")}`:`{bin:tsx} ${n||"src/index.ts"}`}function E(e){return e==null?[]:(Array.isArray(e)?e:[e]).map(t=>typeof t=="string"?z(t):t)}function ie(e,n){const r=P(e||"src/index.ts").replaceAll("\\","/").replace(/\.[cm]?[jt]sx?$/i,".js"),s=r.startsWith("src/")?r.slice(4):r;return`${n}/${s}`.replace(/\/+/g,"/")}
1
+ import{normalizeDatabaseEnvToken as S,normalizeNextPresetEnv as O,normalizePresetEnv as A}from"./preset-env.js";import{DATABASE_FIXTURE_DISCOVERY as j}from"./database-fixture-discovery.js";const _=String;export function defineConfig(e){return e||{}}function D(e={}){return{...e,...e.maxConnections?{maxConnections:e.maxConnections}:{},...e.reservedConnections?{reservedConnections:e.reservedConnections}:{},...e.runtimeConnections?{runtimeConnections:e.runtimeConnections}:{}}}function v(e,n="baseUrl"){const t=S(e,"service name",!1);return`{${S(n,"endpoint field",!1)}:${t}}`}function F(e={}){const n=E(e.migrate),t=E(e.seed),r=E(e.verify);return{inputs:Array.isArray(e.inputs)?[...e.inputs]:void 0,migrate:n,seed:t,verify:r}}function z(e,n={}){return{kind:"command",run:e,...k(n)}}function $(e,n={}){return{kind:"sql-file",path:e,...k(n)}}function u(e,n={}){return{kind:"module",target:e,...k(n)}}function k(e={}){return{...e.cwd!==void 0?{cwd:e.cwd}:{},...e.inputs!==void 0?{inputs:[...e.inputs]}:{},...e.args!==void 0?{args:e.args}:{}}}function N(e,n={}){if(typeof e!="string"||e.trim().length===0)throw new Error("database.schema.fromEnv(...) requires a non-empty env var name");return{kind:"env",env:e.trim(),...n}}function f(e){return`@gfdelarue/testkit/config/database-steps#${e}`}function P(e={}){return u(f("verifySeed"),{args:e})}function q(e={}){return u(f("verifyRows"),{args:e})}function V(e={}){return u(f("runSql"),{args:e})}function X(e={}){return u(f("seedRows"),{args:e})}function M(e={}){return u(f("materializePostgresBinding"),{args:e})}function G(e={}){return u(f("updateRows"),{args:e})}function H(){return{kind:"now"}}function L(e){return{kind:"env",name:S(e,"database.values.env(...) name",!1)}}function W(e){return{kind:"json",value:e}}function Y(e){return{kind:"postgres-connection-from-env",prefix:S(e,"database.values.postgresConnectionFromEnv(...) prefix",!1)}}function J(e={}){const{discovery:n,envFiles:t,template:r,inputs:s,migrate:o,seed:a,verify:d,...c}=e,i=r||R(e)?F({inputs:s,migrate:o,seed:a,verify:d,...r||{}}):void 0;return{discovery:n||{roots:[...j.roots]},envFiles:t,local:!1,database:D(i?{...c,template:i}:c)}}function R(e={}){return["inputs","migrate","seed","verify"].some(n=>Object.prototype.hasOwnProperty.call(e,n))}function B(e={}){return{...e}}function K(e={}){return{kind:"tsc",cwd:e.cwd,entry:e.entry||"src/index.ts",tsconfig:e.tsconfig||"tsconfig.json",outDir:e.outDir||"dist",inputs:Array.isArray(e.inputs)?[...e.inputs]:void 0}}function Q(e={}){return{kind:"next",cwd:e.cwd,distDir:e.distDir||"dist",tsconfig:e.tsconfig||"tsconfig.json",inputs:Array.isArray(e.inputs)?[...e.inputs]:void 0}}function Z(e={}){const{baseUrl:n,build:t,buildInputs:r,cwd:s=".",entry:o="src/index.ts",env:a,envFiles:d,outDir:c="dist",port:i,readyPath:g="/health",readyTimeoutMs:b,shutdownTimeoutMs:p,readyUrl:h,runtime:m,start:C,toolchain:T,tsconfig:U="tsconfig.json",...l}=e,y=A(a),x=n||"http://127.0.0.1:{port}",w=t===void 0?K({cwd:s,entry:o,tsconfig:U,outDir:c,inputs:r}):t,I=C||ne(w,o);return{...l,envFiles:d,runtime:{...m||{},build:w,toolchain:T??m?.toolchain},local:{cwd:s,start:I,...i===void 0?{}:{port:i},baseUrl:x,readyUrl:h??`${x}${g}`,readyTimeoutMs:b,...p===void 0?{}:{shutdownTimeoutMs:p},env:y}}}function ee(e={}){const{api:n,baseUrl:t,build:r,buildInputs:s,cwd:o=".",dependsOn:a,env:d,envFiles:c,mode:i="dev",port:g,readyTimeoutMs:b,shutdownTimeoutMs:p,readyUrl:h,runtime:m,start:C,toolchain:T,...U}=e,l={...O({api:n}),...A(d)},y=t||"http://127.0.0.1:{port}",x=r===void 0?i==="start"?Q({cwd:o,inputs:s}):null:r,w=C||(i==="start"?"{bin:next} start --port {port}":"{bin:next} dev -p {port}");return{...U,envFiles:c,dependsOn:Array.isArray(a)?[...a]:a,runtime:{...m||{},build:x,toolchain:T??m?.toolchain},local:{cwd:o,start:w,...g===void 0?{}:{port:g},baseUrl:y,readyUrl:h??y,readyTimeoutMs:b,...p===void 0?{}:{shutdownTimeoutMs:p},env:i==="start"?{NEXT_DIST_DIR:l.NEXT_DIST_DIR||".next-testkit/{runtimeId}/dist",NEXT_TSCONFIG_PATH:l.NEXT_TSCONFIG_PATH||".next-testkit/{runtimeId}/tsconfig.json",...l}:l}}}export const app={node:Z,next:ee},database={schema:{fromEnv:N},steps:{materializePostgresBinding:M,runSql:V,seedRows:X,updateRows:G,verifyRows:q,verifySeed:P},values:{env:L,json:W,now:H,postgresConnectionFromEnv:Y},postgres(e={}){const{template:n,sourceSchema:t,inputs:r,migrate:s,seed:o,verify:a,...d}=e,c=t===void 0?d:{...d,sourceSchema:t},i=n||R(e)?F({inputs:r,migrate:s,seed:o,verify:a,...n||{}}):void 0;return D(i?{...c,template:i}:c)},fixture:J},endpoint={baseUrl(e){return v(e,"baseUrl")},readyUrl(e){return v(e,"readyUrl")},publicBaseUrl(e){return v(e,"publicBaseUrl")},privateBaseUrl(e){return v(e,"privateBaseUrl")},serviceContextUrl(e){return v(e,"serviceContextUrl")}},step={command:z,module:u,sqlFile:$},presets={nodeNext(e={}){const n=e.workers;if(e.taskConcurrency!=null&&n!=null&&e.taskConcurrency!==n)throw new Error("presets.nodeNext taskConcurrency and workers must match when both are provided.");return{execution:{taskConcurrency:e.taskConcurrency??n??1,fileTimeoutSeconds:e.fileTimeoutSeconds??120},toolchains:{node:B({install:e.install??"download",...e.node?{node:e.node}:{},...e.npm?{npm:e.npm}:{}})}}}},toolchain={node:B};function ne(e,n){return e?.kind==="tsc"?`node {prepareDir}/${te(n||e.entry||"src/index.ts",e.outDir||"dist")}`:`{bin:tsx} ${n||"src/index.ts"}`}function E(e){return e==null?[]:(Array.isArray(e)?e:[e]).map(t=>typeof t=="string"?z(t):t)}function te(e,n){const r=_(e||"src/index.ts").replaceAll("\\","/").replace(/\.[cm]?[jt]sx?$/i,".js"),s=r.startsWith("src/")?r.slice(4):r;return`${n}/${s}`.replace(/\/+/g,"/")}
@@ -1 +1 @@
1
- import m from"node:crypto";import{isUnknownRecord as c}from"../../kernel/unknown-values.js";import o from"node:fs";import i from"node:path";import{fileURLToPath as d,pathToFileURL as p}from"node:url";import{buildWithEsbuildRetry as h}from"../../toolchains/esbuild.js";const u=i.resolve(i.dirname(d(import.meta.url)),"..","..",".."),k=i.join(u,"dist","index.js"),j=i.join(u,"dist","config","authoring","index.js"),w=new Set(["/environment","/node-vitest/data","/node-vitest/contract","/playwright"]),y=["testkit.config.ts","testkit.config.mts","testkit.config.mjs","testkit.config.js"];export function findConfigFile(t){for(const n of y){const e=i.join(t,n);if(o.existsSync(e))return e}return null}export async function loadTestkitConfig(t,n={}){const e=findConfigFile(t);if(!e)return{config:{},configFile:null};const r=i.join(t,".testkit","_config");o.mkdirSync(r,{recursive:!0});const s=b(e),a=i.join(r,`config-${s.slice(0,12)}.mjs`);await h({absWorkingDir:t,bundle:!0,entryPoints:[e],format:"esm",legalComments:"none",outfile:a,platform:"node",sourcemap:"inline",target:"es2020",plugins:[C()]});const l=[s,n.importCacheSalt||""].filter(Boolean).join("-"),g=await import(`${p(a).href}?v=${l}`),f=S(g);if(!c(f))throw new Error(`testkit config file must export an object: ${i.basename(e)}`);return{config:f,configFile:e}}function b(t){const n=o.readFileSync(t,"utf8");return m.createHash("sha256").update(t).update("\0").update(n).digest("hex")}function C(){return{name:"testkit-config-alias",setup(t){t.onResolve({filter:/^@gfdelarue\/testkit(?:\/.*)?$/},n=>({namespace:"file",path:E(n.path)}))}}}function E(t){const n=t.slice(18);if(!n)return k;if(n==="/config")return j;throw w.has(n)?new Error(`testkit.config.ts may not import engine authoring module "${t}". Use repository config helpers from @gfdelarue/testkit/config instead.`):new Error(`Unsupported @gfdelarue/testkit import "${t}" while loading testkit config`)}function S(t){if(c(t))return t.default||t.config||{}}
1
+ import g from"node:crypto";import{rejectUnknownKeys as p}from"../../kernel/known-keys.js";import{isUnknownRecord as c}from"../../kernel/unknown-values.js";import r from"node:fs";import i from"node:path";import{fileURLToPath as d,pathToFileURL as h}from"node:url";import{buildWithEsbuildRetry as k}from"../../toolchains/esbuild.js";import{migrationText as b}from"../../domain/retired-test-types.js";import{protectTestkitStateRoot as w}from"../../process/files/product-dir.js";const u=i.resolve(i.dirname(d(import.meta.url)),"..","..",".."),y=i.join(u,"dist","index.js"),j=i.join(u,"dist","config","authoring","index.js"),T=new Set(["/environment","/node-vitest/data","/playwright"]),E=["testkit.config.ts","testkit.config.mts","testkit.config.mjs","testkit.config.js"],C=Object.keys({areas:!0,artifacts:!0,bugs:!0,discovery:!0,execution:!0,fingerprints:!0,groups:!0,lint:!0,redact:!0,services:!0,toolchains:!0,ui:!0});export function findConfigFile(t){for(const e of E){const o=i.join(t,e);if(r.existsSync(o))return o}return null}export async function loadTestkitConfig(t,e={}){const o=findConfigFile(t);if(!o)return{config:{},configFile:null};w(t,!0);const s=i.join(t,".testkit","_config");r.mkdirSync(s,{recursive:!0});const a=v(o),f=i.join(s,`config-${a.slice(0,12)}.mjs`);await k({absWorkingDir:t,bundle:!0,entryPoints:[o],format:"esm",legalComments:"none",outfile:f,platform:"node",sourcemap:"inline",target:"es2020",plugins:[O()]});const l=[a,e.importCacheSalt||""].filter(Boolean).join("-"),m=await import(`${h(f).href}?v=${l}`),n=x(m);if(!c(n))throw new Error(`testkit config file must export an object: ${i.basename(o)}`);if(Object.hasOwn(n,"profiles"))throw new Error(`${i.basename(o)}: Testkit 0.6.0 removed HTTP profiles; log in from test code. ${b("Remove the data test type")}.`);return p(n,C,i.basename(o)),{config:n,configFile:o}}function v(t){const e=r.readFileSync(t,"utf8");return g.createHash("sha256").update(t).update("\0").update(e).digest("hex")}function O(){return{name:"testkit-config-alias",setup(t){t.onResolve({filter:/^@gfdelarue\/testkit(?:\/.*)?$/},e=>({namespace:"file",path:S(e.path)}))}}}function S(t){const e=t.slice(18);if(!e)return y;if(e==="/config")return j;throw T.has(e)?new Error(`testkit.config.ts may not import engine authoring module "${t}". Use repository config helpers from @gfdelarue/testkit/config instead.`):new Error(`Unsupported @gfdelarue/testkit import "${t}" while loading testkit config`)}function x(t){if(c(t))return t.default||t.config||{}}
@@ -1 +1 @@
1
- import s from"node:path";import{normalizeDiscoveryPath as n}from"./path-policy.js";export function normalizeDiscoveryConfig(r,{allowRoots:t=!0}={}){if(r==null)return{};if(typeof r!="object"||Array.isArray(r))throw new Error("testkit discovery config must be an object");const e={};return t&&Object.prototype.hasOwnProperty.call(r,"roots")&&(e.roots=i(Reflect.get(r,"roots"),"testkit discovery roots")),Object.prototype.hasOwnProperty.call(r,"exclude")&&(e.exclude=i(Reflect.get(r,"exclude"),"testkit discovery exclude")),e}function i(r,t){if(!Array.isArray(r))throw new Error(`${t} must be an array of relative paths`);const e=r.map(o=>{if(typeof o!="string"||o.trim().length===0)throw new Error(`${t} entries must be non-empty strings`);if(s.isAbsolute(o))throw new Error(`${t} entries must be relative paths`);return n(o)}).filter(Boolean);return[...new Set(e)]}
1
+ import s from"node:path";import{rejectUnknownKeys as n}from"../../kernel/known-keys.js";import{normalizeDiscoveryPath as c}from"./path-policy.js";const f=Object.keys({exclude:!0,roots:!0});export function normalizeDiscoveryConfig(t,{allowRoots:r=!0}={}){if(t==null)return{};if(typeof t!="object"||Array.isArray(t))throw new Error("testkit discovery config must be an object");n(t,f,"testkit discovery config");const e={};return r&&Object.prototype.hasOwnProperty.call(t,"roots")&&(e.roots=i(Reflect.get(t,"roots"),"testkit discovery roots")),Object.prototype.hasOwnProperty.call(t,"exclude")&&(e.exclude=i(Reflect.get(t,"exclude"),"testkit discovery exclude")),e}function i(t,r){if(!Array.isArray(t))throw new Error(`${r} must be an array of relative paths`);const e=t.map(o=>{if(typeof o!="string"||o.trim().length===0)throw new Error(`${r} entries must be non-empty strings`);if(s.isAbsolute(o))throw new Error(`${r} entries must be relative paths`);return c(o)}).filter(Boolean);return[...new Set(e)]}
@@ -1 +1 @@
1
- import m from"path";import{randomUUID as j}from"node:crypto";import{isUnknownRecord as D,readDynamicProperty as S}from"../../kernel/unknown-values.js";import{loadConfigContext as P,resolveProductDir as z}from"./load.js";import{hasDiscoveryErrors as B,buildDiscoveryErrorFromDiagnostics as V}from"./discovery.js";import{discoverOwnedTests as _}from"./owned-discovery.js";import{loadTestkitConfig as x}from"./config-loader.js";import{TEST_TYPE_ORDER as A,isRunType as L}from"../../domain/test-types.js";import"../../domain/identifiers.js";import{DEFAULT_RUNTIME_ISOLATION_POLICY as U,resolveRuntimeIsolationPolicy as q}from"./runtime-isolation.js";import{resolveExecutionProfile as Y}from"../../engines/catalog.js";import{readTestFileMetadata as G}from"./file-metadata.js";import{buildDiscoverySummary as K,emptyDiscoverySummary as H}from"./discovery-summary.js";import{matchesSelectedTypes as J,matchesSuiteSelectors as w,normalizeTypeValues as Q,parseSuiteSelectors as W,suiteKey as E}from"../../domain/suite-selection.js";import{errorMessage as b}from"../../kernel/error-serialization.js";const X=10,h=String;export async function discoverTests(e={}){return(await loadDiscoveryContext(e)).discovery}export async function loadDiscoveredConfigContext(e={}){const i=await P({...e,configImportCacheSalt:j()}),{discovery:o}=await loadDiscoveryContext({dir:i.productDir,diagnostics:"error"},i);return{context:i,discovery:o}}export async function loadDiscoveryContext(e={},i){const o=i?.productDir??z(process.cwd(),e.dir),n=se(e);if(i&&n.diagnosticsMode==="error"&&B(i.discovery.diagnostics))throw V(i.discovery.diagnostics);const r=i?{config:i.config,configFile:i.configFile,diagnostics:[]}:await Z(o,n.diagnosticsMode),a={schemaVersion:X,source:"testkit-discovery",product:{name:m.basename(o),directory:o},configFile:r.configFile?m.relative(o,r.configFile)||m.basename(r.configFile):null,filters:{group:n.groupFilter,types:n.typeValues,suiteSelectors:n.suiteSelectors.map(t=>t.raw),fileNames:n.fileNames,runnableOnly:n.runnableOnly,diagnostics:n.diagnosticsMode},groups:[],suites:[],files:[],diagnostics:[...r.diagnostics],summary:H()};if(!r.config)return{discovery:T(a),context:null,notice:null};const c=i?{groups:i.groups,notice:i.ownershipNotice,plainFiles:[],problems:[],project:i.discovery}:await _(o,r.config,{selection:e.vitest,strict:n.diagnosticsMode==="error"}),p=c.project,d={owned:c,fileMetadataByPath:new Map(i?.groups.flatMap(t=>[...t.testkit.fileMetadataByPath])??[])};a.diagnostics.push(...p.diagnostics),re(n.groupFilter,c.groups);let f=i??null;try{f=i??await P({dir:o,preparedDiscovery:d,configContext:{config:r.config,configFile:r.configFile},discoveryOptions:{strict:n.diagnosticsMode==="error"}})}catch(t){if(n.diagnosticsMode==="error")throw t;a.diagnostics.push({code:"config_invalid",severity:"error",message:b(t)})}const y=ie({productDir:o,rawDiscovery:p,declarations:c.groups,groups:f?.groups??[],fileMetadataByPath:d.fileMetadataByPath,filters:n});return{discovery:T({...a,...y}),context:f,notice:c.notice}}export function formatDisplayName(e){return h(e||"").split(/[-_/]+/g).filter(Boolean).map(i=>i.replace(/^\w/,o=>o.toUpperCase())).join(" ")}export function testTypeContract(e,i,o){return Y({engineId:i,profileId:o,testType:e}),e==="data"?{authoringApi:"@gfdelarue/testkit/node-vitest/data",summary:"Data suites use the node-vitest engine and record Testkit data evidence; they are not raw Vitest or service-namespace tests."}:e==="contract"?{authoringApi:"@gfdelarue/testkit/node-vitest/contract",summary:"Contract suites use the node-vitest engine for Node-backed service/runtime contracts without data-flow evidence requirements."}:i==="playwright"?{authoringApi:"@gfdelarue/testkit/playwright"}:{}}async function Z(e,i){try{const o=await x(e),{config:n,configFile:r}=ee(o);return{config:n,configFile:r,diagnostics:[]}}catch(o){if(i==="error")throw o;return{config:null,configFile:null,diagnostics:[{code:"config_invalid",severity:"error",message:b(o)}]}}}function ee(e){if(!D(e))throw new Error("testkit config loader must return an object");const i=S(e,"config"),o=S(e,"configFile");if(!D(i)){const n=typeof o=="string"?m.basename(o):"testkit config file";throw new Error(`testkit config file must export an object: ${n}`)}if(o!==null&&typeof o!="string")throw new Error("testkit config loader returned an invalid config path");return{config:i,configFile:o}}function ie({declarations:e,fileMetadataByPath:i,filters:o,groups:n,productDir:r,rawDiscovery:a}){const c=new Map(n.map(t=>[t.name,t])),p=new Map,d=new Map,f=[];for(const t of e)o.groupFilter&&t.name!==o.groupFilter||p.set(t.name,{name:t.name,roots:[...t.roots],uses:[...t.uses],source:t.source,suiteCount:0,fileCount:0,activeFileCount:0,skippedFileCount:0});for(const t of a.files){const s=t.testType;if(!J(s,o.typeValues)||!w(s,t.suiteName,o.suiteSelectors)||o.groupFilter&&t.serviceName!==o.groupFilter||o.fileNameSet.size>0&&!o.fileNameSet.has(t.filePath))continue;const u=i.get(t.filePath)??G(r,t.filePath);i.set(t.filePath,u);const l=c.get(t.serviceName),g=l?.suites.find(F=>F.testType===s&&F.name===t.suiteName);if(l&&(!g||g.engineId!==u.engineId||g.profileId!==u.profileId))throw new Error(`Normalized suite ${t.serviceName}/${E({name:t.suiteName,testType:s})} conflicts with file ${t.filePath}`);const I=l&&(u.skipReason||l.testkit.skip?.fileReasonByPath.get(t.filePath)||ce(l,s,t.suiteName))||null;if(o.runnableOnly&&I)continue;const O=l?[...new Set([...le(l,s,t.suiteName),...l.testkit.requirements.fileLocksByPath.get(t.filePath)||[],...u.locks])].sort():[],v=oe({groupName:t.serviceName,suiteName:t.suiteName,testType:s,filePath:t.filePath,engineId:u.engineId,profileId:u.profileId,skipReason:I,locks:O,runtimeIsolation:l&&g?q(l,g,t.filePath):U});f.push(v);const k=[t.serviceName,s??"",t.suiteName].join("|"),N=d.get(k);N?N.files.push(v):d.set(k,{groupName:t.serviceName,testType:s,suiteName:t.suiteName,files:[v]})}const y=[...d.values()].map(t=>te(t.groupName,t.testType,t.suiteName,t.files));for(const t of y){const s=p.get(t.group);if(s===void 0)throw new Error(`Discovered suite ${t.id} belongs to no declared group`);s.suiteCount+=1,s.fileCount+=t.fileCount,s.activeFileCount+=t.activeFileCount,s.skippedFileCount+=t.skippedFileCount}return{groups:[...p.values()].sort((t,s)=>t.name.localeCompare(s.name)),suites:y.sort(ue),files:f.sort(pe)}}function oe(e){const i=testTypeContract(e.testType,e.engineId,e.profileId);return{id:R(e.groupName,e.testType,e.engineId,e.profileId,M(e.filePath)),path:e.filePath,displayName:fileDisplayName(e.filePath),group:e.groupName,suiteName:e.suiteName,suiteLabel:formatDisplayName(e.suiteName),...e.testType===void 0?{}:{testType:e.testType},engineId:e.engineId,profileId:e.profileId,skipped:!!e.skipReason,skipReason:e.skipReason,locks:e.locks,runtimeIsolation:e.runtimeIsolation,contract:i}}function te(e,i,o,n){const r=ne(e,i,o,n),a=n.filter(c=>c.skipped).length;return{id:R(e,i,r.engineId,r.profileId,o),group:e,name:o,displayName:formatDisplayName(o),...i===void 0?{}:{testType:i},engineId:r.engineId,profileId:r.profileId,suiteLabel:formatDisplayName(o),fileCount:n.length,activeFileCount:n.length-a,skippedFileCount:a,locks:[...new Set(n.flatMap(c=>c.locks))].sort(),filePaths:n.map(c=>c.path),contract:r.contract}}function ne(e,i,o,n){const r=n[0];for(const a of n.slice(1))if(a.engineId!==r.engineId||a.profileId!==r.profileId)throw new Error(`Discovery suite ${e}/${E({name:o,testType:i})} mixes engine profiles ${r.engineId}/${r.profileId} and ${a.engineId}/${a.profileId}`);return{contract:r.contract,engineId:r.engineId,profileId:r.profileId}}function T(e){return{...e,diagnostics:e.diagnostics.sort(de),summary:K({diagnostics:e.diagnostics,files:e.files,groupCount:e.groups.length,suiteCount:e.suites.length})}}function re(e,i){if(!e||i.some(n=>n.name===e))return;const o=i.map(n=>n.name).sort((n,r)=>n.localeCompare(r)).join(", ");throw new Error(`Group "${e}" not found. Available: ${o}`)}function se(e){const i=ae(Q(C(e.type))),o=W(C(e.suite)),n=C(e.file).map(M);return{typeValues:i,suiteSelectors:o,fileNames:n,fileNameSet:new Set(n),groupFilter:e.group||null,runnableOnly:!!e.runnableOnly,diagnosticsMode:e.diagnostics==="error"?"error":"report"}}function ae(e){const i=[];for(const o of e){if(!L(o))throw new Error(`Discovery produced unsupported test type "${o}".`);i.push(o)}return i}function ce(e,i,o){for(const n of e.testkit.skip?.suites||[])if(w(i,o,[n.selector]))return n.reason;return null}function le(e,i,o){const n=new Set;for(const r of e.testkit.requirements.suites)if(w(i,o,[r.selector]))for(const a of r.locks)n.add(a);return[...n].sort()}function C(e){return Array.isArray(e)?e.filter(o=>o!=null).map(o=>h(o)):e==null||e===""?[]:[h(e)]}function M(e){return h(e).split(m.sep).join("/").replace(/^\.\/+/,"")}export function fileDisplayName(e){const i=new RegExp(`\\.(${A.join("|")})\\.testkit\\.ts$`),o=m.posix.basename(e).replace(i,"");return formatDisplayName(o)}function R(e,i,o,n,r){return[e,...i===void 0?[]:[i],o,n,r].join("|")}function ue(e,i){return e.group.localeCompare(i.group)||(e.testType??"").localeCompare(i.testType??"")||e.name.localeCompare(i.name)}function pe(e,i){return e.group.localeCompare(i.group)||(e.testType??"").localeCompare(i.testType??"")||e.suiteName.localeCompare(i.suiteName)||e.path.localeCompare(i.path)}function de(e,i){return $(e.severity)-$(i.severity)||e.code.localeCompare(i.code)||(e.path||"").localeCompare(i.path||"")}function $(e){return e==="error"?1:2}
1
+ import v from"path";import{randomUUID as w}from"node:crypto";import{isUnknownRecord as l,readDynamicProperty as g}from"../../kernel/unknown-values.js";import{loadConfigContext as u,resolveProductDir as h}from"./load.js";import{hasDiscoveryErrors as D,buildDiscoveryErrorFromDiagnostics as k}from"./discovery.js";import{discoverOwnedTests as C}from"./owned-discovery.js";import{loadTestkitConfig as M}from"./config-loader.js";import{createDiscoveryResult as P,finalizeDiscoveryResult as p,normalizeDiscoveryFilters as F,selectDiscoveredTests as B,validateRequestedGroup as j}from"./discovery-result.js";import{errorMessage as m}from"../../kernel/error-serialization.js";export async function discoverTests(i={}){return(await loadDiscoveryContext(i)).discovery}export async function loadDiscoveredConfigContext(i={}){const{group:o,...e}=i,r=await u({...e,configImportCacheSalt:w()}),{discovery:t}=await loadDiscoveryContext({dir:r.productDir,diagnostics:"error",group:o},r);return{context:r,discovery:t}}export async function loadDiscoveryContext(i={},o){const e=o?.productDir??h(process.cwd(),i.dir),r=F(i);if(o&&r.diagnosticsMode==="error"&&D(o.discovery.diagnostics))throw k(o.discovery.diagnostics);const t=o?{config:o.config,configFile:o.configFile,diagnostics:[]}:await R(e,r.diagnosticsMode),s=P({productDir:e,configFile:t.configFile,filters:r,diagnostics:t.diagnostics});if(!t.config)return{discovery:p(s),context:null};const n=o?{groups:o.groups,migration:null,plainFiles:[],problems:[],project:o.discovery}:await C(e,t.config,{selection:i.vitest,strict:r.diagnosticsMode==="error"}),f=n.project,d={owned:n,fileMetadataByPath:new Map(o?.groups.flatMap(c=>[...c.testkit.fileMetadataByPath])??[])};s.diagnostics.push(...f.diagnostics),j(r.groupFilter,n.groups);let a=o??null;try{a=o??await u({dir:e,preparedDiscovery:d,configContext:{config:t.config,configFile:t.configFile},discoveryOptions:{strict:r.diagnosticsMode==="error"}})}catch(c){if(r.diagnosticsMode==="error")throw c;s.diagnostics.push({code:"config_invalid",severity:"error",message:m(c)})}const y=B({productDir:e,rawDiscovery:f,declarations:n.groups,policies:(a?.groups??[]).map(I),fileMetadataByPath:d.fileMetadataByPath,filters:r});return{discovery:p({...s,...y}),context:a}}async function R(i,o){try{const e=await M(i),{config:r,configFile:t}=b(e);return{config:r,configFile:t,diagnostics:[]}}catch(e){if(o==="error")throw e;return{config:null,configFile:null,diagnostics:[{code:"config_invalid",severity:"error",message:m(e)}]}}}function b(i){if(!l(i))throw new Error("testkit config loader must return an object");const o=g(i,"config"),e=g(i,"configFile");if(!l(o)){const r=typeof e=="string"?v.basename(e):"testkit config file";throw new Error(`testkit config file must export an object: ${r}`)}if(e!==null&&typeof e!="string")throw new Error("testkit config loader returned an invalid config path");return{config:o,configFile:e}}export function projectGroupTestPolicy(i){return{fileLocks:i.requirements.fileLocksByPath,fileMetadata:i.fileMetadataByPath,fileSkipReasons:i.skip?.fileReasonByPath??new Map,runtimeIsolation:{defaultPolicy:i.runtimeIsolation.defaultPolicy,files:i.runtimeIsolation.filePolicyByPath,suites:i.runtimeIsolation.suites},suiteLocks:i.requirements.suites,suiteSkips:i.skip?.suites??[]}}function I(i){return{...projectGroupTestPolicy(i.testkit),name:i.name,suites:i.suites}}
@@ -0,0 +1 @@
1
+ import m from"path";import{TEST_TYPE_ORDER as F,isRunType as b}from"../../domain/test-types.js";import{DEFAULT_RUNTIME_ISOLATION_POLICY as $}from"../../domain/runtime-isolation-contract.js";import{normalizeTypeValues as D,parseSuiteSelectors as x,suiteKey as w}from"../../domain/suite-selection.js";import{resolveExecutionProfile as O}from"../../engines/catalog.js";import{buildDiscoverySummary as M,emptyDiscoverySummary as z}from"./discovery-summary.js";import{readTestFileMetadata as V}from"./file-metadata.js";import{findSuiteLocks as _,findSuiteSkipReason as A,matchesSelectedTypes as L,matchesSuiteSelectors as j,resolveRuntimeIsolation as B}from"./test-selection.js";const U=10,d=String;export function createDiscoveryResult({configFile:e,diagnostics:o,filters:t,productDir:i}){return{schemaVersion:U,source:"testkit-discovery",product:{name:m.basename(i),directory:i},configFile:e?m.relative(i,e)||m.basename(e):null,filters:{group:t.groupFilter,types:t.typeValues,suiteSelectors:t.suiteSelectors.map(r=>r.raw),fileNames:t.fileNames,runnableOnly:t.runnableOnly,diagnostics:t.diagnosticsMode},groups:[],suites:[],files:[],diagnostics:[...o],summary:z()}}export function formatDisplayName(e){return d(e||"").split(/[-_/]+/g).filter(Boolean).map(o=>o.replace(/^\w/,t=>t.toUpperCase())).join(" ")}export function testTypeContract(e,o,t){return O({engineId:o,profileId:t,testType:e}),o==="playwright"?{authoringApi:"@gfdelarue/testkit/playwright"}:{}}export function selectDiscoveredTests({declarations:e,fileMetadataByPath:o,filters:t,policies:i,productDir:r,rawDiscovery:u}){const l=new Map(i.map(n=>[n.name,n])),f=new Map,g=new Map,N=[];for(const n of e)t.groupFilter&&n.name!==t.groupFilter||f.set(n.name,{name:n.name,roots:[...n.roots],uses:[...n.uses],source:n.source,suiteCount:0,fileCount:0,activeFileCount:0,skippedFileCount:0});for(const n of u.files){const s=n.testType;if(!L(s,t.typeValues)||!j(s,n.suiteName,t.suiteSelectors)||t.groupFilter&&n.serviceName!==t.groupFilter||t.fileNameSet.size>0&&!t.fileNameSet.has(n.filePath))continue;const p=o.get(n.filePath)??V(r,n.filePath);o.set(n.filePath,p);const a=l.get(n.serviceName),c=a?.suites.find(k=>k.testType===s&&k.name===n.suiteName);if(a&&(!c||c.engineId!==p.engineId||c.profileId!==p.profileId))throw new Error(`Normalized suite ${n.serviceName}/${w({name:n.suiteName,testType:s})} conflicts with file ${n.filePath}`);const C=a&&(p.skipReason||a.fileSkipReasons.get(n.filePath)||A(a,s,n.suiteName))||null;if(t.runnableOnly&&C)continue;const P=a?[...new Set([..._(a,s,n.suiteName),...a.fileLocks.get(n.filePath)||[],...p.locks])].sort():[],y=Y({groupName:n.serviceName,suiteName:n.suiteName,testType:s,filePath:n.filePath,engineId:p.engineId,profileId:p.profileId,skipReason:C,locks:P,runtimeIsolation:a&&c?B(a,c,n.filePath):$});N.push(y);const S=[n.serviceName,s??"",n.suiteName].join("|"),v=g.get(S);v?v.files.push(y):g.set(S,{groupName:n.serviceName,testType:s,suiteName:n.suiteName,files:[y]})}const h=[...g.values()].map(n=>q(n.groupName,n.testType,n.suiteName,n.files));for(const n of h){const s=f.get(n.group);if(s===void 0)throw new Error(`Discovered suite ${n.id} belongs to no declared group`);s.suiteCount+=1,s.fileCount+=n.fileCount,s.activeFileCount+=n.activeFileCount,s.skippedFileCount+=n.skippedFileCount}return{groups:[...f.values()].sort((n,s)=>n.name.localeCompare(s.name)),suites:h.sort(H),files:N.sort(J)}}function Y(e){const o=testTypeContract(e.testType,e.engineId,e.profileId);return{id:E(e.groupName,e.testType,e.engineId,e.profileId,T(e.filePath)),path:e.filePath,displayName:fileDisplayName(e.filePath),group:e.groupName,suiteName:e.suiteName,suiteLabel:formatDisplayName(e.suiteName),...e.testType===void 0?{}:{testType:e.testType},engineId:e.engineId,profileId:e.profileId,skipped:!!e.skipReason,skipReason:e.skipReason,locks:e.locks,runtimeIsolation:e.runtimeIsolation,contract:o}}function q(e,o,t,i){const r=G(e,o,t,i),u=i.filter(l=>l.skipped).length;return{id:E(e,o,r.engineId,r.profileId,t),group:e,name:t,displayName:formatDisplayName(t),...o===void 0?{}:{testType:o},engineId:r.engineId,profileId:r.profileId,suiteLabel:formatDisplayName(t),fileCount:i.length,activeFileCount:i.length-u,skippedFileCount:u,locks:[...new Set(i.flatMap(l=>l.locks))].sort(),filePaths:i.map(l=>l.path),contract:r.contract}}function G(e,o,t,i){const r=i[0];for(const u of i.slice(1))if(u.engineId!==r.engineId||u.profileId!==r.profileId)throw new Error(`Discovery suite ${e}/${w({name:t,testType:o})} mixes engine profiles ${r.engineId}/${r.profileId} and ${u.engineId}/${u.profileId}`);return{contract:r.contract,engineId:r.engineId,profileId:r.profileId}}export function finalizeDiscoveryResult(e){return{...e,diagnostics:e.diagnostics.sort(Q),summary:M({diagnostics:e.diagnostics,files:e.files,groupCount:e.groups.length,suiteCount:e.suites.length})}}export function validateRequestedGroup(e,o){if(!e||o.some(i=>i.name===e))return;const t=o.map(i=>i.name).sort((i,r)=>i.localeCompare(r)).join(", ");throw new Error(`Group "${e}" not found. Available: ${t}`)}export function normalizeDiscoveryFilters(e){const o=K(D(I(e.type))),t=x(I(e.suite)),i=I(e.file).map(T);return{typeValues:o,suiteSelectors:t,fileNames:i,fileNameSet:new Set(i),groupFilter:e.group||null,runnableOnly:!!e.runnableOnly,diagnosticsMode:e.diagnostics==="error"?"error":"report"}}function K(e){const o=[];for(const t of e){if(!b(t))throw new Error(`Discovery produced unsupported test type "${t}".`);o.push(t)}return o}function I(e){return Array.isArray(e)?e.filter(t=>t!=null).map(t=>d(t)):e==null||e===""?[]:[d(e)]}function T(e){return d(e).split(m.sep).join("/").replace(/^\.\/+/,"")}export function fileDisplayName(e){const o=new RegExp(`\\.(${F.join("|")})\\.testkit\\.ts$`),t=m.posix.basename(e).replace(o,"");return formatDisplayName(t)}function E(e,o,t,i,r){return[e,...o===void 0?[]:[o],t,i,r].join("|")}function H(e,o){return e.group.localeCompare(o.group)||(e.testType??"").localeCompare(o.testType??"")||e.name.localeCompare(o.name)}function J(e,o){return e.group.localeCompare(o.group)||(e.testType??"").localeCompare(o.testType??"")||e.suiteName.localeCompare(o.suiteName)||e.path.localeCompare(o.path)}function Q(e,o){return R(e.severity)-R(o.severity)||e.code.localeCompare(o.code)||(e.path||"").localeCompare(o.path||"")}function R(e){return e==="error"?1:2}
@@ -1,2 +1,2 @@
1
- import k from"fs";import y from"path";import{recordWork as v}from"../../kernel/work-metrics.js";import{DEFAULT_DISCOVERY_EXCLUDES as F,normalizeDiscoveryPath as C,resolveDiscoveryRoots as D,shouldExcludeDiscoveryPath as E}from"./path-policy.js";const P=String,w="__testkit__",N=/\.(?:test|spec)\.[cm]?[jt]sx?$/u,$=[{suffix:".contract.testkit.ts",testType:"contract"},{suffix:".data.testkit.ts",testType:"data"},{suffix:".ui.testkit.ts",testType:"ui"}];export function scanTestFiles(o,e={}){const i=[],n=[],t=[],u=[...new Set([...F,...(e.exclude||[]).map(b)])],l=D(o,e.roots,".");for(;l.length>0;){const c=l.pop();if(c!==void 0){v("discovery.directoriesRead");for(const s of k.readdirSync(c,{withFileTypes:!0})){v("discovery.entriesVisited");const r=y.join(c,s.name);if(s.isSymbolicLink())continue;if(s.isDirectory()){const p=C(y.relative(o,r));if(E(p,u))continue;l.push(r);continue}if(!s.isFile())continue;const a=b(y.relative(o,r));if(!x(a)){a.endsWith(".testkit.ts")&&t.push(a);continue}a.split("/").includes(w)?i.push(a):n.push(a)}}}return{suiteFiles:i.sort((c,s)=>c.localeCompare(s)),legacyFiles:n.sort((c,s)=>c.localeCompare(s)),untypedFiles:t.sort((c,s)=>c.localeCompare(s))}}export function buildDiscoveryProject(o,e,i={}){const n=i.strict!==!1,t=I(o),u=new Map;for(const s of[...e].sort((r,a)=>r.filePath.localeCompare(a.filePath))){const r=s.filePath;if(s.kind==="ambiguous"){const g=[...s.owners].sort((S,T)=>S.localeCompare(T));t.push({code:"ambiguous_test",severity:"error",message:`Ambiguous test file: ${r} -> ${g.join(", ")}`,path:r,groupNames:g});continue}const a=x(r),p=`${s.owner}|${a?.testType??""}`,m=u.get(p)??new Map;u.set(p,m);const f=L(s.relativePath,a),d=f.join("/"),h=a===null?{}:{testType:a.testType},_=m.get(d)??{_suiteKey:d,_suitePath:f,serviceName:s.owner,suite:{files:[],name:"",...h},type:h};m.set(d,_),_.suite.files.push(r)}if(n&&hasDiscoveryErrors(t))throw buildDiscoveryErrorFromDiagnostics(t);const l=[],c={};for(const s of u.values()){const r=j([...s.values()]);for(const{_suiteKey:a,_suitePath:p,serviceName:m,suite:f,type:d}of s.values()){f.name=K(r,a),f.files.sort((h,_)=>h.localeCompare(_));for(const h of f.files)l.push({...d,filePath:h,serviceName:m,suiteName:f.name,suitePath:[...p]});(c[m]??=[]).push(f)}}for(const s of Object.values(c))s?.sort((r,a)=>(r.testType??"").localeCompare(a.testType??"")||r.name.localeCompare(a.name));return{suitesByService:c,files:l.sort((s,r)=>s.serviceName.localeCompare(r.serviceName)||(s.testType??"").localeCompare(r.testType??"")||s.suiteName.localeCompare(r.suiteName)||s.filePath.localeCompare(r.filePath)),diagnostics:t}}export function hasDiscoveryErrors(o=[]){return o.some(e=>e?.severity==="error")}export function buildDiscoveryErrorFromDiagnostics(o=[]){const e=["Filesystem discovery failed for one or more .testkit.ts files."],i=o.filter(t=>t.code==="legacy_path").map(t=>t.path),n=o.filter(t=>t.code==="ambiguous_test");if(i.length>0){e.push(""),e.push("Legacy test files outside __testkit__:");for(const t of i)e.push(`- ${t}`)}if(n.length>0){e.push(""),e.push("Ambiguous test files:");for(const t of n)e.push(`- ${t.path} -> ${t.groupNames.join(", ")}`)}return e.push(""),e.push('Expected test files to live under a "__testkit__" directory within a group root.'),new Error(e.join(`
2
- `))}function j(o){const e=new Map,i=new Map;for(const n of o){const t=n._suitePath;let u="";for(let l=1;l<=t.length;l+=1){u=t.slice(-l).join("-");const c=e.get(u);if(!c){e.set(u,n._suiteKey);break}if(c===n._suiteKey)break;u=""}u||(u=t.join("-")),i.set(n._suiteKey,u)}if(new Set(i.values()).size!==o.length){const n=new Set;for(const t of o){const u=t._suitePath.join("-");let l=u;for(let c=2;n.has(l);c+=1)l=`${u}-${P(c)}`;n.add(l),i.set(t._suiteKey,l)}}return i}function K(o,e){const i=o.get(e);if(i===void 0)throw new Error(`Discovery suite name was not resolved for "${e}"`);return i}function L(o,e){const i=o.split("/").slice(0,-1);if(e===null)return i.length>0?i:[y.posix.basename(o).replace(N,"")];const n=i.indexOf(w),t=n===-1?[]:[...i.slice(0,n),...i.slice(n+1,n+2)];return t.length>0?t:[y.posix.basename(o,e.suffix)]}function x(o){return $.find(e=>o.endsWith(e.suffix))||null}function b(o){const e=P(o).split(y.sep).join("/");return e==="."||e==="./"?".":e.replace(/^\.\/+/,"").replace(/\/+$/,"")||"."}function I(o){return o.map(e=>({code:"legacy_path",severity:"error",message:`Legacy test file outside __testkit__: ${e}`,path:e}))}
1
+ import T from"fs";import y from"path";import{recordWork as v}from"../../kernel/work-metrics.js";import{DEFAULT_DISCOVERY_EXCLUDES as k,normalizeDiscoveryPath as C,resolveDiscoveryRoots as D,shouldExcludeDiscoveryPath as E}from"./path-policy.js";const P=String,w="__testkit__";export const PLAIN_TEST_SUFFIX=/\.(?:test|spec)\.[cm]?[jt]sx?$/u;const N=[{suffix:".ui.testkit.ts",testType:"ui"}];export function scanTestFiles(o,e={}){const i=[],n=[],t=[],u=[...new Set([...k,...(e.exclude||[]).map(x)])],l=D(o,e.roots,".");for(;l.length>0;){const c=l.pop();if(c!==void 0){v("discovery.directoriesRead");for(const s of T.readdirSync(c,{withFileTypes:!0})){v("discovery.entriesVisited");const r=y.join(c,s.name);if(s.isSymbolicLink())continue;if(s.isDirectory()){const p=C(y.relative(o,r));if(E(p,u))continue;l.push(r);continue}if(!s.isFile())continue;const a=x(y.relative(o,r));if(!b(a)){a.endsWith(".testkit.ts")&&t.push(a);continue}a.split("/").includes(w)?i.push(a):n.push(a)}}}return{suiteFiles:i.sort((c,s)=>c.localeCompare(s)),legacyFiles:n.sort((c,s)=>c.localeCompare(s)),untypedFiles:t.sort((c,s)=>c.localeCompare(s))}}export function buildDiscoveryProject(o,e,i={}){const n=i.strict!==!1,t=L(o),u=new Map;for(const s of[...e].sort((r,a)=>r.filePath.localeCompare(a.filePath))){const r=s.filePath;if(s.kind==="ambiguous"){const g=[...s.owners].sort((S,F)=>S.localeCompare(F));t.push({code:"ambiguous_test",severity:"error",message:`Ambiguous test file: ${r} -> ${g.join(", ")}`,path:r,groupNames:g});continue}const a=b(r),p=`${s.owner}|${a?.testType??""}`,m=u.get(p)??new Map;u.set(p,m);const f=K(s.relativePath,a),d=f.join("/"),h=a===null?{}:{testType:a.testType},_=m.get(d)??{_suiteKey:d,_suitePath:f,serviceName:s.owner,suite:{files:[],name:"",...h},type:h};m.set(d,_),_.suite.files.push(r)}if(n&&hasDiscoveryErrors(t))throw buildDiscoveryErrorFromDiagnostics(t);const l=[],c={};for(const s of u.values()){const r=$([...s.values()]);for(const{_suiteKey:a,_suitePath:p,serviceName:m,suite:f,type:d}of s.values()){f.name=j(r,a),f.files.sort((h,_)=>h.localeCompare(_));for(const h of f.files)l.push({...d,filePath:h,serviceName:m,suiteName:f.name,suitePath:[...p]});(c[m]??=[]).push(f)}}for(const s of Object.values(c))s?.sort((r,a)=>(r.testType??"").localeCompare(a.testType??"")||r.name.localeCompare(a.name));return{suitesByService:c,files:l.sort((s,r)=>s.serviceName.localeCompare(r.serviceName)||(s.testType??"").localeCompare(r.testType??"")||s.suiteName.localeCompare(r.suiteName)||s.filePath.localeCompare(r.filePath)),diagnostics:t}}export function hasDiscoveryErrors(o=[]){return o.some(e=>e?.severity==="error")}export function buildDiscoveryErrorFromDiagnostics(o=[]){const e=["Filesystem discovery failed for one or more .testkit.ts files."],i=o.filter(t=>t.code==="legacy_path").map(t=>t.path),n=o.filter(t=>t.code==="ambiguous_test");if(i.length>0){e.push(""),e.push("Legacy test files outside __testkit__:");for(const t of i)e.push(`- ${t}`)}if(n.length>0){e.push(""),e.push("Ambiguous test files:");for(const t of n)e.push(`- ${t.path} -> ${t.groupNames.join(", ")}`)}return e.push(""),e.push('Expected test files to live under a "__testkit__" directory within a group root.'),new Error(e.join(`
2
+ `))}function $(o){const e=new Map,i=new Map;for(const n of o){const t=n._suitePath;let u="";for(let l=1;l<=t.length;l+=1){u=t.slice(-l).join("-");const c=e.get(u);if(!c){e.set(u,n._suiteKey);break}if(c===n._suiteKey)break;u=""}u||(u=t.join("-")),i.set(n._suiteKey,u)}if(new Set(i.values()).size!==o.length){const n=new Set;for(const t of o){const u=t._suitePath.join("-");let l=u;for(let c=2;n.has(l);c+=1)l=`${u}-${P(c)}`;n.add(l),i.set(t._suiteKey,l)}}return i}function j(o,e){const i=o.get(e);if(i===void 0)throw new Error(`Discovery suite name was not resolved for "${e}"`);return i}function K(o,e){const i=o.split("/").slice(0,-1);if(e===null)return i.length>0?i:[y.posix.basename(o).replace(PLAIN_TEST_SUFFIX,"")];const n=i.indexOf(w),t=n===-1?[]:[...i.slice(0,n),...i.slice(n+1,n+2)];return t.length>0?t:[y.posix.basename(o,e.suffix)]}function b(o){return N.find(e=>o.endsWith(e.suffix))||null}function x(o){const e=P(o).split(y.sep).join("/");return e==="."||e==="./"?".":e.replace(/^\.\/+/,"").replace(/\/+$/,"")||"."}function L(o){return o.map(e=>({code:"legacy_path",severity:"error",message:`Legacy test file outside __testkit__: ${e}`,path:e}))}
@@ -1 +1 @@
1
- import{isWorkObserved as $,recordWork as L}from"../../kernel/work-metrics.js";import{retiredEngineMessage as A}from"../../domain/retired-test-types.js";import{isPathWithin as h}from"../../process/files/paths.js";import g from"fs";import S from"path";import{createRequire as B}from"node:module";import{fileURLToPath as O}from"node:url";import{ENGINE_IDS as j,PUBLIC_TEST_TYPES as K,isEngineId as V}from"../../domain/identifiers.js";import{normalizeRuntimeIsolationPolicy as W}from"../../domain/runtime-isolation-contract.js";import{resolveEngineProfile as _,allowsEngineAuthoringImport as z,expectedEngineAuthoringModule as U,invalidEngineAuthoringImport as G,requireMatchingEngineImport as J}from"../../engines/catalog.js";const Y=B(import.meta.url),H=O(import.meta.resolve("typescript"));let Q;function a(){return Q??=Y(H)}const I="@gfdelarue/testkit";export function loadTestFileMetadataMap(e,t={},i=new Map){for(const r of Object.values(t||{}).flatMap(s=>s??[]))for(const s of r.files||[])i.has(s)||i.set(s,readTestFileMetadata(e,s));return i}export function readTestFileMetadata(e,t){const i=K.find(f=>t.endsWith(`.${f}.testkit.ts`));if(i===void 0)return{engineId:"node-vitest",locks:[],profileId:"node-vitest",runtimeIsolation:null,skipReason:null};const r=a(),s=ke(e,t);let n;if($()){const f=g.readFileSync(s);L("metadata.filesRead",1),L("metadata.bytesRead",f.byteLength),n=f.toString("utf8")}else n=g.readFileSync(s,"utf8");const o=r.createSourceFile(s,n,r.ScriptTarget.Latest,!0,r.ScriptKind.TS),l=Ee(o);if(l.length>0){const f=l.map(d=>r.flattenDiagnosticMessageText(d.messageText," ")).join("; ");throw c(t,`TypeScript syntax is invalid: ${f}`)}let p=null,u=null;for(const f of o.statements)if(!(!r.isVariableStatement(f)||!re(f))){for(const d of f.declarationList.declarations)if(!(!r.isIdentifier(d.name)||d.name.text!=="testkit")){if(p)throw c(t,'exported "testkit" metadata must be declared exactly once');if(!ne(f))throw c(t,'exported "testkit" metadata must be const');p=d,u=f}}if(!p||!u)throw c(t,'export const testkit = defineFile({ engine: "..." }) is required');oe(o,u,t);const m=X(p.initializer,t,i);return requireMatchingEngineAuthoringImports(o,t,m),m}function X(e,t,i){const r=a();if(!e)throw c(t,'exported "testkit" metadata has no value');if(!r.isCallExpression(e))throw c(t,"testkit metadata must use defineFile({ ... })");if(ie(e.expression)!=="defineFile")throw c(t,"testkit metadata must call defineFile");if(e.arguments.length!==1)throw c(t,"defineFile metadata must receive exactly one object literal");const s=e.arguments[0];if(!r.isObjectLiteralExpression(s))throw c(t,"defineFile metadata must receive an object literal");return Z(s,t,i)}function Z(e,t,i){const r=a(),s={locks:[],runtimeIsolation:null,skipReason:null};let n=null;const o=new Set;for(const u of e.properties){if(r.isSpreadAssignment(u))throw c(t,"testkit metadata may not contain spread properties");if(!r.isPropertyAssignment(u))throw c(t,"testkit metadata properties must use key: value assignments");const m=b(u.name,t,"testkit metadata");if(!se(m))throw c(t,`unknown metadata property "${m}"`);if(o.has(m))throw c(t,`duplicate metadata property "${m}"`);if(o.add(m),m==="engine"){n=k(u.initializer,t,'metadata property "engine"');continue}if(m==="locks"){s.locks=P(u.initializer,t);continue}if(m==="skip"){s.skipReason=ee(u.initializer,t);continue}s.runtimeIsolation=te(u.initializer,t)}if(!n)throw c(t,'a literal "engine" property is required');const l=A(n);if(l!==null)throw c(t,l);if(!V(n))throw c(t,`unsupported engine "${n}"; expected one of: ${j.join(", ")}`);let p;try{p=_(i,n)}catch(u){throw c(t,R(u),u)}return{engineId:n,locks:s.locks,profileId:p.id,runtimeIsolation:s.runtimeIsolation,skipReason:s.skipReason}}function P(e,t){const i=a();if(!i.isArrayLiteralExpression(e))throw c(t,'metadata property "locks" must be an array');const r=[];for(const s of e.elements){if(i.isSpreadElement(s)||i.isOmittedExpression(s))throw c(t,'metadata property "locks" must contain only string literals');r.push(D(s,t,'metadata property "locks" entry'))}return[...new Set(r)].sort()}function ee(e,t){const i=a(),r=x(e);if(r!==null)return F(r,t,'metadata property "skip"').trim();if(!i.isObjectLiteralExpression(e))throw c(t,'metadata property "skip" must be a string literal or { reason: string literal }');if(e.properties.length!==1)throw c(t,'metadata property "skip" object must contain exactly one reason property');const s=e.properties[0];if(!i.isPropertyAssignment(s))throw c(t,'metadata property "skip" reason must use a string-literal assignment');if(b(s.name,t,'metadata property "skip"')!=="reason")throw c(t,'metadata property "skip" object only accepts "reason"');return D(s.initializer,t,'metadata property "skip.reason"')}function te(e,t){const i=k(e,t,'metadata property "runtimeIsolation"');try{return W(i,"defineFile runtimeIsolation")}catch(r){throw c(t,R(r),r)}}function ie(e){return a().isIdentifier(e)?e.text:null}function b(e,t,i){const r=a();if(r.isIdentifier(e)||r.isStringLiteral(e))return e.text;throw c(t,`${i} property names must be plain identifiers or string literals`)}function x(e){const t=a();return t.isStringLiteral(e)||t.isNoSubstitutionTemplateLiteral(e)?e.text:null}function k(e,t,i){const r=x(e);if(r===null)throw c(t,`${i} must be a string literal`);return F(r,t,i)}function D(e,t,i){return k(e,t,i).trim()}function F(e,t,i){if(e.trim().length===0)throw c(t,`${i} must not be empty`);return e}function re(e){const t=a();return(e.modifiers||[]).some(i=>i.kind===t.SyntaxKind.ExportKeyword)}function ne(e){const t=a();return(e.declarationList.flags&t.NodeFlags.Const)!==0}function se(e){return["engine","locks","runtimeIsolation","skip"].includes(e)}function oe(e,t,i){const r=a();let s=null,n=0;for(const o of e.statements){if(r.isImportDeclaration(o)){const l=o.importClause;if(!l||l.phaseModifier===r.SyntaxKind.TypeKeyword)continue;l.name?.text==="defineFile"&&(n+=1);const p=l.namedBindings;if(p&&r.isNamespaceImport(p)){p.name.text==="defineFile"&&(n+=1);continue}if(!p||!r.isNamedImports(p))continue;for(const u of p.elements){if(u.isTypeOnly||u.name.text!=="defineFile")continue;if(n+=1,(u.propertyName?.text??u.name.text)==="defineFile"&&r.isStringLiteral(o.moduleSpecifier)&&o.moduleSpecifier.text===I){if(s)throw c(i,`defineFile must have exactly one named value import from "${I}"`);s=o}}continue}n+=Ie(o)}if(!s)throw c(i,`defineFile must be a named value import from "${I}"`);if(n!==1)throw c(i,"the imported defineFile binding may not be shadowed or redeclared");if(s.getStart(e)>t.getStart(e))throw c(i,"the defineFile import must precede metadata")}export function requireMatchingEngineAuthoringImports(e,t,i){ae(e,t,i);const r=U(i.profileId);for(const s of collectModuleSpecifiers(e))if(J(t,i,s),!z(r,s))throw G(t,i,s,r)}function ae(e,t,i){const r=a(),s=q(e),n=o=>{if(ce(o,t,i),le(o,s,t,i),r.isCallExpression(o)&&T(o,s)){const l=o.arguments.at(0);if(!l||!r.isStringLiteralLike(l)){const p=o.expression.kind===r.SyntaxKind.ImportKeyword?"import()":"require()";throw new Error(`Invalid Testkit authoring in "${t}": ${i.engineId}/${i.profileId} may not use non-literal ${p}; module specifiers must be string literals so engine boundaries can be verified`)}}for(const l of C(o))n(l);r.forEachChild(o,n)};n(e)}function T(e,t){const i=a();return e.expression.kind===i.SyntaxKind.ImportKeyword||i.isIdentifier(e.expression)&&e.expression.text==="require"&&!E(e.expression,t)}function ce(e,t,i){const r=a();if(r.isImportDeclaration(e)){const n=e.importClause?.namedBindings;if(n&&r.isNamedImports(n)&&n.elements.find(l=>!l.isTypeOnly&&(l.propertyName?.text??l.name.text)==="createRequire"))throw y(t,i,"createRequire")}if(r.isPropertyAccessExpression(e)&&e.name.text==="createRequire"||r.isElementAccessExpression(e)&&x(e.argumentExpression)==="createRequire"||r.isBindingElement(e)&&(fe(e)==="createRequire"||r.isIdentifier(e.name)&&e.name.text==="createRequire"))throw y(t,i,"createRequire")}function le(e,t,i,r){const s=a();if(s.isPropertyAccessExpression(e)&&e.name.text==="require"&&v(e.expression,t)||s.isElementAccessExpression(e)&&x(e.argumentExpression)==="require"&&v(e.expression,t))throw y(i,r,e.getText());if(s.isIdentifier(e)&&e.text==="require"&&!E(e,t)&&!ue(e)&&!pe(e))throw y(i,r,"global require")}function y(e,t,i){return new Error(`Invalid Testkit authoring in "${e}": ${t.engineId}/${t.profileId} may not alias or indirectly access the module loader through ${i}; use literal import and require specifiers so engine boundaries can be verified`)}function v(e,t){return a().isIdentifier(e)&&(e.text==="globalThis"||e.text==="module")&&!E(e,t)}function ue(e){return a().isCallExpression(e.parent)&&e.parent.expression===e}function pe(e){const t=a(),i=e.parent;return me(e)||t.isPropertyAccessExpression(i)&&i.name===e||t.isPropertyAssignment(i)&&i.name===e||t.isImportSpecifier(i)&&i.propertyName===e||t.isExportSpecifier(i)&&i.parent.parent.moduleSpecifier!==void 0}function me(e){const t=a(),i=e.parent;return i.name===e&&!t.isShorthandPropertyAssignment(i)&&!t.isExportSpecifier(i)}function fe(e){const t=a(),i=e.propertyName;return i&&(t.isIdentifier(i)||t.isStringLiteralLike(i))?i.text:null}function q(e){const t=a(),i=new WeakMap,r=(n,o)=>{let l=i.get(n);l||(l=new Set,i.set(n,l)),N(l,o)},s=n=>{if(t.isImportDeclaration(n))de(n,e,r);else if(t.isImportEqualsDeclaration(n)&&!n.isTypeOnly)r(e,n.name);else if(t.isVariableDeclaration(n)){const o=n.parent,l=t.isVariableDeclarationList(o)&&(o.flags&t.NodeFlags.BlockScoped)===0?ge(n):w(n);r(l,n.name)}else t.isParameter(n)?r(ye(n),n.name):t.isFunctionDeclaration(n)&&n.name?r(w(n),n.name):t.isFunctionExpression(n)&&n.name?r(n,n.name):(t.isClassDeclaration(n)||t.isEnumDeclaration(n)||t.isModuleDeclaration(n))&&n.name&&t.isIdentifier(n.name)?r(w(n),n.name):t.isClassExpression(n)&&n.name?r(n,n.name):t.isCatchClause(n)&&n.variableDeclaration&&r(n,n.variableDeclaration.name);t.forEachChild(n,s)};return s(e),i}function de(e,t,i){const r=a(),s=e.importClause;if(!s||s.phaseModifier===r.SyntaxKind.TypeKeyword)return;s.name&&i(t,s.name);const n=s.namedBindings;if(n&&r.isNamespaceImport(n))i(t,n.name);else if(n&&r.isNamedImports(n))for(const o of n.elements)o.isTypeOnly||i(t,o.name)}function N(e,t){const i=a();if(i.isIdentifier(t)){e.add(t.text);return}for(const r of t.elements)i.isOmittedExpression(r)||N(e,r.name)}function E(e,t){const i=a();let r=e;for(;;){if(t.get(r)?.has(e.text))return!0;if(i.isSourceFile(r))return!1;r=r.parent}}function w(e){let t=e.parent;for(;;){if(xe(t))return t;t=t.parent}}function ge(e){const t=a();let i=e.parent;for(;;){if(t.isFunctionLike(i)||t.isSourceFile(i))return i;i=i.parent}}function ye(e){const t=a();let i=e.parent;for(;;){if(t.isFunctionLike(i)||t.isSourceFile(i))return i;i=i.parent}}function xe(e){const t=a();return t.isSourceFile(e)||t.isBlock(e)||t.isCaseBlock(e)||t.isCatchClause(e)||t.isForStatement(e)||t.isForInStatement(e)||t.isForOfStatement(e)||t.isFunctionLike(e)||t.isClassStaticBlockDeclaration(e)}export function collectModuleSpecifiers(e){const t=a(),i=new Set,r=q(e),s=n=>{const o=Se(n,r);o&&i.add(o);for(const l of C(n))s(l);t.forEachChild(n,s)};return s(e),i}function C(e){return e.jsDoc??[]}function Se(e,t){const i=a();if(i.isJSDocImportTag(e)&&i.isStringLiteralLike(e.moduleSpecifier)||(i.isImportDeclaration(e)||i.isExportDeclaration(e))&&e.moduleSpecifier&&i.isStringLiteralLike(e.moduleSpecifier))return e.moduleSpecifier.text;if(i.isImportEqualsDeclaration(e)&&i.isExternalModuleReference(e.moduleReference)&&i.isStringLiteralLike(e.moduleReference.expression))return e.moduleReference.expression.text;if(i.isImportTypeNode(e)){const s=e.argument;if(i.isLiteralTypeNode(s)&&i.isStringLiteralLike(s.literal))return s.literal.text}if(!i.isCallExpression(e)||!T(e,t))return null;const r=e.arguments[0];return i.isStringLiteralLike(r)?r.text:null}function Ie(e){const t=a();return t.isVariableStatement(e)?e.declarationList.declarations.reduce((i,r)=>i+M(r.name,"defineFile"),0):(t.isClassDeclaration(e)||t.isEnumDeclaration(e)||t.isFunctionDeclaration(e))&&e.name?.text==="defineFile"?1:0}function M(e,t){const i=a();return i.isIdentifier(e)?e.text===t?1:0:e.elements.reduce((r,s)=>i.isOmittedExpression(s)?r:r+M(s.name,t),0)}function ke(e,t){const i=S.resolve(e);if(S.isAbsolute(t))throw new Error(`Test file metadata path must be product-relative: ${t}`);const r=S.resolve(i,t);if(!h(i,r))throw new Error(`Test file metadata path escapes the product root: ${t}`);if(!g.existsSync(r))throw new Error(`Test file metadata source does not exist: ${t}`);const s=g.realpathSync(i),n=g.realpathSync(r);if(!h(s,n))throw new Error(`Test file metadata path resolves outside the product root: ${t}`);return n}function Ee(e){return e.parseDiagnostics??[]}function c(e,t,i){return new Error(`Invalid testkit metadata in "${e}": ${t}.`,{cause:i})}function R(e){return e instanceof Error?e.message.replace(/\.$/u,""):String(e)}
1
+ import{isWorkObserved as $,recordWork as L}from"../../kernel/work-metrics.js";import{isPathWithin as h}from"../../process/files/paths.js";import d from"fs";import S from"path";import{createRequire as A}from"node:module";import{fileURLToPath as B}from"node:url";import{ENGINE_IDS as O,PUBLIC_TEST_TYPES as j,isEngineId as K}from"../../domain/identifiers.js";import{normalizeRuntimeIsolationPolicy as V}from"../../domain/runtime-isolation-contract.js";import{resolveEngineProfile as W,allowsEngineAuthoringImport as _,expectedEngineAuthoringModule as z,invalidEngineAuthoringImport as U,requireMatchingEngineImport as G}from"../../engines/catalog.js";const J=A(import.meta.url),Y=B(import.meta.resolve("typescript"));let H;function a(){return H??=J(Y)}const I="@gfdelarue/testkit";export function loadTestFileMetadataMap(e,t={},i=new Map){for(const r of Object.values(t||{}).flatMap(s=>s??[]))for(const s of r.files||[])i.has(s)||i.set(s,readTestFileMetadata(e,s));return i}export function readTestFileMetadata(e,t){const i=j.find(m=>t.endsWith(`.${m}.testkit.ts`));if(i===void 0)return{engineId:"node-vitest",locks:[],profileId:"node-vitest",runtimeIsolation:null,skipReason:null};const r=a(),s=Ie(e,t);let n;if($()){const m=d.readFileSync(s);L("metadata.filesRead",1),L("metadata.bytesRead",m.byteLength),n=m.toString("utf8")}else n=d.readFileSync(s,"utf8");const o=r.createSourceFile(s,n,r.ScriptTarget.Latest,!0,r.ScriptKind.TS),l=ke(o);if(l.length>0){const m=l.map(f=>r.flattenDiagnosticMessageText(f.messageText," ")).join("; ");throw c(t,`TypeScript syntax is invalid: ${m}`)}let u=null,p=null;for(const m of o.statements)if(!(!r.isVariableStatement(m)||!ie(m))){for(const f of m.declarationList.declarations)if(!(!r.isIdentifier(f.name)||f.name.text!=="testkit")){if(u)throw c(t,'exported "testkit" metadata must be declared exactly once');if(!re(m))throw c(t,'exported "testkit" metadata must be const');u=f,p=m}}if(!u||!p)throw c(t,'export const testkit = defineFile({ engine: "..." }) is required');se(o,p,t);const x=Q(u.initializer,t,i);return requireMatchingEngineAuthoringImports(o,t,x),x}function Q(e,t,i){const r=a();if(!e)throw c(t,'exported "testkit" metadata has no value');if(!r.isCallExpression(e))throw c(t,"testkit metadata must use defineFile({ ... })");if(te(e.expression)!=="defineFile")throw c(t,"testkit metadata must call defineFile");if(e.arguments.length!==1)throw c(t,"defineFile metadata must receive exactly one object literal");const s=e.arguments[0];if(!r.isObjectLiteralExpression(s))throw c(t,"defineFile metadata must receive an object literal");return X(s,t,i)}function X(e,t,i){const r=a(),s={locks:[],runtimeIsolation:null,skipReason:null};let n=null;const o=new Set;for(const u of e.properties){if(r.isSpreadAssignment(u))throw c(t,"testkit metadata may not contain spread properties");if(!r.isPropertyAssignment(u))throw c(t,"testkit metadata properties must use key: value assignments");const p=b(u.name,t,"testkit metadata");if(!ne(p))throw c(t,`unknown metadata property "${p}"`);if(o.has(p))throw c(t,`duplicate metadata property "${p}"`);if(o.add(p),p==="engine"){n=k(u.initializer,t,'metadata property "engine"');continue}if(p==="locks"){s.locks=Z(u.initializer,t);continue}if(p==="skip"){s.skipReason=P(u.initializer,t);continue}s.runtimeIsolation=ee(u.initializer,t)}if(!n)throw c(t,'a literal "engine" property is required');if(!K(n))throw c(t,`unsupported engine "${n}"; expected one of: ${O.join(", ")}`);let l;try{l=W(i,n)}catch(u){throw c(t,M(u),u)}return{engineId:n,locks:s.locks,profileId:l.id,runtimeIsolation:s.runtimeIsolation,skipReason:s.skipReason}}function Z(e,t){const i=a();if(!i.isArrayLiteralExpression(e))throw c(t,'metadata property "locks" must be an array');const r=[];for(const s of e.elements){if(i.isSpreadElement(s)||i.isOmittedExpression(s))throw c(t,'metadata property "locks" must contain only string literals');r.push(D(s,t,'metadata property "locks" entry'))}return[...new Set(r)].sort()}function P(e,t){const i=a(),r=y(e);if(r!==null)return F(r,t,'metadata property "skip"').trim();if(!i.isObjectLiteralExpression(e))throw c(t,'metadata property "skip" must be a string literal or { reason: string literal }');if(e.properties.length!==1)throw c(t,'metadata property "skip" object must contain exactly one reason property');const s=e.properties[0];if(!i.isPropertyAssignment(s))throw c(t,'metadata property "skip" reason must use a string-literal assignment');if(b(s.name,t,'metadata property "skip"')!=="reason")throw c(t,'metadata property "skip" object only accepts "reason"');return D(s.initializer,t,'metadata property "skip.reason"')}function ee(e,t){const i=k(e,t,'metadata property "runtimeIsolation"');try{return V(i,"defineFile runtimeIsolation")}catch(r){throw c(t,M(r),r)}}function te(e){return a().isIdentifier(e)?e.text:null}function b(e,t,i){const r=a();if(r.isIdentifier(e)||r.isStringLiteral(e))return e.text;throw c(t,`${i} property names must be plain identifiers or string literals`)}function y(e){const t=a();return t.isStringLiteral(e)||t.isNoSubstitutionTemplateLiteral(e)?e.text:null}function k(e,t,i){const r=y(e);if(r===null)throw c(t,`${i} must be a string literal`);return F(r,t,i)}function D(e,t,i){return k(e,t,i).trim()}function F(e,t,i){if(e.trim().length===0)throw c(t,`${i} must not be empty`);return e}function ie(e){const t=a();return(e.modifiers||[]).some(i=>i.kind===t.SyntaxKind.ExportKeyword)}function re(e){const t=a();return(e.declarationList.flags&t.NodeFlags.Const)!==0}function ne(e){return["engine","locks","runtimeIsolation","skip"].includes(e)}function se(e,t,i){const r=a();let s=null,n=0;for(const o of e.statements){if(r.isImportDeclaration(o)){const l=o.importClause;if(!l||l.phaseModifier===r.SyntaxKind.TypeKeyword)continue;l.name?.text==="defineFile"&&(n+=1);const u=l.namedBindings;if(u&&r.isNamespaceImport(u)){u.name.text==="defineFile"&&(n+=1);continue}if(!u||!r.isNamedImports(u))continue;for(const p of u.elements){if(p.isTypeOnly||p.name.text!=="defineFile")continue;if(n+=1,(p.propertyName?.text??p.name.text)==="defineFile"&&r.isStringLiteral(o.moduleSpecifier)&&o.moduleSpecifier.text===I){if(s)throw c(i,`defineFile must have exactly one named value import from "${I}"`);s=o}}continue}n+=Se(o)}if(!s)throw c(i,`defineFile must be a named value import from "${I}"`);if(n!==1)throw c(i,"the imported defineFile binding may not be shadowed or redeclared");if(s.getStart(e)>t.getStart(e))throw c(i,"the defineFile import must precede metadata")}export function requireMatchingEngineAuthoringImports(e,t,i){oe(e,t,i);const r=z(i.profileId);for(const s of collectModuleSpecifiers(e))if(G(t,i,s),!_(r,s))throw U(t,i,s,r)}function oe(e,t,i){const r=a(),s=q(e),n=o=>{if(ae(o,t,i),ce(o,s,t,i),r.isCallExpression(o)&&T(o,s)){const l=o.arguments.at(0);if(!l||!r.isStringLiteralLike(l)){const u=o.expression.kind===r.SyntaxKind.ImportKeyword?"import()":"require()";throw new Error(`Invalid Testkit authoring in "${t}": ${i.engineId}/${i.profileId} may not use non-literal ${u}; module specifiers must be string literals so engine boundaries can be verified`)}}for(const l of C(o))n(l);r.forEachChild(o,n)};n(e)}function T(e,t){const i=a();return e.expression.kind===i.SyntaxKind.ImportKeyword||i.isIdentifier(e.expression)&&e.expression.text==="require"&&!E(e.expression,t)}function ae(e,t,i){const r=a();if(r.isImportDeclaration(e)){const n=e.importClause?.namedBindings;if(n&&r.isNamedImports(n)&&n.elements.find(l=>!l.isTypeOnly&&(l.propertyName?.text??l.name.text)==="createRequire"))throw g(t,i,"createRequire")}if(r.isPropertyAccessExpression(e)&&e.name.text==="createRequire"||r.isElementAccessExpression(e)&&y(e.argumentExpression)==="createRequire"||r.isBindingElement(e)&&(me(e)==="createRequire"||r.isIdentifier(e.name)&&e.name.text==="createRequire"))throw g(t,i,"createRequire")}function ce(e,t,i,r){const s=a();if(s.isPropertyAccessExpression(e)&&e.name.text==="require"&&v(e.expression,t)||s.isElementAccessExpression(e)&&y(e.argumentExpression)==="require"&&v(e.expression,t))throw g(i,r,e.getText());if(s.isIdentifier(e)&&e.text==="require"&&!E(e,t)&&!le(e)&&!ue(e))throw g(i,r,"global require")}function g(e,t,i){return new Error(`Invalid Testkit authoring in "${e}": ${t.engineId}/${t.profileId} may not alias or indirectly access the module loader through ${i}; use literal import and require specifiers so engine boundaries can be verified`)}function v(e,t){return a().isIdentifier(e)&&(e.text==="globalThis"||e.text==="module")&&!E(e,t)}function le(e){return a().isCallExpression(e.parent)&&e.parent.expression===e}function ue(e){const t=a(),i=e.parent;return pe(e)||t.isPropertyAccessExpression(i)&&i.name===e||t.isPropertyAssignment(i)&&i.name===e||t.isImportSpecifier(i)&&i.propertyName===e||t.isExportSpecifier(i)&&i.parent.parent.moduleSpecifier!==void 0}function pe(e){const t=a(),i=e.parent;return i.name===e&&!t.isShorthandPropertyAssignment(i)&&!t.isExportSpecifier(i)}function me(e){const t=a(),i=e.propertyName;return i&&(t.isIdentifier(i)||t.isStringLiteralLike(i))?i.text:null}function q(e){const t=a(),i=new WeakMap,r=(n,o)=>{let l=i.get(n);l||(l=new Set,i.set(n,l)),N(l,o)},s=n=>{if(t.isImportDeclaration(n))fe(n,e,r);else if(t.isImportEqualsDeclaration(n)&&!n.isTypeOnly)r(e,n.name);else if(t.isVariableDeclaration(n)){const o=n.parent,l=t.isVariableDeclarationList(o)&&(o.flags&t.NodeFlags.BlockScoped)===0?de(n):w(n);r(l,n.name)}else t.isParameter(n)?r(ge(n),n.name):t.isFunctionDeclaration(n)&&n.name?r(w(n),n.name):t.isFunctionExpression(n)&&n.name?r(n,n.name):(t.isClassDeclaration(n)||t.isEnumDeclaration(n)||t.isModuleDeclaration(n))&&n.name&&t.isIdentifier(n.name)?r(w(n),n.name):t.isClassExpression(n)&&n.name?r(n,n.name):t.isCatchClause(n)&&n.variableDeclaration&&r(n,n.variableDeclaration.name);t.forEachChild(n,s)};return s(e),i}function fe(e,t,i){const r=a(),s=e.importClause;if(!s||s.phaseModifier===r.SyntaxKind.TypeKeyword)return;s.name&&i(t,s.name);const n=s.namedBindings;if(n&&r.isNamespaceImport(n))i(t,n.name);else if(n&&r.isNamedImports(n))for(const o of n.elements)o.isTypeOnly||i(t,o.name)}function N(e,t){const i=a();if(i.isIdentifier(t)){e.add(t.text);return}for(const r of t.elements)i.isOmittedExpression(r)||N(e,r.name)}function E(e,t){const i=a();let r=e;for(;;){if(t.get(r)?.has(e.text))return!0;if(i.isSourceFile(r))return!1;r=r.parent}}function w(e){let t=e.parent;for(;;){if(ye(t))return t;t=t.parent}}function de(e){const t=a();let i=e.parent;for(;;){if(t.isFunctionLike(i)||t.isSourceFile(i))return i;i=i.parent}}function ge(e){const t=a();let i=e.parent;for(;;){if(t.isFunctionLike(i)||t.isSourceFile(i))return i;i=i.parent}}function ye(e){const t=a();return t.isSourceFile(e)||t.isBlock(e)||t.isCaseBlock(e)||t.isCatchClause(e)||t.isForStatement(e)||t.isForInStatement(e)||t.isForOfStatement(e)||t.isFunctionLike(e)||t.isClassStaticBlockDeclaration(e)}export function collectModuleSpecifiers(e){const t=a(),i=new Set,r=q(e),s=n=>{const o=xe(n,r);o&&i.add(o);for(const l of C(n))s(l);t.forEachChild(n,s)};return s(e),i}function C(e){return e.jsDoc??[]}function xe(e,t){const i=a();if(i.isJSDocImportTag(e)&&i.isStringLiteralLike(e.moduleSpecifier)||(i.isImportDeclaration(e)||i.isExportDeclaration(e))&&e.moduleSpecifier&&i.isStringLiteralLike(e.moduleSpecifier))return e.moduleSpecifier.text;if(i.isImportEqualsDeclaration(e)&&i.isExternalModuleReference(e.moduleReference)&&i.isStringLiteralLike(e.moduleReference.expression))return e.moduleReference.expression.text;if(i.isImportTypeNode(e)){const s=e.argument;if(i.isLiteralTypeNode(s)&&i.isStringLiteralLike(s.literal))return s.literal.text}if(!i.isCallExpression(e)||!T(e,t))return null;const r=e.arguments[0];return i.isStringLiteralLike(r)?r.text:null}function Se(e){const t=a();return t.isVariableStatement(e)?e.declarationList.declarations.reduce((i,r)=>i+R(r.name,"defineFile"),0):(t.isClassDeclaration(e)||t.isEnumDeclaration(e)||t.isFunctionDeclaration(e))&&e.name?.text==="defineFile"?1:0}function R(e,t){const i=a();return i.isIdentifier(e)?e.text===t?1:0:e.elements.reduce((r,s)=>i.isOmittedExpression(s)?r:r+R(s.name,t),0)}function Ie(e,t){const i=S.resolve(e);if(S.isAbsolute(t))throw new Error(`Test file metadata path must be product-relative: ${t}`);const r=S.resolve(i,t);if(!h(i,r))throw new Error(`Test file metadata path escapes the product root: ${t}`);if(!d.existsSync(r))throw new Error(`Test file metadata source does not exist: ${t}`);const s=d.realpathSync(i),n=d.realpathSync(r);if(!h(s,n))throw new Error(`Test file metadata path resolves outside the product root: ${t}`);return n}function ke(e){return e.parseDiagnostics??[]}function c(e,t,i){return new Error(`Invalid testkit metadata in "${e}": ${t}.`,{cause:i})}function M(e){return e instanceof Error?e.message.replace(/\.$/u,""):String(e)}
@@ -1 +1,2 @@
1
- import l from"node:path";import{requireServiceName as S}from"../../domain/service-name.js";import{errorMessage as y}from"../../kernel/error-serialization.js";import{isUnknownRecord as v,readDynamicProperty as d}from"../../kernel/unknown-values.js";import{normalizeDiscoveryConfig as b}from"./decode-discovery.js";import{DEFAULT_DISCOVERY_EXCLUDES as C,shouldExcludeDiscoveryPath as w}from"./path-policy.js";const j=String;export const ROOT_GROUP="root",LEGACY_OWNERSHIP_NOTICE="testkit.config.ts has services but no groups, so each service owns the tests under its directory as a group of the same name. Legacy ownership will be removed; declare groups with roots and uses instead.";export function resolveGroups(e,r){const t=[],s=m(d(e,"discovery"),null,t),n=L(e,t),o=e&&Object.hasOwn(e,"groups")?D(d(e,"groups"),n,t):O(n,t);e&&Object.hasOwn(e,"groups")&&t.push(...z(n,o.groups));const u=(s.exclude??[]).map(f),p=[...C,...u],a=r.map(c=>P(c,o.rules,c.kind==="typed"?p:u)),i=new Set(o.groups.filter(c=>c.source==="services").map(c=>c.name));return{assignments:a,groups:[...o.groups,{name:ROOT_GROUP,roots:["."],source:"implicit",uses:[]}],notice:a.some(c=>c.status==="owned"&&i.has(c.group))?LEGACY_OWNERSHIP_NOTICE:null,problems:t}}export function readDiscoveryScan(e){return m(d(e,"discovery"),null,[])}export function repoDiscoveryProblem(e){const r=[];return m(d(e,"discovery"),null,r),r[0]??null}export function legacyServiceProblem(e,r){const t=[];return O({[e]:r},t),t[0]??null}function P(e,r,t){const s=I(e.path);if(w(s,t))return{path:s,status:"excluded"};const n=r.filter(i=>(e.kind==="typed"||!i.typedOnly)&&(i.prefix===null||x(s,i.prefix))),o=n.filter(i=>!w(s,i.exclude));if(n.length>0&&o.length===0)return{path:s,status:"excluded"};if(o.length===0)return{group:ROOT_GROUP,path:s,relativePath:s,status:"owned"};const u=Math.max(...o.map(i=>i.depth)),p=o.filter(i=>i.depth===u),a=[...new Set(p.map(i=>i.group))].sort((i,c)=>i.localeCompare(c));return a.length>1?{groups:a,path:s,status:"ambiguous"}:{group:a[0],path:s,relativePath:R(p[0],s),status:"owned"}}function D(e,r,t){const s={groups:[],rules:[]};if(!v(e))return t.push("testkit.config.ts groups must be an object"),s;for(const[n,o]of Object.entries(e)){const u=`groups.${n}`;if(n===ROOT_GROUP){t.push(`${u} is reserved for the implicit group that owns files no other group claims; rename it`);continue}try{S(n,`Group name "${n}"`)}catch(i){t.push(y(i));continue}if(!v(o)){t.push(`${u} must be an object with roots`);continue}const p=$(o.roots,`${u}.roots`,!0,t),a=$(o.exclude,`${u}.exclude`,!1,t);s.groups.push({name:n,roots:p,source:"groups",uses:A(o.uses,u,r,t)});for(const i of p)s.rules.push({depth:i==="."?0:i.split("/").length,exclude:a,group:n,legacyCwd:null,prefix:i==="."?null:i,typedOnly:!1})}return s}const E=["skip","requirements","runtimeIsolation"];function z(e,r){return Object.entries(e).flatMap(([t,s])=>E.filter(n=>d(s,n)!==void 0).map(n=>{const o=r.find(u=>u.uses.includes(t))?.name??t;return`services.${t}.${n} applies to tests, and services own no tests when groups is set; move it to groups.${o}.${n}`}))}function $(e,r,t,s){if(e===void 0&&!t)return[];if(!Array.isArray(e)||t&&e.length===0)return s.push(`${r} must be ${t?"a non-empty array":"an array"} of repo-relative paths`),[];const n=e,o=new Set;for(const u of n){const p=k(u);p===null?s.push(`${r} entry ${JSON.stringify(u)} must be a relative path inside the product directory`):o.add(p)}return[...o]}function A(e,r,t,s){if(e===void 0)return[];if(!_(e))return s.push(`${r}.uses must be an array of service names`),[];const n=[];for(const o of new Set(e))Object.hasOwn(t,o)?n.push(o):s.push(`${r}.uses names "${o}", which is not a service; declare services.${o} or remove it from ${r}.uses`);return n}function O(e,r){const t={groups:[],rules:[]};for(const[s,n]of Object.entries(e)){if(s===ROOT_GROUP){r.push("services.root is reserved for the implicit group that owns files no other group claims; rename the service");continue}const o=m(d(n,"discovery"),s,r),u=G(n),p=o.roots&&o.roots.length>0?o.roots:["."],a=(o.exclude??[]).map(c=>f(u==="."?c:l.posix.join(u,c))),i=p.map(c=>{const g=f(c),h=u==="."?g:g==="."?u:f(l.posix.join(u,g));return{depth:h==="."?0:h.split("/").filter(Boolean).length,exclude:a,group:s,legacyCwd:u==="."?null:u,prefix:h==="."?null:h,typedOnly:!0}});t.rules.push(...i),t.groups.push({name:s,roots:[...new Set(i.map(c=>c.prefix??"."))],source:"services",uses:[s]})}return t}function G(e){return f(j(d(d(e,"local"),"cwd")||"."))}function L(e,r){const t=d(e,"services");return t==null?{}:v(t)?t:(r.push("testkit.config.ts services must be an object"),{})}function m(e,r,t){try{return b(e,{allowRoots:!0})}catch(s){return t.push(r===null?y(s):`Service "${r}" has invalid discovery config: ${y(s)}`),{}}}function R(e,r){return e.prefix!==null&&x(r,e.prefix)?l.posix.relative(e.prefix,r):e.legacyCwd!==null&&x(r,e.legacyCwd)?l.posix.relative(e.legacyCwd,r):r}function _(e){return Array.isArray(e)&&e.every(r=>typeof r=="string")}function x(e,r){return e===r||e.startsWith(`${r}/`)}function k(e){if(typeof e!="string")return null;const r=e.trim(),t=r.replaceAll("\\","/");if(t===""||l.posix.isAbsolute(t)||l.win32.isAbsolute(r))return null;const s=l.posix.normalize(t).replace(/\/+$/u,"")||".";return s===".."||s.startsWith("../")?null:s}function I(e){return l.posix.normalize(e.split(l.sep).join("/")).replace(/\/+$/u,"")}function f(e){const r=e.split(l.sep).join("/");return r==="."||r==="./"?".":r.replace(/^\.\/+/u,"").replace(/\/+$/u,"")||"."}
1
+ import f from"node:path";import{migrationText as P}from"../../domain/retired-test-types.js";import{requireServiceName as b}from"../../domain/service-name.js";import{errorMessage as x}from"../../kernel/error-serialization.js";import{unknownKeysMessage as A}from"../../kernel/known-keys.js";import{isUnknownRecord as h,readDynamicProperty as a}from"../../kernel/unknown-values.js";import{DATABASE_FIXTURE_DISCOVERY as D}from"../authoring/database-fixture-discovery.js";import{normalizeDiscoveryConfig as k}from"./decode-discovery.js";import{DEFAULT_DISCOVERY_EXCLUDES as z,shouldExcludeDiscoveryPath as m}from"./path-policy.js";const E=String,R=Object.keys({exclude:!0,requirements:!0,roots:!0,runtimeIsolation:!0,skip:!0,uses:!0});export const ROOT_GROUP="root";export function resolveGroups(e,r){const t=[],s=g(a(e,"discovery"),t),i=T(e,t),u=(s.exclude??[]).map(d),n=[...z,...u],o=e!=null&&Object.hasOwn(e,"groups"),p=o?G(a(e,"groups"),i,t):{groups:[],rules:[]};return o&&t.push(...C(i,p.groups)),{assignments:r.map(l=>v(l,p.rules,l.kind==="typed"?n:u)),groups:[...p.groups,{name:ROOT_GROUP,roots:["."],source:"implicit",uses:[]}],migration:o?null:I(i,r,n),problems:t}}export function readDiscoveryScan(e){return g(a(e,"discovery"),[])}export function repoDiscoveryProblem(e){const r=[];return g(a(e,"discovery"),r),r[0]??null}function v(e,r,t){const s=F(e.path);if(m(s,t))return{path:s,status:"excluded"};const i=r.filter(c=>c.prefix===null||O(s,c.prefix)),u=i.filter(c=>!m(s,c.exclude));if(i.length>0&&u.length===0)return{path:s,status:"excluded"};if(u.length===0)return{group:ROOT_GROUP,path:s,relativePath:s,status:"owned"};const n=Math.max(...u.map(c=>c.depth)),o=u.filter(c=>c.depth===n),p=[...new Set(o.map(c=>c.group))].sort((c,j)=>c.localeCompare(j));if(p.length>1)return{groups:p,path:s,status:"ambiguous"};const l=o[0].prefix;return{group:p[0],path:s,relativePath:l===null?s:f.posix.relative(l,s),status:"owned"}}function G(e,r,t){const s={groups:[],rules:[]};if(!h(e))return t.push("testkit.config.ts groups must be an object"),s;for(const[i,u]of Object.entries(e)){const n=`groups.${i}`;if(i===ROOT_GROUP){t.push(`${n} is reserved for the implicit group that owns files no other group claims; rename it`);continue}try{b(i,`Group name "${i}"`)}catch(c){t.push(x(c));continue}if(!h(u)){t.push(`${n} must be an object with roots`);continue}const o=A(u,R,n);o!==null&&t.push(o);const p=w(u.roots,`${n}.roots`,!0,t),l=w(u.exclude,`${n}.exclude`,!1,t);s.groups.push({name:i,roots:p,source:"groups",uses:U(u.uses,n,r,t)});for(const c of p)s.rules.push({depth:c==="."?0:c.split("/").length,exclude:l,group:i,prefix:c==="."?null:c})}return s}const _=["skip","requirements","runtimeIsolation"];function C(e,r){return Object.entries(e).flatMap(([t,s])=>{const i=r.find(o=>o.uses.includes(t))?.name??t,u=_.filter(o=>a(s,o)!==void 0).map(o=>`services.${t}.${o} applies to tests, and services own no tests when groups is set; move it to groups.${i}.${o}`),n=a(s,"discovery");return n!==void 0&&!L(n)&&u.push(`services.${t}.discovery applies to tests, and services own no tests when groups is set; move its roots and exclude to groups.${i}.roots and groups.${i}.exclude, as paths from the product directory`),u})}function L(e){return h(e)&&JSON.stringify(e)===JSON.stringify(D)}function w(e,r,t,s){if(e===void 0&&!t)return[];if(!Array.isArray(e)||t&&e.length===0)return s.push(`${r} must be ${t?"a non-empty array":"an array"} of repo-relative paths`),[];const i=e,u=new Set;for(const n of i){const o=S(n);o===null?s.push(`${r} entry ${JSON.stringify(n)} must be a relative path inside the product directory`):u.add(o)}return[...u]}function U(e,r,t,s){if(e===void 0)return[];if(!J(e))return s.push(`${r}.uses must be an array of service names`),[];const i=[];for(const u of new Set(e))Object.hasOwn(t,u)?i.push(u):s.push(`${r}.uses names "${u}", which is not a service; declare services.${u} or remove it from ${r}.uses`);return i}function I(e,r,t){const s=Object.entries(e).flatMap(([n,o])=>M(n,o)),i=new Set;for(const n of r){if(n.kind!=="typed")continue;const o=v(n,s,t);if(o.status==="ambiguous")for(const p of o.groups)i.add(p);if(o.status==="owned"&&o.group!==ROOT_GROUP&&i.add(o.group),o.status==="excluded"&&!m(o.path,t))for(const p of s)(p.prefix===null||O(o.path,p.prefix))&&i.add(p.group)}return i.size===0?null:["testkit.config.ts has services and no groups. Testkit 0.6.0 removed legacy service ownership, so services no longer own the test files under their directories. Add these groups to keep each typed test file with the service that owned it:","","groups: {",...Object.keys(e).filter(n=>i.has(n)).map(n=>{const o=s.filter(c=>c.group===n),p=o[0].exclude.filter($),l=[`roots: ${y([...new Set(o.map(c=>c.prefix??"."))].filter($))}`,`uses: ${y([n])}`,...p.length>0?[`exclude: ${y(p)}`]:[]];return` ${/^[A-Za-z_$][\w$]*$/u.test(n)?n:JSON.stringify(n)}: { ${l.join(", ")} },`}),"},","","A group also owns the plain Vitest files under its roots.",`${P("Remove legacy service ownership")}.`].join(`
2
+ `)}function M(e,r){if(e===ROOT_GROUP)return[];const t=g(a(r,"discovery"),null);if(t===null)return[];const s=N(r),i=(t.exclude??[]).map(u=>d(s==="."?u:f.posix.join(s,u)));return(t.roots&&t.roots.length>0?t.roots:["."]).map(u=>{const n=d(u),o=s==="."?n:n==="."?s:d(f.posix.join(s,n));return{depth:o==="."?0:o.split("/").filter(Boolean).length,exclude:i,group:e,prefix:o==="."?null:o}})}function $(e){return S(e)!==null}function y(e){return`[${e.map(r=>JSON.stringify(r)).join(", ")}]`}function N(e){return d(E(a(a(e,"local"),"cwd")||"."))}function T(e,r){const t=a(e,"services");return t==null?{}:h(t)?t:(r.push("testkit.config.ts services must be an object"),{})}function g(e,r){try{return k(e,{allowRoots:!0})}catch(t){return r===null?null:(r.push(x(t)),{})}}function J(e){return Array.isArray(e)&&e.every(r=>typeof r=="string")}function O(e,r){return e===r||e.startsWith(`${r}/`)}function S(e){if(typeof e!="string")return null;const r=e.trim(),t=r.replaceAll("\\","/");if(t===""||f.posix.isAbsolute(t)||f.win32.isAbsolute(r))return null;const s=f.posix.normalize(t).replace(/\/+$/u,"")||".";return s===".."||s.startsWith("../")?null:s}function F(e){return f.posix.normalize(e.split(f.sep).join("/")).replace(/\/+$/u,"")}function d(e){const r=e.split(f.sep).join("/");return r==="."||r==="./"?".":r.replace(/^\.\/+/u,"").replace(/\/+$/u,"")||"."}
@@ -1 +1 @@
1
- export{loadConfigContext,loadConfigs,parseDotenv,resolveProductDir,resolveServiceCwd}from"./load.js";export{discoverTests,loadDiscoveredConfigContext,loadDiscoveryContext,fileDisplayName,formatDisplayName,testTypeContract}from"./discover-tests.js";export{findConfigFile,loadTestkitConfig}from"./config-loader.js";export{materializeDatabaseConfig}from"./database-materialization.js";export{detectNextApp}from"./runtime.js";export{DEFAULT_RUNTIME_ISOLATION_POLICY}from"./runtime-isolation.js";export{collectModuleSpecifiers,requireMatchingEngineAuthoringImports}from"./file-metadata.js";export{hasDiscoveryErrors,buildDiscoveryErrorFromDiagnostics}from"./discovery.js";
1
+ export{loadConfigContext,loadConfigs,parseDotenv,resolveProductDir,resolveServiceCwd}from"./load.js";export{discoverTests,loadDiscoveredConfigContext,loadDiscoveryContext}from"./discover-tests.js";export{fileDisplayName,formatDisplayName,testTypeContract}from"./discovery-result.js";export{findConfigFile,loadTestkitConfig}from"./config-loader.js";export{materializeDatabaseConfig}from"./database-materialization.js";export{detectNextApp}from"./runtime.js";export{DEFAULT_RUNTIME_ISOLATION_POLICY}from"./runtime-isolation.js";export{collectModuleSpecifiers,requireMatchingEngineAuthoringImports}from"./file-metadata.js";export{hasDiscoveryErrors,buildDiscoveryErrorFromDiagnostics}from"./discovery.js";