@gfdelarue/testkit 0.3.7 → 0.4.0

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 (144) hide show
  1. package/dist/cli/command-catalog.d.ts +0 -127
  2. package/dist/cli/command-catalog.js +1 -1
  3. package/dist/cli/command-registry.d.ts +1 -0
  4. package/dist/cli/command-registry.js +1 -1
  5. package/dist/cli/entrypoint.js +1 -1
  6. package/dist/cli/framework/execute.d.ts +1 -0
  7. package/dist/cli/framework/execute.js +6 -6
  8. package/dist/cli/index.js +1 -1
  9. package/dist/cli/version-drift.js +7 -7
  10. package/dist/config/authoring/index.d.ts +0 -5
  11. package/dist/config/loader/index.d.ts +0 -1
  12. package/dist/config/loader/index.js +1 -1
  13. package/dist/evidence/bundles.d.ts +2 -3
  14. package/dist/evidence/bundles.js +1 -1
  15. package/dist/evidence/contracts.d.ts +0 -8
  16. package/dist/evidence/index.d.ts +1 -1
  17. package/dist/evidence/index.js +1 -1
  18. package/dist/process/execa-bundle.json +1 -1
  19. package/package.json +3 -10
  20. package/testkit-config-schema.md +0 -23
  21. package/dist/app/cloud/campaigns.d.ts +0 -6
  22. package/dist/app/cloud/campaigns.js +0 -1
  23. package/dist/app/cloud/control-actions.d.ts +0 -12
  24. package/dist/app/cloud/control-actions.js +0 -2
  25. package/dist/app/cloud/control-links.d.ts +0 -5
  26. package/dist/app/cloud/control-links.js +0 -1
  27. package/dist/app/cloud/control-read.d.ts +0 -24
  28. package/dist/app/cloud/control-read.js +0 -1
  29. package/dist/app/cloud/control-resources.d.ts +0 -3
  30. package/dist/app/cloud/control-resources.js +0 -1
  31. package/dist/app/cloud/control.d.ts +0 -34
  32. package/dist/app/cloud/control.js +0 -5
  33. package/dist/app/cloud/credentials.d.ts +0 -11
  34. package/dist/app/cloud/credentials.js +0 -2
  35. package/dist/app/cloud/index.d.ts +0 -22
  36. package/dist/app/cloud/index.js +0 -2
  37. package/dist/app/cloud/login.d.ts +0 -63
  38. package/dist/app/cloud/login.js +0 -2
  39. package/dist/app/cloud/migration/archive.d.ts +0 -13
  40. package/dist/app/cloud/migration/archive.js +0 -1
  41. package/dist/app/cloud/migration/commands.d.ts +0 -8
  42. package/dist/app/cloud/migration/commands.js +0 -1
  43. package/dist/app/cloud/migration/evidence.d.ts +0 -13
  44. package/dist/app/cloud/migration/evidence.js +0 -2
  45. package/dist/app/cloud/migration/files.d.ts +0 -16
  46. package/dist/app/cloud/migration/files.js +0 -2
  47. package/dist/app/cloud/migration/index.d.ts +0 -6
  48. package/dist/app/cloud/migration/index.js +0 -1
  49. package/dist/app/cloud/migration/journal.d.ts +0 -64
  50. package/dist/app/cloud/migration/journal.js +0 -1
  51. package/dist/app/cloud/migration/mapping-dispositions.d.ts +0 -3
  52. package/dist/app/cloud/migration/mapping-dispositions.js +0 -1
  53. package/dist/app/cloud/migration/mapping.d.ts +0 -11
  54. package/dist/app/cloud/migration/mapping.js +0 -2
  55. package/dist/app/cloud/migration/model.d.ts +0 -175
  56. package/dist/app/cloud/migration/model.js +0 -1
  57. package/dist/app/cloud/migration/plan.d.ts +0 -7
  58. package/dist/app/cloud/migration/plan.js +0 -1
  59. package/dist/app/cloud/migration/progress.d.ts +0 -19
  60. package/dist/app/cloud/migration/progress.js +0 -1
  61. package/dist/app/cloud/migration/reconcile.d.ts +0 -22
  62. package/dist/app/cloud/migration/reconcile.js +0 -1
  63. package/dist/app/cloud/migration/reconciliation-spool.d.ts +0 -4
  64. package/dist/app/cloud/migration/reconciliation-spool.js +0 -1
  65. package/dist/app/cloud/migration/state.d.ts +0 -15
  66. package/dist/app/cloud/migration/state.js +0 -1
  67. package/dist/app/cloud/migration/transfer.d.ts +0 -6
  68. package/dist/app/cloud/migration/transfer.js +0 -1
  69. package/dist/cli/cloud-apply-help.d.ts +0 -1
  70. package/dist/cli/cloud-apply-help.js +0 -56
  71. package/dist/cli/cloud-selection.d.ts +0 -2
  72. package/dist/cli/cloud-selection.js +0 -3
  73. package/dist/cli/commands/cloud/apply.d.ts +0 -5
  74. package/dist/cli/commands/cloud/apply.js +0 -1
  75. package/dist/cli/commands/cloud/campaign/create.d.ts +0 -5
  76. package/dist/cli/commands/cloud/campaign/create.js +0 -1
  77. package/dist/cli/commands/cloud/download.d.ts +0 -5
  78. package/dist/cli/commands/cloud/download.js +0 -1
  79. package/dist/cli/commands/cloud/list.d.ts +0 -5
  80. package/dist/cli/commands/cloud/list.js +0 -1
  81. package/dist/cli/commands/cloud/login.d.ts +0 -13
  82. package/dist/cli/commands/cloud/login.js +0 -2
  83. package/dist/cli/commands/cloud/logout.d.ts +0 -7
  84. package/dist/cli/commands/cloud/logout.js +0 -1
  85. package/dist/cli/commands/cloud/migrate/apply.d.ts +0 -2
  86. package/dist/cli/commands/cloud/migrate/apply.js +0 -1
  87. package/dist/cli/commands/cloud/migrate/plan.d.ts +0 -7
  88. package/dist/cli/commands/cloud/migrate/plan.js +0 -1
  89. package/dist/cli/commands/cloud/migrate/progress.d.ts +0 -2
  90. package/dist/cli/commands/cloud/migrate/progress.js +0 -2
  91. package/dist/cli/commands/cloud/migrate/reconcile.d.ts +0 -2
  92. package/dist/cli/commands/cloud/migrate/reconcile.js +0 -1
  93. package/dist/cli/commands/cloud/policy/preview.d.ts +0 -5
  94. package/dist/cli/commands/cloud/policy/preview.js +0 -1
  95. package/dist/cli/commands/cloud/project/create.d.ts +0 -5
  96. package/dist/cli/commands/cloud/project/create.js +0 -1
  97. package/dist/cli/commands/cloud/publish.d.ts +0 -7
  98. package/dist/cli/commands/cloud/publish.js +0 -1
  99. package/dist/cli/commands/cloud/session/revoke.d.ts +0 -8
  100. package/dist/cli/commands/cloud/session/revoke.js +0 -1
  101. package/dist/cli/commands/cloud/show.d.ts +0 -5
  102. package/dist/cli/commands/cloud/show.js +0 -1
  103. package/dist/cli/commands/cloud/snapshot.d.ts +0 -5
  104. package/dist/cli/commands/cloud/snapshot.js +0 -1
  105. package/dist/cli/commands/cloud/status.d.ts +0 -33
  106. package/dist/cli/commands/cloud/status.js +0 -1
  107. package/dist/cloud/account.d.ts +0 -34
  108. package/dist/cloud/account.js +0 -1
  109. package/dist/cloud/archive-contract.d.ts +0 -1
  110. package/dist/cloud/archive-contract.js +0 -1
  111. package/dist/cloud/archive.d.ts +0 -12
  112. package/dist/cloud/archive.js +0 -1
  113. package/dist/cloud/campaigns.d.ts +0 -200
  114. package/dist/cloud/campaigns.js +0 -2
  115. package/dist/cloud/client.d.ts +0 -68
  116. package/dist/cloud/client.js +0 -4
  117. package/dist/cloud/download.d.ts +0 -5
  118. package/dist/cloud/download.js +0 -1
  119. package/dist/cloud/historical-contract.d.ts +0 -224
  120. package/dist/cloud/historical-contract.js +0 -1
  121. package/dist/cloud/historical-json.d.ts +0 -16
  122. package/dist/cloud/historical-json.js +0 -1
  123. package/dist/cloud/historical.d.ts +0 -69
  124. package/dist/cloud/historical.js +0 -1
  125. package/dist/cloud/pagination.d.ts +0 -5
  126. package/dist/cloud/pagination.js +0 -1
  127. package/dist/cloud/transport.d.ts +0 -18
  128. package/dist/cloud/transport.js +0 -1
  129. package/dist/config/loader/cloud.d.ts +0 -2
  130. package/dist/config/loader/cloud.js +0 -1
  131. package/docs/cloud-control.md +0 -108
  132. package/docs/testkit-cloud-boundary.md +0 -628
  133. package/node_modules/buffer-crc32/LICENSE +0 -19
  134. package/node_modules/buffer-crc32/README.md +0 -55
  135. package/node_modules/buffer-crc32/dist/index.cjs +0 -306
  136. package/node_modules/buffer-crc32/dist/index.d.cts +0 -35
  137. package/node_modules/buffer-crc32/dist/index.d.mts +0 -35
  138. package/node_modules/buffer-crc32/dist/index.mjs +0 -304
  139. package/node_modules/buffer-crc32/index.d.ts +0 -35
  140. package/node_modules/buffer-crc32/package.json +0 -54
  141. package/node_modules/yazl/LICENSE +0 -21
  142. package/node_modules/yazl/README.md +0 -457
  143. package/node_modules/yazl/index.js +0 -826
  144. package/node_modules/yazl/package.json +0 -34
@@ -18,133 +18,6 @@ export declare const commandCatalog: Readonly<{
18
18
  readonly cache: import("./framework/definitions.js").FlagDefinition<("all" | "bundles" | "runtime")[] | undefined>;
19
19
  readonly resources: import("./framework/definitions.js").FlagDefinition<boolean>;
20
20
  }, undefined>;
21
- "cloud campaign create": import("./framework/command.js").CommandDefinition<{
22
- readonly url: import("./framework/definitions.js").FlagDefinition<string | undefined>;
23
- readonly org: import("./framework/definitions.js").FlagDefinition<string | undefined>;
24
- readonly project: import("./framework/definitions.js").FlagDefinition<string | undefined>;
25
- readonly "request-key": import("./framework/definitions.js").FlagDefinition<string | undefined>;
26
- readonly focus: import("./framework/definitions.js").FlagDefinition<string | undefined>;
27
- readonly "target-bugs": import("./framework/definitions.js").FlagDefinition<number | undefined>;
28
- }, undefined>;
29
- "cloud policy preview": import("./framework/command.js").CommandDefinition<{
30
- readonly url: import("./framework/definitions.js").FlagDefinition<string | undefined>;
31
- readonly org: import("./framework/definitions.js").FlagDefinition<string | undefined>;
32
- readonly file: import("./framework/definitions.js").FlagDefinition<string>;
33
- }, {
34
- readonly policy: import("./framework/definitions.js").ArgumentDefinition<string>;
35
- }>;
36
- "cloud download": import("./framework/command.js").CommandDefinition<{
37
- readonly url: import("./framework/definitions.js").FlagDefinition<string | undefined>;
38
- readonly receipt: import("./framework/definitions.js").FlagDefinition<string>;
39
- readonly path: import("./framework/definitions.js").FlagDefinition<string>;
40
- readonly output: import("./framework/definitions.js").FlagDefinition<string>;
41
- }, undefined>;
42
- "cloud list": import("./framework/command.js").CommandDefinition<{
43
- readonly url: import("./framework/definitions.js").FlagDefinition<string | undefined>;
44
- readonly org: import("./framework/definitions.js").FlagDefinition<string | undefined>;
45
- readonly project: import("./framework/definitions.js").FlagDefinition<string | undefined>;
46
- readonly limit: import("./framework/definitions.js").FlagDefinition<number | undefined>;
47
- readonly cursor: import("./framework/definitions.js").FlagDefinition<string | undefined>;
48
- readonly receipt: import("./framework/definitions.js").FlagDefinition<string | undefined>;
49
- readonly case: import("./framework/definitions.js").FlagDefinition<string | undefined>;
50
- readonly path: import("./framework/definitions.js").FlagDefinition<string | undefined>;
51
- readonly decision: import("./framework/definitions.js").FlagDefinition<"hold" | "ineligible" | "ship" | undefined>;
52
- readonly pr: import("./framework/definitions.js").FlagDefinition<number | undefined>;
53
- readonly sort: import("./framework/definitions.js").FlagDefinition<"accepted_at" | undefined>;
54
- readonly direction: import("./framework/definitions.js").FlagDefinition<"asc" | "desc" | undefined>;
55
- readonly flaky: import("./framework/definitions.js").FlagDefinition<boolean | undefined>;
56
- readonly page: import("./framework/definitions.js").FlagDefinition<number | undefined>;
57
- readonly branch: import("./framework/definitions.js").FlagDefinition<string | undefined>;
58
- readonly campaign: import("./framework/definitions.js").FlagDefinition<string | undefined>;
59
- readonly "root-cause-key": import("./framework/definitions.js").FlagDefinition<string | undefined>;
60
- readonly "issue-reference": import("./framework/definitions.js").FlagDefinition<string | undefined>;
61
- readonly view: import("./framework/definitions.js").FlagDefinition<"all" | "counted" | "secondary" | undefined>;
62
- readonly order: import("./framework/definitions.js").FlagDefinition<"created" | "verified" | undefined>;
63
- }, {
64
- readonly resource: import("./framework/definitions.js").ArgumentDefinition<string>;
65
- }>;
66
- "cloud show": import("./framework/command.js").CommandDefinition<{
67
- readonly url: import("./framework/definitions.js").FlagDefinition<string | undefined>;
68
- readonly org: import("./framework/definitions.js").FlagDefinition<string | undefined>;
69
- readonly project: import("./framework/definitions.js").FlagDefinition<string | undefined>;
70
- readonly campaign: import("./framework/definitions.js").FlagDefinition<string | undefined>;
71
- }, {
72
- readonly resource: import("./framework/definitions.js").ArgumentDefinition<"account" | "campaign" | "case" | "connection" | "finding" | "organisation" | "performance" | "policy" | "project" | "project-policy" | "receipt" | "run" | "test-file">;
73
- readonly id: import("./framework/definitions.js").ArgumentDefinition<string | undefined>;
74
- }>;
75
- "cloud project create": import("./framework/command.js").CommandDefinition<{
76
- readonly url: import("./framework/definitions.js").FlagDefinition<string | undefined>;
77
- readonly org: import("./framework/definitions.js").FlagDefinition<string | undefined>;
78
- readonly name: import("./framework/definitions.js").FlagDefinition<string | undefined>;
79
- readonly repository: import("./framework/definitions.js").FlagDefinition<string | undefined>;
80
- readonly provider: import("./framework/definitions.js").FlagDefinition<"gitea" | "github" | undefined>;
81
- readonly host: import("./framework/definitions.js").FlagDefinition<string | undefined>;
82
- readonly "publish-decisions": import("./framework/definitions.js").FlagDefinition<boolean>;
83
- readonly "request-key": import("./framework/definitions.js").FlagDefinition<string | undefined>;
84
- readonly browser: import("./framework/definitions.js").FlagDefinition<boolean>;
85
- }, undefined>;
86
- "cloud login": import("./framework/command.js").CommandDefinition<{
87
- readonly url: import("./framework/definitions.js").FlagDefinition<string | undefined>;
88
- readonly browser: import("./framework/definitions.js").FlagDefinition<boolean>;
89
- readonly force: import("./framework/definitions.js").FlagDefinition<boolean | undefined>;
90
- }, undefined>;
91
- "cloud session revoke": import("./framework/command.js").CommandDefinition<{
92
- readonly url: import("./framework/definitions.js").FlagDefinition<string | undefined>;
93
- }, {
94
- readonly id: import("./framework/definitions.js").ArgumentDefinition<string>;
95
- }>;
96
- "cloud status": import("./framework/command.js").CommandDefinition<{
97
- readonly url: import("./framework/definitions.js").FlagDefinition<string | undefined>;
98
- }, undefined>;
99
- "cloud logout": import("./framework/command.js").CommandDefinition<{
100
- readonly url: import("./framework/definitions.js").FlagDefinition<string | undefined>;
101
- }, undefined>;
102
- "cloud apply": import("./framework/command.js").CommandDefinition<{
103
- readonly browser: import("./framework/definitions.js").FlagDefinition<boolean>;
104
- readonly "secret-output": import("./framework/definitions.js").FlagDefinition<string | undefined>;
105
- readonly campaign: import("./framework/definitions.js").FlagDefinition<string | undefined>;
106
- readonly dir: import("./framework/definitions.js").FlagDefinition<string | undefined>;
107
- readonly file: import("./framework/definitions.js").FlagDefinition<string>;
108
- readonly project: import("./framework/definitions.js").FlagDefinition<string | undefined>;
109
- readonly url: import("./framework/definitions.js").FlagDefinition<string | undefined>;
110
- }, undefined>;
111
- "cloud migrate plan": import("./framework/command.js").CommandDefinition<{
112
- readonly source: import("./framework/definitions.js").FlagDefinition<string>;
113
- readonly registry: import("./framework/definitions.js").FlagDefinition<string>;
114
- readonly regressions: import("./framework/definitions.js").FlagDefinition<string>;
115
- readonly mapping: import("./framework/definitions.js").FlagDefinition<string>;
116
- readonly output: import("./framework/definitions.js").FlagDefinition<string>;
117
- readonly url: import("./framework/definitions.js").FlagDefinition<string>;
118
- readonly project: import("./framework/definitions.js").FlagDefinition<string>;
119
- readonly campaign: import("./framework/definitions.js").FlagDefinition<string>;
120
- }, undefined>;
121
- "cloud migrate apply": import("./framework/command.js").CommandDefinition<{
122
- readonly plan: import("./framework/definitions.js").FlagDefinition<string>;
123
- }, undefined>;
124
- "cloud migrate reconcile": import("./framework/command.js").CommandDefinition<{
125
- readonly plan: import("./framework/definitions.js").FlagDefinition<string>;
126
- }, undefined>;
127
- "cloud publish": import("./framework/command.js").CommandDefinition<{
128
- readonly campaign: import("./framework/definitions.js").FlagDefinition<string | undefined>;
129
- readonly dir: import("./framework/definitions.js").FlagDefinition<string | undefined>;
130
- readonly project: import("./framework/definitions.js").FlagDefinition<string | undefined>;
131
- readonly run: import("./framework/definitions.js").FlagDefinition<string | undefined>;
132
- readonly url: import("./framework/definitions.js").FlagDefinition<string | undefined>;
133
- }, undefined>;
134
- "cloud snapshot": import("./framework/command.js").CommandDefinition<{
135
- readonly campaign: import("./framework/definitions.js").FlagDefinition<string | undefined>;
136
- readonly finding: import("./framework/definitions.js").FlagDefinition<string | undefined>;
137
- readonly findings: import("./framework/definitions.js").FlagDefinition<boolean | undefined>;
138
- readonly receipts: import("./framework/definitions.js").FlagDefinition<boolean | undefined>;
139
- readonly history: import("./framework/definitions.js").FlagDefinition<boolean | undefined>;
140
- readonly "root-cause-key": import("./framework/definitions.js").FlagDefinition<string | undefined>;
141
- readonly "issue-reference": import("./framework/definitions.js").FlagDefinition<string | undefined>;
142
- readonly view: import("./framework/definitions.js").FlagDefinition<"all" | "counted" | "secondary" | undefined>;
143
- readonly limit: import("./framework/definitions.js").FlagDefinition<number | undefined>;
144
- readonly cursor: import("./framework/definitions.js").FlagDefinition<string | undefined>;
145
- readonly project: import("./framework/definitions.js").FlagDefinition<string | undefined>;
146
- readonly url: import("./framework/definitions.js").FlagDefinition<string | undefined>;
147
- }, undefined>;
148
21
  "db schema refresh": import("./framework/command.js").CommandDefinition<{
149
22
  dir: import("./framework/definitions.js").FlagDefinition<string | undefined>;
150
23
  service: import("./framework/definitions.js").FlagDefinition<string | undefined>;
@@ -1 +1 @@
1
- import{cloudListResources as o,cloudShowResources as n}from"../app/cloud/control-resources.js";import{cloudApplyHelp as a}from"./cloud-apply-help.js";import{Args as r,Flags as e}from"./framework/definitions.js";import{defineCommand as t}from"./framework/command.js";import{sharedFlags as i,runFlags as l}from"./command-flags.js";import{publicTestTypeList as c,publicTestTypeListText as s}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:r.string({description:"Optional test file selector",required:!1})},flags:{...i,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})}}),cleanup:t({summary:"Clean stale local testkit state",enableJsonFlag:!0,flags:{...i,"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})}}),"cloud campaign create":t({summary:"Create a campaign without starting tests or investigators",enableJsonFlag:!0,flags:{url:e.string({description:"Cloud origin"}),org:e.string({description:"Organisation selector"}),project:e.string({description:"Project ID, slug, or name"}),"request-key":e.string({description:"Reuse a key to retry the same creation"}),focus:e.string({description:"Campaign focus"}),"target-bugs":e.integer({description:"Target bug count"})}}),"cloud policy preview":t({summary:"Evaluate draft policy rules against a receipt without saving",enableJsonFlag:!0,args:{policy:r.string({required:!0,description:"Policy ID"})},flags:{url:e.string({description:"Cloud origin"}),org:e.string({description:"Organisation selector"}),file:e.string({required:!0,description:"JSON file with receiptId and rules"})}}),"cloud download":t({summary:"Download and verify a receipt artifact",enableJsonFlag:!0,flags:{url:e.string({description:"Cloud origin"}),receipt:e.string({required:!0,description:"Receipt ID"}),path:e.string({required:!0,description:"Exact manifest artifact path"}),output:e.string({required:!0,description:"New destination file"})}}),"cloud list":t({summary:"List Cloud resources with their identifiers and pagination",enableJsonFlag:!0,args:{resource:r.string({required:!0,options:Object.keys(o)})},flags:{url:e.string({description:"Cloud origin"}),org:e.string({description:"Organisation ID, slug, or unambiguous name"}),project:e.string({description:"Project ID, slug, or unambiguous name"}),limit:e.integer({description:"Page size"}),cursor:e.string({description:"Opaque nextCursor from the previous page"}),receipt:e.string({description:"Receipt for artifact metadata"}),case:e.string({description:"Case ID for history"}),path:e.string({description:"Exact test file path for history"}),decision:e.string({description:"Run decision",options:["ship","hold","ineligible"]}),pr:e.integer({description:"Pull request number"}),sort:e.string({description:"Run sort",options:["accepted_at"]}),direction:e.string({description:"Run order",options:["asc","desc"]}),flaky:e.boolean({description:"Only flaky tests"}),page:e.integer({description:"Run page number"}),branch:e.string({description:"Exact run branch"}),campaign:e.string({description:"Limit findings to a campaign"}),"root-cause-key":e.string({description:"Exact finding root cause key"}),"issue-reference":e.string({description:"Exact finding issue URL"}),view:e.string({description:"Finding view",options:["all","counted","secondary"]}),order:e.string({description:"Finding order",options:["created","verified"]})}}),"cloud show":t({summary:"Inspect a Cloud resource, result, or pending connection",enableJsonFlag:!0,args:{resource:r.string({required:!0,options:n}),id:r.string({description:"Resource ID or test file path"})},flags:{url:e.string({description:"Cloud origin"}),org:e.string({description:"Organisation ID, slug, or name"}),project:e.string({description:"Project ID, slug, or name"}),campaign:e.string({description:"Campaign for a finding"})}}),"cloud project create":t({summary:"Connect a repository to Cloud and wait for any provider consent",enableJsonFlag:!0,flags:{url:e.string({description:"Cloud origin"}),org:e.string({description:"Organisation ID, slug, or name"}),name:e.string({description:"Project name; defaults to repository name"}),repository:e.string({description:"owner/repo or remote URL; defaults to Git origin"}),provider:e.string({description:"Repository provider",options:["github","gitea"]}),host:e.string({description:"Repository host"}),"publish-decisions":e.boolean({description:"Explicitly enable provider decision publishing",default:!1}),"request-key":e.string({description:"Reuse the same key to resume an uncertain creation"}),browser:e.boolean({description:"Open provider consent in the browser",allowNo:!0,default:!0})}}),"cloud login":t({summary:"Connect your Cloud account through browser approval",enableJsonFlag:!0,flags:{url:e.string({description:"Testkit Cloud origin"}),browser:e.boolean({description:"Open the approval link in your browser",allowNo:!0,default:!0}),force:e.boolean({description:"Start a new approval to switch accounts or replace a session"})}}),"cloud session revoke":t({summary:"Revoke one of your Cloud CLI sessions",enableJsonFlag:!0,args:{id:r.string({required:!0,description:"Session ID from cloud list sessions"})},flags:{url:e.string({description:"Testkit Cloud origin"})}}),"cloud status":t({summary:"Show the Cloud origin, credential source, and signed-in account",enableJsonFlag:!0,flags:{url:e.string({description:"Testkit Cloud origin"})}}),"cloud logout":t({summary:"Revoke and remove the saved Cloud account session",enableJsonFlag:!0,flags:{url:e.string({description:"Testkit Cloud origin"})}}),"cloud apply":t({summary:"Apply an idempotent, file-based mutation to Testkit Cloud",description:a,enableJsonFlag:!0,flags:{browser:e.boolean({description:"Open any required provider consent",allowNo:!0,default:!0}),"secret-output":e.string({description:"New private file for an explicitly created credential"}),campaign:e.string({description:"Apply a report to this campaign"}),dir:e.string({description:"Product directory containing optional local verification config"}),file:e.string({description:"JSON command envelope to apply",required:!0}),project:e.string({description:"Testkit Cloud project ID"}),url:e.string({description:"Testkit Cloud origin"})}}),"cloud migrate plan":t({summary:"Plan a legacy campaign migration without remote writes",enableJsonFlag:!0,flags:{source:e.string({description:"Legacy source root",required:!0}),registry:e.string({description:"Campaign registry path relative to source root",required:!0}),regressions:e.string({description:"Regression registry path relative to source root",required:!0}),mapping:e.string({description:"Mapping file, absolute or relative to source root",required:!0}),output:e.string({description:"New private output directory outside source root",required:!0}),url:e.string({description:"Testkit Cloud origin",required:!0}),project:e.string({description:"Testkit Cloud project ID",required:!0}),campaign:e.string({description:"Existing campaign ID",required:!0})}}),"cloud migrate apply":t({summary:"Apply or resume a saved legacy campaign migration",enableJsonFlag:!0,flags:{plan:e.string({description:"Saved migration plan path",required:!0})}}),"cloud migrate reconcile":t({summary:"Read back and reconcile a saved legacy campaign migration",enableJsonFlag:!0,flags:{plan:e.string({description:"Saved migration plan path",required:!0})}}),"cloud publish":t({summary:"Publish one verified immutable run bundle to Testkit Cloud",enableJsonFlag:!0,flags:{campaign:e.string({description:"Associate receipt with this campaign; requires --run"}),dir:e.string({description:"Explicit product directory"}),project:e.string({description:"Testkit Cloud project ID"}),run:e.string({description:"Immutable run ID; defaults to latest"}),url:e.string({description:"Testkit Cloud origin"})}}),"cloud snapshot":t({summary:"Read the current versioned Testkit Cloud project state",enableJsonFlag:!0,flags:{campaign:e.string({description:"Campaign ID"}),finding:e.string({description:"Finding detail within the campaign"}),findings:e.boolean({description:"Read project-wide findings"}),receipts:e.boolean({description:"Read campaign receipt history"}),history:e.boolean({description:"Read accepted campaign changes"}),"root-cause-key":e.string({description:"Exact finding root-cause key"}),"issue-reference":e.string({description:"Exact external issue URL"}),view:e.string({description:"Finding view",options:["all","counted","secondary"]}),limit:e.integer({description:"Page size, 1\u2013100; history maximum 20"}),cursor:e.string({description:"Opaque nextCursor from the preceding page"}),project:e.string({description:"Testkit Cloud project ID"}),url:e.string({description:"Testkit Cloud origin"})}}),"db schema refresh":t({summary:"Refresh a source database schema cache",enableJsonFlag:!0,flags:i}),"db schema verify":t({summary:"Verify local schema replay against the source database schema",enableJsonFlag:!0,flags:{...i,"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:i}),discover:t({summary:"Discover managed tests and report their metadata",enableJsonFlag:!0,flags:{...i,type:e.string({char:"t",multiple:!0,description:`Filter by suite type(s): ${s({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"})}}),"run status":t({summary:"Show active Testkit run progress",enableJsonFlag:!0,flags:{...i,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:{...i,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:{type:r.string({description:`Optional suite type shortcut: ${s({includeAll:!0})}`,required:!1,options:c({includeAll:!0})})},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:i}),stop:t({summary:"Stop all Testkit-owned runs",enableJsonFlag:!0,flags:{...i,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"})}}),workspace:t({summary:"Inspect Testkit workstation data for a product",enableJsonFlag:!0,flags:{...i,"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 r,Flags as e}from"./framework/definitions.js";import{defineCommand as t}from"./framework/command.js";import{sharedFlags as s,runFlags as i}from"./command-flags.js";import{publicTestTypeList as o,publicTestTypeListText as a}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:r.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})}}),cleanup:t({summary:"Clean stale local testkit state",enableJsonFlag:!0,flags:{...s,"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:s}),"db schema verify":t({summary:"Verify local schema replay against the source database schema",enableJsonFlag:!0,flags:{...s,"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:s}),discover:t({summary:"Discover managed tests and report their metadata",enableJsonFlag:!0,flags:{...s,type:e.string({char:"t",multiple:!0,description:`Filter by suite type(s): ${a({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"})}}),"run status":t({summary:"Show active Testkit run progress",enableJsonFlag:!0,flags:{...s,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:{...s,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:{type:r.string({description:`Optional suite type shortcut: ${a({includeAll:!0})}`,required:!1,options:o({includeAll:!0})})},flags:i}),"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:s}),stop:t({summary:"Stop all Testkit-owned runs",enableJsonFlag:!0,flags:{...s,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"})}}),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,2 +1,3 @@
1
1
  import type { CommandRegistration } from "./framework/command.js";
2
2
  export declare const commandRegistry: readonly CommandRegistration[];
3
+ export declare const removedCommands: ReadonlyMap<string, string>;
@@ -1 +1 @@
1
- import{commandCatalog as i}from"./command-catalog.js";export const commandRegistry=Object.freeze([o("artifacts",()=>import("./commands/artifacts.js")),o("cleanup",()=>import("./commands/cleanup.js")),o("cloud apply",()=>import("./commands/cloud/apply.js")),o("cloud campaign create",()=>import("./commands/cloud/campaign/create.js")),o("cloud policy preview",()=>import("./commands/cloud/policy/preview.js")),o("cloud download",()=>import("./commands/cloud/download.js")),o("cloud list",()=>import("./commands/cloud/list.js")),o("cloud show",()=>import("./commands/cloud/show.js")),o("cloud project create",()=>import("./commands/cloud/project/create.js")),o("cloud login",()=>import("./commands/cloud/login.js")),o("cloud logout",()=>import("./commands/cloud/logout.js")),o("cloud session revoke",()=>import("./commands/cloud/session/revoke.js")),o("cloud status",()=>import("./commands/cloud/status.js")),o("cloud migrate plan",()=>import("./commands/cloud/migrate/plan.js")),o("cloud migrate apply",()=>import("./commands/cloud/migrate/apply.js")),o("cloud migrate reconcile",()=>import("./commands/cloud/migrate/reconcile.js")),o("cloud publish",()=>import("./commands/cloud/publish.js")),o("cloud snapshot",()=>import("./commands/cloud/snapshot.js")),o("db schema refresh",()=>import("./commands/db/schema/refresh.js")),o("db schema verify",()=>import("./commands/db/schema/verify.js")),o("destroy",()=>import("./commands/destroy.js")),o("discover",()=>import("./commands/discover.js")),o("doctor",()=>import("./commands/doctor.js")),o("guard unsafe-entrypoint",()=>import("./commands/guard/unsafe-entrypoint.js")),o("lint",()=>import("./commands/lint.js")),o("run status",()=>import("./commands/run/status.js")),o("run stop",()=>import("./commands/run/stop.js")),o("run",()=>import("./commands/run.js")),o("setup playwright",()=>import("./commands/setup/playwright.js")),o("status",()=>import("./commands/status.js")),o("stop",()=>import("./commands/stop.js")),o("typecheck",()=>import("./commands/typecheck.js")),o("workspace",()=>import("./commands/workspace.js"))]);function o(t,r){return Object.freeze({...i[t],id:t.split(" "),load:r})}
1
+ import{commandCatalog as e}from"./command-catalog.js";export const commandRegistry=Object.freeze([t("artifacts",()=>import("./commands/artifacts.js")),t("cleanup",()=>import("./commands/cleanup.js")),t("db schema refresh",()=>import("./commands/db/schema/refresh.js")),t("db schema verify",()=>import("./commands/db/schema/verify.js")),t("destroy",()=>import("./commands/destroy.js")),t("discover",()=>import("./commands/discover.js")),t("doctor",()=>import("./commands/doctor.js")),t("guard unsafe-entrypoint",()=>import("./commands/guard/unsafe-entrypoint.js")),t("lint",()=>import("./commands/lint.js")),t("run status",()=>import("./commands/run/status.js")),t("run stop",()=>import("./commands/run/stop.js")),t("run",()=>import("./commands/run.js")),t("setup playwright",()=>import("./commands/setup/playwright.js")),t("status",()=>import("./commands/status.js")),t("stop",()=>import("./commands/stop.js")),t("typecheck",()=>import("./commands/typecheck.js")),t("workspace",()=>import("./commands/workspace.js"))]),removedCommands=new Map([["cloud","The testkit cloud commands were removed in Testkit 0.4.0. Testkit no longer publishes runs to Testkit Cloud."]]);function t(r,o){return Object.freeze({...e[r],id:r.split(" "),load:o})}
@@ -1 +1 @@
1
- import{commandRegistry as c}from"./command-registry.js";export function normalizeCliArgs(n){if(n[0]==="help")return f(n);if(u(n))return[...n];const t=new Set(c.map(s=>s.id[0])),l=new Set(c.flatMap(s=>Object.entries(s.flags??{}).flatMap(([i,r])=>r.kind==="boolean"?[]:[`--${i}`,...r.char?[`-${r.char}`]:[]]))),o=m(n,l),e=o.at(0)??null;return e!==null&&t.has(e.value)?d(n,o):["run",...n]}function u(n){const t=n[0];return t==="--help"||t==="-h"||t==="--version"||t==="-v"}function f(n){const t=n[1];return t?[t,...n.slice(2),"--help"]:["--help"]}function m(n,t){const l=[];for(let o=0;o<n.length;o+=1){const e=n[o];if(e==="--")break;if(!e.startsWith("-")){l.push({index:o,value:e});continue}const s=e.indexOf("="),i=s<0?e:e.slice(0,s);s<0&&t.has(i)&&(o+=1)}return l}function d(n,t){if(!t.at(0))return[...n];const o=[...c].sort((i,r)=>r.id.length-i.id.length).find(i=>i.id.every((r,a)=>t[a]?.value===r)),e=t.slice(0,o?.id.length??1),s=new Set(e.map(i=>i.index));return[...e.map(i=>i.value),...n.filter((i,r)=>!s.has(r))]}
1
+ import{commandRegistry as c,removedCommands as u}from"./command-registry.js";export function normalizeCliArgs(n){if(n[0]==="help")return m(n);if(f(n))return[...n];const e=new Set([...c.map(s=>s.id[0]),...u.keys()]),l=new Set(c.flatMap(s=>Object.entries(s.flags??{}).flatMap(([i,r])=>r.kind==="boolean"?[]:[`--${i}`,...r.char?[`-${r.char}`]:[]]))),o=d(n,l),t=o.at(0)??null;return t!==null&&e.has(t.value)?h(n,o):["run",...n]}function f(n){const e=n[0];return e==="--help"||e==="-h"||e==="--version"||e==="-v"}function m(n){const e=n[1];return e?[e,...n.slice(2),"--help"]:["--help"]}function d(n,e){const l=[];for(let o=0;o<n.length;o+=1){const t=n[o];if(t==="--")break;if(!t.startsWith("-")){l.push({index:o,value:t});continue}const s=t.indexOf("="),i=s<0?t:t.slice(0,s);s<0&&e.has(i)&&(o+=1)}return l}function h(n,e){if(!e.at(0))return[...n];const o=[...c].sort((i,r)=>r.id.length-i.id.length).find(i=>i.id.every((r,a)=>e[a]?.value===r)),t=e.slice(0,o?.id.length??1),s=new Set(t.map(i=>i.index));return[...t.map(i=>i.value),...n.filter((i,r)=>!s.has(r))]}
@@ -9,5 +9,6 @@ export interface ExecuteCliOptions {
9
9
  readonly beforeAction?: (id: readonly string[]) => Promise<void>;
10
10
  readonly identity: CliIdentity;
11
11
  readonly registrations: readonly CommandRegistration[];
12
+ readonly removedCommands?: ReadonlyMap<string, string>;
12
13
  }
13
14
  export declare function executeCliArguments(argv: readonly string[], options: Readonly<ExecuteCliOptions>): Promise<void>;
@@ -1,6 +1,6 @@
1
- import{serializeCliError as g,writeStandardOutput as m}from"../json-errors.js";import{CliUsageError as l,commandExitCode as p,errorMessage as w}from"./errors.js";import{renderCommandHelp as h,renderRootHelp as C,renderTopicHelp as x,renderVersion as O}from"./help.js";import{parseCommandArguments as S}from"./parser.js";export async function executeCliArguments(t,n){const r=H;if(t.length===1&&o(t[0])){await r.writeStandardOutput(C(n.identity,n.registrations));return}if(t.length===1&&y(t[0])){await r.writeStandardOutput(O(n.identity));return}let e;try{e=E(t,n.registrations)}catch(a){const s=F(t,n.registrations);if(s){await r.writeStandardOutput(x(s,n.registrations));return}await c(a,!1,r);return}if(u(e.argv,o)){await r.writeStandardOutput(h(e.registration,n.registrations));return}const i=e.registration.enableJsonFlag===!0&&u(e.argv,a=>a==="--json");try{const a=S(e.argv,e.registration.flags,e.registration.args,e.registration.enableJsonFlag===!0);await n.beforeAction?.(e.registration.id);const d=await(await e.registration.load()).default({...a,json:i,log(f=""){r.writeStandardOutput(`${f}
2
- `)},setExitCode:r.setExitCode});i&&d!==void 0&&await r.writeStandardOutput(`${JSON.stringify(d,null,2)}
3
- `)}catch(a){await c(a,i,r)}}function E(t,n){const e=n.filter(i=>i.id.every((a,s)=>t[s]===a)).sort((i,a)=>a.id.length-i.id.length).at(0);if(!e){const i=t.filter(a=>!a.startsWith("-")).at(0);throw new l(i?`Unknown command: ${i}`:"A Testkit command is required")}return{registration:e,argv:t.slice(e.id.length)}}async function c(t,n,r){const e=p(t);if(n){await r.writeStandardOutput(`${JSON.stringify({error:g(t)},null,2)}
4
- `),r.setExitCode(e);return}r.setExitCode(e);const i=t instanceof l?`
5
- Run "testkit --help" to see available commands and options.`:"";r.writeStandardError(`Error: ${w(t)}${i}
6
- `)}function o(t){return t==="--help"||t==="-h"}function y(t){return t==="--version"||t==="-v"}function F(t,n){const r=t.findIndex(o);if(r<1)return null;const e=t.slice(0,r);return n.some(i=>i.id.length>e.length&&e.every((a,s)=>i.id[s]===a))?e:null}function u(t,n){const r=t.indexOf("--");return(r<0?t:t.slice(0,r)).some(n)}const H={setExitCode(t){process.exitCode=t},writeStandardError(t){process.stderr.write(t)},writeStandardOutput:m};
1
+ import{serializeCliError as g,writeStandardOutput as m}from"../json-errors.js";import{CliUsageError as d,commandExitCode as w,errorMessage as p}from"./errors.js";import{renderCommandHelp as h,renderRootHelp as C,renderTopicHelp as x,renderVersion as O}from"./help.js";import{parseCommandArguments as S}from"./parser.js";export async function executeCliArguments(t,i){const e=H;if(t.length===1&&l(t[0])){await e.writeStandardOutput(C(i.identity,i.registrations));return}if(t.length===1&&y(t[0])){await e.writeStandardOutput(O(i.identity));return}let r;try{r=E(t,i)}catch(n){const s=F(t,i.registrations);if(s){await e.writeStandardOutput(x(s,i.registrations));return}await c(n,!1,e);return}if(u(r.argv,l)){await e.writeStandardOutput(h(r.registration,i.registrations));return}const a=r.registration.enableJsonFlag===!0&&u(r.argv,n=>n==="--json");try{const n=S(r.argv,r.registration.flags,r.registration.args,r.registration.enableJsonFlag===!0);await i.beforeAction?.(r.registration.id);const o=await(await r.registration.load()).default({...n,json:a,log(f=""){e.writeStandardOutput(`${f}
2
+ `)},setExitCode:e.setExitCode});a&&o!==void 0&&await e.writeStandardOutput(`${JSON.stringify(o,null,2)}
3
+ `)}catch(n){await c(n,a,e)}}function E(t,{registrations:i,removedCommands:e}){const a=i.filter(n=>n.id.every((s,o)=>t[o]===s)).sort((n,s)=>s.id.length-n.id.length).at(0);if(!a){const n=t.filter(s=>!s.startsWith("-")).at(0);throw n?new d(e?.get(n)??`Unknown command: ${n}`):new d("A Testkit command is required")}return{registration:a,argv:t.slice(a.id.length)}}async function c(t,i,e){const r=w(t);if(i){await e.writeStandardOutput(`${JSON.stringify({error:g(t)},null,2)}
4
+ `),e.setExitCode(r);return}e.setExitCode(r);const a=t instanceof d?`
5
+ Run "testkit --help" to see available commands and options.`:"";e.writeStandardError(`Error: ${p(t)}${a}
6
+ `)}function l(t){return t==="--help"||t==="-h"}function y(t){return t==="--version"||t==="-v"}function F(t,i){const e=t.findIndex(l);if(e<1)return null;const r=t.slice(0,e);return i.some(a=>a.id.length>r.length&&r.every((n,s)=>a.id[s]===n))?r:null}function u(t,i){const e=t.indexOf("--");return(e<0?t:t.slice(0,e)).some(i)}const H={setExitCode(t){process.exitCode=t},writeStandardError(t){process.stderr.write(t)},writeStandardOutput:m};
package/dist/cli/index.js CHANGED
@@ -1 +1 @@
1
- import{testkitIdentity as r}from"../app/package-inspection.js";import{commandRegistry as e}from"./command-registry.js";import{normalizeCliArgs as o}from"./entrypoint.js";import{executeCliArguments as n}from"./framework/index.js";export async function executeCli(t,i={}){await n(t,{...i,identity:r(),registrations:e})}export async function run(t=process.argv){await executeCli(o(t.slice(2)))}
1
+ import{testkitIdentity as r}from"../app/package-inspection.js";import{commandRegistry as e,removedCommands as o}from"./command-registry.js";import{normalizeCliArgs as m}from"./entrypoint.js";import{executeCliArguments as n}from"./framework/index.js";export async function executeCli(t,i={}){await n(t,{...i,identity:r(),registrations:e,removedCommands:o})}export async function run(t=process.argv){await executeCli(m(t.slice(2)))}
@@ -1,7 +1,7 @@
1
- import{inspectPackageVersionDrift as a}from"../app/package-inspection.js";export function checkCliVersionDrift(t,e={}){if(S(t))return null;const r=a({...e,binUrl:e.binUrl||import.meta.url,dir:g(t)});return r?p({...r,args:t}):null}export function emitCliVersionDriftAndExit(t,{json:e=!1}={}){if(!t)return!1;const r=f;return e?r.writeStandardOutput(`${JSON.stringify({error:t},null,2)}
2
- `):(r.writeStandardError(`${t.message}
3
- `),r.writeStandardError(`Current binary: @gfdelarue/testkit ${t.current.version} (${t.current.packageRoot})
4
- `),t.product.version&&t.product.packageRoot&&r.writeStandardError(`Product install: @gfdelarue/testkit ${t.product.version} (${t.product.packageRoot})
5
- `),t.product.declaredSpec&&r.writeStandardError(`Product declaration: ${t.product.declaredSpec}
6
- `),r.writeStandardError(`Run: ${t.hint}
7
- `)),r.setExitCode(1),!0}export function isJsonMode(t=[]){return o(t).includes("--json")}const f=Object.freeze({setExitCode(t){process.exitCode=t},writeStandardError(t){process.stderr.write(t)},writeStandardOutput(t){process.stdout.write(t)}});function p({productDir:t,currentPackageRoot:e,currentVersion:r,productPackageRoot:n,productVersion:i,declaredSpec:s,args:d}){const c=n?"./node_modules/.bin/testkit":"npm exec testkit --",l=`cd ${u(t)} && ${c} ${$(k(d))}`.trim();return{code:"testkit_cli_version_mismatch",message:`Testkit CLI version mismatch for ${t}`,current:{version:r,packageRoot:e},product:{dir:t,version:i,packageRoot:n,declaredSpec:s||null},hint:l}}function S(t=[]){const e=t[0];return!!(!e||e==="--help"||e==="-h"||e==="--version"||e==="-v"||e==="help"||e==="cloud"&&["login","status","session","logout","list","show","project","campaign","policy","download","snapshot"].includes(t[1]??""))}function k(t=[]){const e=[];for(let r=0;r<t.length;r+=1){const n=t[r];if(n==="--"){e.push(...t.slice(r));break}if(n==="--dir"){const i=t.at(r+1);i!==void 0&&i!=="--"&&(r+=1);continue}n.startsWith("--dir=")||e.push(n)}return e}function g(t){const e=o(t);for(let r=0;r<e.length;r+=1){const n=e[r];if(n==="--dir"){const i=e.at(r+1);return i&&(i==="-"||!i.startsWith("-"))?i:null}if(n.startsWith("--dir="))return n.slice(6)||null}return null}function o(t){const e=t.indexOf("--");return e<0?t:t.slice(0,e)}function $(t=[]){return t.map(u).join(" ")}function u(t){const e=t;return/^[A-Za-z0-9_./:=@+-]+$/u.test(e)?e:`'${e.replace(/'/g,"'\\''")}'`}
1
+ import{inspectPackageVersionDrift as a}from"../app/package-inspection.js";export function checkCliVersionDrift(r,t={}){if(S(r))return null;const e=a({...t,binUrl:t.binUrl||import.meta.url,dir:$(r)});return e?p({...e,args:r}):null}export function emitCliVersionDriftAndExit(r,{json:t=!1}={}){if(!r)return!1;const e=f;return t?e.writeStandardOutput(`${JSON.stringify({error:r},null,2)}
2
+ `):(e.writeStandardError(`${r.message}
3
+ `),e.writeStandardError(`Current binary: @gfdelarue/testkit ${r.current.version} (${r.current.packageRoot})
4
+ `),r.product.version&&r.product.packageRoot&&e.writeStandardError(`Product install: @gfdelarue/testkit ${r.product.version} (${r.product.packageRoot})
5
+ `),r.product.declaredSpec&&e.writeStandardError(`Product declaration: ${r.product.declaredSpec}
6
+ `),e.writeStandardError(`Run: ${r.hint}
7
+ `)),e.setExitCode(1),!0}export function isJsonMode(r=[]){return o(r).includes("--json")}const f=Object.freeze({setExitCode(r){process.exitCode=r},writeStandardError(r){process.stderr.write(r)},writeStandardOutput(r){process.stdout.write(r)}});function p({productDir:r,currentPackageRoot:t,currentVersion:e,productPackageRoot:n,productVersion:i,declaredSpec:s,args:d}){const c=n?"./node_modules/.bin/testkit":"npm exec testkit --",l=`cd ${u(r)} && ${c} ${x(k(d))}`.trim();return{code:"testkit_cli_version_mismatch",message:`Testkit CLI version mismatch for ${r}`,current:{version:e,packageRoot:t},product:{dir:r,version:i,packageRoot:n,declaredSpec:s||null},hint:l}}function S(r=[]){const t=r[0];return!t||t==="--help"||t==="-h"||t==="--version"||t==="-v"||t==="help"}function k(r=[]){const t=[];for(let e=0;e<r.length;e+=1){const n=r[e];if(n==="--"){t.push(...r.slice(e));break}if(n==="--dir"){const i=r.at(e+1);i!==void 0&&i!=="--"&&(e+=1);continue}n.startsWith("--dir=")||t.push(n)}return t}function $(r){const t=o(r);for(let e=0;e<t.length;e+=1){const n=t[e];if(n==="--dir"){const i=t.at(e+1);return i&&(i==="-"||!i.startsWith("-"))?i:null}if(n.startsWith("--dir="))return n.slice(6)||null}return null}function o(r){const t=r.indexOf("--");return t<0?r:r.slice(0,t)}function x(r=[]){return r.map(u).join(" ")}function u(r){const t=r;return/^[A-Za-z0-9_./:=@+-]+$/u.test(t)?t:`'${t.replace(/'/g,"'\\''")}'`}
@@ -265,11 +265,6 @@ export interface NodeNextPresetOptions {
265
265
  workers?: number;
266
266
  }
267
267
  export interface TestkitConfig {
268
- cloud?: {
269
- verification?: {
270
- minimumConfirmingRuns?: number;
271
- };
272
- };
273
268
  artifacts?: {
274
269
  publication?: {
275
270
  directory: string;
@@ -7,5 +7,4 @@ export { detectNextApp, type NormalizedRuntimeConfig } from "./runtime.js";
7
7
  export { DEFAULT_RUNTIME_ISOLATION_POLICY, resolveRuntimeIsolationPolicy, type RuntimeIsolationFileMetadata, type RuntimeIsolationPolicy, type RuntimeIsolationResolutionSuite, type RuntimeIsolationResolverConfig, type RuntimeIsolationResolverTestkitConfig, } from "./runtime-isolation.js";
8
8
  export { collectModuleSpecifiers, requireMatchingEngineAuthoringImports, type TestFileMetadata, } from "./file-metadata.js";
9
9
  export type { NormalizedSkipConfig } from "./skip-config.js";
10
- export { minimumConfirmingRuns } from "./cloud.js";
11
10
  export { hasDiscoveryErrors, buildDiscoveryErrorFromDiagnostics } from "./discovery.js";
@@ -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,resolveRuntimeIsolationPolicy}from"./runtime-isolation.js";export{collectModuleSpecifiers,requireMatchingEngineAuthoringImports}from"./file-metadata.js";export{minimumConfirmingRuns}from"./cloud.js";export{hasDiscoveryErrors,buildDiscoveryErrorFromDiagnostics}from"./discovery.js";
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,resolveRuntimeIsolationPolicy}from"./runtime-isolation.js";export{collectModuleSpecifiers,requireMatchingEngineAuthoringImports}from"./file-metadata.js";export{hasDiscoveryErrors,buildDiscoveryErrorFromDiagnostics}from"./discovery.js";
@@ -1,7 +1,7 @@
1
1
  import type { RunArtifactDocument } from "./reporting-types.js";
2
2
  import type { NormalizedArtifactPublicationConfig, NormalizedArtifactRetentionConfig } from "./artifact-config.js";
3
- import { type RunBundleArtifact, type RunBundleCleanup, type RunBundleManifest, type RunBundlePublication, type RunBundleRetentionDecision, type RunBundleSummary, type RunBundleVerification, type VerifiedRunBundleUpload, type SealRunBundleOptions } from "./contracts.js";
4
- export type { RunBundleArchive, RunBundleArtifact, RunBundleCleanup, RunBundleLatestProjection, RunBundleManifest, RunBundleManifestEntry, RunBundlePublication, RunBundleRedaction, RunBundleRetentionDecision, RunBundleStorage, RunBundleSummary, RunBundleVerification, VerifiedRunBundleUpload, SealRunBundleOptions, } from "./contracts.js";
3
+ import { type RunBundleArtifact, type RunBundleCleanup, type RunBundleManifest, type RunBundlePublication, type RunBundleRetentionDecision, type RunBundleSummary, type RunBundleVerification, type SealRunBundleOptions } from "./contracts.js";
4
+ export type { RunBundleArchive, RunBundleArtifact, RunBundleCleanup, RunBundleLatestProjection, RunBundleManifest, RunBundleManifestEntry, RunBundlePublication, RunBundleRedaction, RunBundleRetentionDecision, RunBundleStorage, RunBundleSummary, RunBundleVerification, SealRunBundleOptions, } from "./contracts.js";
5
5
  interface PublicationReceiptInput extends RunBundlePublication {
6
6
  bytesUploaded: number;
7
7
  }
@@ -23,6 +23,5 @@ export declare function listRunBundles(productDir: string, options?: {
23
23
  file?: null | string;
24
24
  }): RunBundleSummary[];
25
25
  export declare function verifyRunBundle(productDir: string, runId: unknown): RunBundleVerification;
26
- export declare function prepareVerifiedRunBundleUpload(productDir: string, requestedRunId?: null | string): VerifiedRunBundleUpload;
27
26
  export declare function publishRunBundle(productDir: string, runId: unknown, publication: NormalizedArtifactPublicationConfig | null, dependencies?: Readonly<RunBundlePublicationDependencies>): RunBundlePublication;
28
27
  export declare function applyRunBundleRetention(productDir: string, currentRunId: unknown, retention?: Partial<NormalizedArtifactRetentionConfig>, dependencies?: Readonly<RunBundleRetentionDependencies>): RunBundleRetentionDecision;
@@ -1 +1 @@
1
- import{recordWork as J}from"../kernel/work-metrics.js";import{optionalString as A}from"../kernel/unknown-values.js";import{readRunProvenance as ue}from"./read-provenance.js";import{parseRunBundleArtifact as le,parseRunBundleManifest as Y,readRunBundleArtifact as fe}from"./read-bundle.js";import{readRunDocument as de}from"./read-run.js";import me from"crypto";import c from"fs";import u from"path";import{redactSensitiveText as G}from"../kernel/redaction.js";import{BUNDLE_SCHEMA_VERSION as Q,OBJECTS_DIRNAME as X,PROVENANCE_SCHEMA_VERSION as pe,RETENTION_DECISION_SCHEMA_VERSION as he,RUNS_DIRNAME as ge}from"./contracts.js";import{bundleRelativePath as P,errorMessage as k,hashFile as z,isObjectRecord as $,normalizePath as Z,normalizeRunId as I,objectRecords as ee,optionalStringArray as D,optionalUnknownArray as ye,readJsonFile as O,requirePublicationPathOutsideResults as M,requireRunId as ve,runIncludesFile as Se,resolveManifestEntryPath as U,resolveRunBundleDirectory as x,resolveRunsRoot as te,resultsRoot as q,sanitizeSegment as Re,sha256 as C,stableJson as W,treeBytes as ne,walkFiles as re,writeBufferAtomic as je,writeJson as N}from"./storage.js";import{resolveCiProvenance as be}from"./ci-provenance.js";const K=["artifacts","logs","setup"],we=/(?:PASSWORD|TOKEN|SECRET|PRIVATE_KEY|AUTH|COOKIE|SESSION|API_KEY)/iu,Ie=["package.json","package-lock.json","npm-shrinkwrap.json","pnpm-lock.yaml","yarn.lock","testkit.config.ts","testkit.config.mts","testkit.config.js","testkit.config.mjs"];export function sealRunBundle(e,n,t={},s={}){const r=n,o=ve(r),i=te(e,!0),a=u.dirname(i),f=u.join(i,o),m=s.cleanupActiveRunResults??De,p=loadVerifiedRunBundleArtifactIfExists(e,o);if(p!==null)return{...p,archive:{...p.archive,cleanup:m(a)}};const d=u.join(i,`.${o}.${String(process.pid)}.${String(Date.now())}.${me.randomBytes(4).toString("hex")}.tmp`);c.mkdirSync(d,{recursive:!0});try{const R=_e(t.env??process.env),g=Ee({productDir:e,resultsDir:a,runId:o,tempDir:d,sensitiveValues:R}),h=Be(r,o,R),y=de(h.artifact,"run artifact");g.redaction.structuredReplacements=h.replacements,g.redaction.replacements+=h.replacements;const v=$e(e,y,o,{...t,redaction:g.redaction}),j={schemaVersion:Q,runId:o,status:"complete",canonicalRunPath:P(o,"run.json"),manifestPath:P(o,"manifest.json"),provenancePath:P(o,"provenance.json"),integrity:"sha256",publication:{configured:!!t.archiveConfig?.publication,status:t.archiveConfig?.publication?"pending":"not_configured"},redaction:g.redaction};y.archive=j,N(u.join(d,"run.json"),y),N(u.join(d,"provenance.json"),v);const b=xe(d),T=C(Buffer.from(W(b))),E={schemaVersion:Q,runId:o,createdAt:new Date().toISOString(),algorithm:"sha256",files:b,storage:{fileCount:b.length,logicalBytes:b.reduce((_,F)=>_+F.bytes,0),objectBytesWritten:g.objectBytesWritten,deduplicatedBytes:g.deduplicatedBytes},redaction:g.redaction,digest:T};return N(u.join(d,"manifest.json"),E),c.renameSync(d,f),{...y,archive:{...j,cleanup:m(a),manifestDigest:E.digest,storage:E.storage}}}catch(R){try{c.rmSync(d,{recursive:!0,force:!0})}catch{}try{Ce(e)}catch{}throw R}}export function loadVerifiedRunBundleArtifactIfExists(e,n){const t=I(n),s=x(e,t);if(!c.existsSync(s))return null;const r=verifyRunBundle(e,t);if(!r.ok)throw new Error(`Immutable run bundle ${t} already exists but failed integrity verification: ${r.errors.join("; ")}`);return loadRunBundleArtifact(e,t)}export function loadRunBundleArtifact(e,n){return se(e,n).artifact}function se(e,n){const t=I(n),s=fe(e,t),r=loadRunBundleManifest(e,t);return{manifest:r,artifact:{...s,archive:{...s.archive,manifestDigest:r.digest,storage:r.storage}}}}export function loadRunBundleManifest(e,n){const t=I(n),s=x(e,t),r=u.join(s,"manifest.json");if(!c.existsSync(r))throw new Error(`No immutable Testkit run manifest found for ${t}`);return Y(O(r),r,t)}export function listRunBundles(e,n={}){return ie(e,n).map(({artifact:t,manifest:s,...r})=>r)}function ie(e,n={}){const t=te(e,!1);if(!c.existsSync(t))return[];const s=c.readdirSync(t,{withFileTypes:!0});return J("artifacts.runDirectoriesRead",1),J("artifacts.runEntriesVisited",s.length),s.filter(o=>o.isDirectory()&&!o.name.startsWith(".")).map(o=>{try{const{artifact:i,manifest:a}=se(e,o.name);return n.file&&!Se(i,n.file)?null:{artifact:i,manifest:a,runId:o.name,status:i.run.status,startedAt:i.run.startedAt,finishedAt:i.run.finishedAt??null,manifestDigest:i.archive.manifestDigest??null,canonicalRunPath:i.archive.canonicalRunPath}}catch{return null}}).filter(o=>o!==null).sort((o,i)=>(i.finishedAt??i.startedAt??"").localeCompare(o.finishedAt??o.startedAt??""))}export function verifyRunBundle(e,n){const t=I(n);try{return V(x(e,t),t)}catch(s){return{ok:!1,runId:t,errors:[k(s)],checkedFiles:0}}}export function prepareVerifiedRunBundleUpload(e,n=null){const t=n??listRunBundles(e).at(0)?.runId??null;if(t===null)throw new Error("No immutable Testkit run bundle is available to publish");const s=verifyRunBundle(e,t);if(!s.ok)throw new Error(`Immutable run bundle ${t} failed integrity verification: ${s.errors.join("; ")}`);const r=loadRunBundleManifest(e,t),o=x(e,t);return{runId:t,manifest:r,files:r.files.map(i=>({...i,absolutePath:U(o,i.path)}))}}export function publishRunBundle(e,n,t,s={}){const r=I(n),o=s.writePublicationReceipt??Pe;if(!t)return o(e,r,{status:"not_configured",required:!1,target:null,bytesUploaded:0});const i=u.resolve(e,t.directory);try{M(e,i)}catch(d){return o(e,r,{status:"failed",required:t.required,target:i,error:k(d),bytesUploaded:0})}const a=Re(u.basename(e)),f=u.join(i,a),m=u.join(f,r),p=u.join(f,`.${r}.${String(process.pid)}.${String(Date.now())}.tmp`);try{const d=x(e,r);if(c.mkdirSync(f,{recursive:!0}),M(e,f),c.existsSync(m)){const h=c.lstatSync(m);if(h.isSymbolicLink()||!h.isDirectory())throw new Error("Publication target must be a regular bundle directory");M(e,m);const y=V(d,r);if(!y.ok)throw new Error(`Source run bundle failed integrity verification: ${y.errors.join("; ")}`);const v=V(m,r);if(!v.ok)throw new Error(`Publication target failed integrity verification: ${v.errors.join("; ")}`);if(y.manifestDigest!==v.manifestDigest)throw new Error("Publication target already exists with a different manifest digest");return o(e,r,{status:"complete",required:t.required,target:m,manifestDigest:y.manifestDigest,reused:!0,bundleBytes:ne(d),bytesUploaded:0})}const R=ne(d);c.cpSync(d,p,{recursive:!0,preserveTimestamps:!0});const g=V(p,r);if(!g.ok)throw new Error(g.errors.join("; "));return c.renameSync(p,m),o(e,r,{status:"complete",required:t.required,target:m,manifestDigest:g.manifestDigest,reused:!1,bundleBytes:R,bytesUploaded:R})}catch(d){return c.rmSync(p,{recursive:!0,force:!0}),o(e,r,{status:"failed",required:t.required,target:m,error:k(d),bytesUploaded:0})}}export function applyRunBundleRetention(e,n,t={},s={}){const r=I(n),o=s.removeObject??ce,i=s.removeRunBundle??Ne,a=t.maxRuns??null,f=t.maxAgeDays??null,m=new Set(t.preserveSuites||[]),p=ie(e),d=new Set([r]);for(const l of p)Oe(l.artifact,m)&&d.add(l.runId);const R=new Set(a?p.slice(0,a).map(l=>l.runId):p.map(l=>l.runId)),g=f==null?null:Date.now()-f*24*60*60*1e3,h=[];for(const l of p){if(d.has(l.runId))continue;const S=g!=null&&Date.parse(l.finishedAt||l.startedAt||"")<g,w=!R.has(l.runId);if(!S&&!w)continue;const B=[];S&&B.push("max_age"),w&&B.push("max_runs"),h.push({path:u.join(q(e),ge,l.runId),reasons:B,runId:l.runId})}const y=new Map(p.map(l=>[l.runId,l.manifest.files.filter(S=>S.path.startsWith("files/")).map(S=>S.sha256)])),v=oe(e),j=[],b=[];for(const l of h){let S=null;try{i(l.path)}catch(B){S=H(B)}const w=!c.existsSync(l.path);!w&&S===null&&(S="Run bundle still exists after removal completed"),w&&j.push({reasons:l.reasons,runId:l.runId}),S!==null&&b.push({...l,error:S,removed:w})}const T=new Set(j.map(l=>l.runId)),E=new Set(p.filter(l=>!T.has(l.runId)).flatMap(l=>y.get(l.runId)??[])),_=ae(v,E,o),F=new Date().toISOString();return Ve({currentRunId:r,decidedAt:F,garbageCollection:_,policy:{maxRuns:a,maxAgeDays:f,preserveSuites:[...m].sort()},protectedRunIds:[...d].sort(),removed:j,removalErrors:b})}function Ee({resultsDir:e,tempDir:n,sensitiveValues:t}){let s=0,r=0,o=0,i=0,a=0;const f=u.join(e,X,"sha256");c.mkdirSync(f,{recursive:!0});for(const m of K){const p=u.join(e,m);if(c.existsSync(p))for(const d of re(p)){const R=u.relative(p,d),g=c.readFileSync(d),h=Te(g,d,t);h.scanned&&(o+=1),h.replacements>0&&(i+=1,a+=h.replacements);const y=C(h.buffer),v=u.join(f,y);c.existsSync(v)?r+=h.buffer.length:(je(v,h.buffer),c.chmodSync(v,292),s+=h.buffer.length);const j=u.join(n,"files",m,R);c.mkdirSync(u.dirname(j),{recursive:!0});try{c.linkSync(v,j)}catch{c.copyFileSync(v,j)}}}return{objectBytesWritten:s,deduplicatedBytes:r,redaction:{policyVersion:1,status:"applied",textualFilesScanned:o,filesRedacted:i,replacements:a,sensitiveEnvironmentKeys:t.map(m=>m.key).sort(),valuesStored:!1}}}function Be(e,n,t){const s=new Map(K.map(o=>[`.testkit/results/${o}/`,`${P(n,`files/${o}`)}/`])),r=L(e,s);return ke(Ae(r),t)}function Ae(e){if(!$(e))return e;const n={...e};if($(n.product)){const t={...n.product};delete t.directory,n.product=t}if($(n.git)){const t={...n.git};delete t.remoteUrl,delete t.repoRoot,delete t.worktreePath,n.git=t}return delete n.host,n}function L(e,n){if(Array.isArray(e))return e.map(s=>L(s,n));if($(e))return Object.fromEntries(Object.entries(e).map(([s,r])=>[s,L(r,n)]));if(typeof e!="string")return e;let t=e;for(const[s,r]of n)t.startsWith(s)&&(t=`${r}${t.slice(s.length)}`);return t}function ke(e,n){let t=0;function s(r){if(Array.isArray(r))return r.map(s);if($(r))return Object.fromEntries(Object.entries(r).map(([i,a])=>[i,s(a)]));if(typeof r!="string")return r;let o=G(r);o!==r&&(t+=1);for(const i of n){if(!o.includes(i.value))continue;const a=o.split(i.value);t+=a.length-1,o=a.join("<redacted>")}return o}return{artifact:s(e),replacements:t}}function $e(e,n,t,s){const r=D(n.run.fileNames),o=[...new Set([...Ie,...r])].map(i=>ze(e,i)).filter(i=>i!==null);return{schemaVersion:pe,runId:t,ci:be(s.env??process.env),source:{git:n.git??null,files:o},runtime:{controller:{testkitVersion:A(n.run.testkitVersion),node:process.versions.node,platform:process.platform,architecture:process.arch},runtimeStats:ye(n.run.runtimeStats)},scenario:{types:D(n.run.types),suiteSelectors:D(n.run.suiteSelectors),fileNames:r,serviceFilter:A(n.run.serviceFilter),planningDigest:C(Buffer.from(W(n.planning??null))),declaredEnvironmentKeys:[...new Set(s.declaredEnvironmentKeys??[])].sort()},redaction:s.redaction}}function xe(e){return re(e).map(n=>({path:Z(u.relative(e,n)),bytes:c.statSync(n).size,sha256:z(n)})).sort((n,t)=>n.path.localeCompare(t.path))}function V(e,n){const t=u.join(e,"manifest.json"),s=Y(O(t),t,n);le(O(u.join(e,"run.json")),u.join(e,"run.json"),n);const r=[];for(const i of s.files){let a;try{a=U(e,i.path)}catch(m){r.push(k(m));continue}if(!c.existsSync(a)){r.push(`missing ${i.path}`);continue}const f=c.lstatSync(a);if(f.isSymbolicLink()){r.push(`symbolic link not allowed ${i.path}`);continue}if(!f.isFile()){r.push(`not a regular file ${i.path}`);continue}f.size!==i.bytes&&r.push(`size mismatch ${i.path}`),z(a)!==i.sha256&&r.push(`checksum mismatch ${i.path}`)}C(Buffer.from(W(s.files)))!==s.digest&&r.push("manifest digest mismatch"),s.storage.fileCount!==s.files.length&&r.push("manifest file count mismatch");for(const i of["run.json","provenance.json"])s.files.some(a=>a.path===i)||r.push(`manifest must include ${i}`);if(r.length===0){const i=U(e,"provenance.json");ue(O(i),i,n)}return{runId:n,checkedFiles:s.files.length,storage:s.storage,ok:r.length===0,errors:r,manifestDigest:s.digest}}function Pe(e,n,t){const s={schemaVersion:1,runId:n,recordedAt:new Date().toISOString(),...t};return N(u.join(q(e),"publications",`${n}.json`),s),s}function Oe(e,n){return n.size===0?!1:ee(e.services).some(t=>ee(t.suites).some(s=>{const r=A(t.name)??"",o=A(s.name)??"",i=A(s.testType)??"";return[o,`${i}:${o}`,`${r}/${i}:${o}`].some(f=>n.has(f))}))}function Ce(e){const n=new Set;for(const t of listRunBundles(e)){const s=loadRunBundleManifest(e,t.runId);for(const r of s.files)r.path.startsWith("files/")&&n.add(r.sha256)}return ae(oe(e),n,ce)}function oe(e){const n=u.join(q(e),X,"sha256");return c.existsSync(n)?c.readdirSync(n,{withFileTypes:!0}).filter(t=>t.isFile()).map(t=>{const s=u.join(n,t.name);return{bytes:c.statSync(s).size,digest:t.name,path:s}}):[]}function ae(e,n,t){const s=[];let r=0,o=0;for(const i of e){if(n.has(i.digest))continue;let a=null;try{t(i.path)}catch(m){a=H(m)}!c.existsSync(i.path)?(r+=1,o+=i.bytes):a===null&&(a="Object still exists after removal completed"),a!==null&&s.push({error:a,path:i.path})}return{failures:s,reclaimedBytes:o,removedObjects:r,status:s.length===0?"complete":"failed"}}function Ne(e){c.rmSync(e,{recursive:!0,force:!0})}function ce(e){c.rmSync(e,{force:!0})}function Ve({currentRunId:e,decidedAt:n,garbageCollection:t,policy:s,protectedRunIds:r,removed:o,removalErrors:i}){const a=[...i.map(f=>`remove ${f.runId}: ${f.error}`),...t.failures.map(f=>`garbage collect ${f.path}: ${f.error}`)];return{currentRunId:e,decidedAt:n,garbageCollection:t,policy:s,protectedRunIds:r,removed:o,removalErrors:i,schemaVersion:he,status:a.length===0?"complete":"failed",...a.length===0?{}:{error:a.join("; ")}}}function Te(e,n,t){if(!Fe(e,n)){for(const i of t)if(e.includes(Buffer.from(i.value)))throw new Error(`Refusing to archive binary file containing sensitive environment value: ${n}`);return{buffer:e,scanned:!1,replacements:0}}const s=e.toString("utf8");let r=G(s),o=r===s?0:1;for(const i of t){if(!r.includes(i.value))continue;const a=r.split(i.value);o+=a.length-1,r=a.join("<redacted>")}return{buffer:Buffer.from(r),scanned:!0,replacements:o}}function _e(e){const n=[];for(const[t,s]of Object.entries(e))we.test(t)&&typeof s=="string"&&s.length>=8&&n.push({key:t,value:s});return n}function Fe(e,n){if(e.includes(0))return!1;const t=u.extname(n).toLowerCase();return![".zip",".gz",".png",".jpg",".jpeg",".webm",".pdf"].includes(t)}function ze(e,n){const t=u.resolve(e,n);return!t.startsWith(`${u.resolve(e)}${u.sep}`)||!c.existsSync(t)||!c.statSync(t).isFile()?null:{path:Z(u.relative(e,t)),bytes:c.statSync(t).size,sha256:z(t)}}function De(e){const n=[];for(const t of[...K.map(s=>u.join(e,s)),u.join(e,"live.json")])try{c.rmSync(t,{recursive:!0,force:!0})}catch(s){n.push(`${t}: ${H(s)}`)}return n.length===0?{status:"complete"}:{error:n.join("; "),status:"failed"}}function H(e){try{return k(e)}catch{return"Unknown post-seal archive operation failure"}}
1
+ import{recordWork as H}from"../kernel/work-metrics.js";import{optionalString as A}from"../kernel/unknown-values.js";import{readRunProvenance as ue}from"./read-provenance.js";import{parseRunBundleArtifact as le,parseRunBundleManifest as J,readRunBundleArtifact as fe}from"./read-bundle.js";import{readRunDocument as de}from"./read-run.js";import me from"crypto";import c from"fs";import u from"path";import{redactSensitiveText as Y}from"../kernel/redaction.js";import{BUNDLE_SCHEMA_VERSION as G,OBJECTS_DIRNAME as Q,PROVENANCE_SCHEMA_VERSION as pe,RETENTION_DECISION_SCHEMA_VERSION as he,RUNS_DIRNAME as ge}from"./contracts.js";import{bundleRelativePath as x,errorMessage as k,hashFile as F,isObjectRecord as $,normalizePath as X,normalizeRunId as w,objectRecords as Z,optionalStringArray as z,optionalUnknownArray as ye,readJsonFile as P,requirePublicationPathOutsideResults as M,requireRunId as ve,runIncludesFile as Se,resolveManifestEntryPath as ee,resolveRunBundleDirectory as O,resolveRunsRoot as te,resultsRoot as U,sanitizeSegment as Re,sha256 as C,stableJson as q,treeBytes as ne,walkFiles as re,writeBufferAtomic as je,writeJson as N}from"./storage.js";import{resolveCiProvenance as be}from"./ci-provenance.js";const W=["artifacts","logs","setup"],Ie=/(?:PASSWORD|TOKEN|SECRET|PRIVATE_KEY|AUTH|COOKIE|SESSION|API_KEY)/iu,we=["package.json","package-lock.json","npm-shrinkwrap.json","pnpm-lock.yaml","yarn.lock","testkit.config.ts","testkit.config.mts","testkit.config.js","testkit.config.mjs"];export function sealRunBundle(e,n,t={},s={}){const r=n,o=ve(r),i=te(e,!0),a=u.dirname(i),f=u.join(i,o),m=s.cleanupActiveRunResults??ze,p=loadVerifiedRunBundleArtifactIfExists(e,o);if(p!==null)return{...p,archive:{...p.archive,cleanup:m(a)}};const d=u.join(i,`.${o}.${String(process.pid)}.${String(Date.now())}.${me.randomBytes(4).toString("hex")}.tmp`);c.mkdirSync(d,{recursive:!0});try{const R=Te(t.env??process.env),g=Ee({productDir:e,resultsDir:a,runId:o,tempDir:d,sensitiveValues:R}),h=Be(r,o,R),y=de(h.artifact,"run artifact");g.redaction.structuredReplacements=h.replacements,g.redaction.replacements+=h.replacements;const v=$e(e,y,o,{...t,redaction:g.redaction}),j={schemaVersion:G,runId:o,status:"complete",canonicalRunPath:x(o,"run.json"),manifestPath:x(o,"manifest.json"),provenancePath:x(o,"provenance.json"),integrity:"sha256",publication:{configured:!!t.archiveConfig?.publication,status:t.archiveConfig?.publication?"pending":"not_configured"},redaction:g.redaction};y.archive=j,N(u.join(d,"run.json"),y),N(u.join(d,"provenance.json"),v);const b=xe(d),D=C(Buffer.from(q(b))),E={schemaVersion:G,runId:o,createdAt:new Date().toISOString(),algorithm:"sha256",files:b,storage:{fileCount:b.length,logicalBytes:b.reduce((T,_)=>T+_.bytes,0),objectBytesWritten:g.objectBytesWritten,deduplicatedBytes:g.deduplicatedBytes},redaction:g.redaction,digest:D};return N(u.join(d,"manifest.json"),E),c.renameSync(d,f),{...y,archive:{...j,cleanup:m(a),manifestDigest:E.digest,storage:E.storage}}}catch(R){try{c.rmSync(d,{recursive:!0,force:!0})}catch{}try{Ce(e)}catch{}throw R}}export function loadVerifiedRunBundleArtifactIfExists(e,n){const t=w(n),s=O(e,t);if(!c.existsSync(s))return null;const r=verifyRunBundle(e,t);if(!r.ok)throw new Error(`Immutable run bundle ${t} already exists but failed integrity verification: ${r.errors.join("; ")}`);return loadRunBundleArtifact(e,t)}export function loadRunBundleArtifact(e,n){return se(e,n).artifact}function se(e,n){const t=w(n),s=fe(e,t),r=loadRunBundleManifest(e,t);return{manifest:r,artifact:{...s,archive:{...s.archive,manifestDigest:r.digest,storage:r.storage}}}}export function loadRunBundleManifest(e,n){const t=w(n),s=O(e,t),r=u.join(s,"manifest.json");if(!c.existsSync(r))throw new Error(`No immutable Testkit run manifest found for ${t}`);return J(P(r),r,t)}export function listRunBundles(e,n={}){return ie(e,n).map(({artifact:t,manifest:s,...r})=>r)}function ie(e,n={}){const t=te(e,!1);if(!c.existsSync(t))return[];const s=c.readdirSync(t,{withFileTypes:!0});return H("artifacts.runDirectoriesRead",1),H("artifacts.runEntriesVisited",s.length),s.filter(o=>o.isDirectory()&&!o.name.startsWith(".")).map(o=>{try{const{artifact:i,manifest:a}=se(e,o.name);return n.file&&!Se(i,n.file)?null:{artifact:i,manifest:a,runId:o.name,status:i.run.status,startedAt:i.run.startedAt,finishedAt:i.run.finishedAt??null,manifestDigest:i.archive.manifestDigest??null,canonicalRunPath:i.archive.canonicalRunPath}}catch{return null}}).filter(o=>o!==null).sort((o,i)=>(i.finishedAt??i.startedAt??"").localeCompare(o.finishedAt??o.startedAt??""))}export function verifyRunBundle(e,n){const t=w(n);try{return V(O(e,t),t)}catch(s){return{ok:!1,runId:t,errors:[k(s)],checkedFiles:0}}}export function publishRunBundle(e,n,t,s={}){const r=w(n),o=s.writePublicationReceipt??Pe;if(!t)return o(e,r,{status:"not_configured",required:!1,target:null,bytesUploaded:0});const i=u.resolve(e,t.directory);try{M(e,i)}catch(d){return o(e,r,{status:"failed",required:t.required,target:i,error:k(d),bytesUploaded:0})}const a=Re(u.basename(e)),f=u.join(i,a),m=u.join(f,r),p=u.join(f,`.${r}.${String(process.pid)}.${String(Date.now())}.tmp`);try{const d=O(e,r);if(c.mkdirSync(f,{recursive:!0}),M(e,f),c.existsSync(m)){const h=c.lstatSync(m);if(h.isSymbolicLink()||!h.isDirectory())throw new Error("Publication target must be a regular bundle directory");M(e,m);const y=V(d,r);if(!y.ok)throw new Error(`Source run bundle failed integrity verification: ${y.errors.join("; ")}`);const v=V(m,r);if(!v.ok)throw new Error(`Publication target failed integrity verification: ${v.errors.join("; ")}`);if(y.manifestDigest!==v.manifestDigest)throw new Error("Publication target already exists with a different manifest digest");return o(e,r,{status:"complete",required:t.required,target:m,manifestDigest:y.manifestDigest,reused:!0,bundleBytes:ne(d),bytesUploaded:0})}const R=ne(d);c.cpSync(d,p,{recursive:!0,preserveTimestamps:!0});const g=V(p,r);if(!g.ok)throw new Error(g.errors.join("; "));return c.renameSync(p,m),o(e,r,{status:"complete",required:t.required,target:m,manifestDigest:g.manifestDigest,reused:!1,bundleBytes:R,bytesUploaded:R})}catch(d){return c.rmSync(p,{recursive:!0,force:!0}),o(e,r,{status:"failed",required:t.required,target:m,error:k(d),bytesUploaded:0})}}export function applyRunBundleRetention(e,n,t={},s={}){const r=w(n),o=s.removeObject??ce,i=s.removeRunBundle??Ne,a=t.maxRuns??null,f=t.maxAgeDays??null,m=new Set(t.preserveSuites||[]),p=ie(e),d=new Set([r]);for(const l of p)Oe(l.artifact,m)&&d.add(l.runId);const R=new Set(a?p.slice(0,a).map(l=>l.runId):p.map(l=>l.runId)),g=f==null?null:Date.now()-f*24*60*60*1e3,h=[];for(const l of p){if(d.has(l.runId))continue;const S=g!=null&&Date.parse(l.finishedAt||l.startedAt||"")<g,I=!R.has(l.runId);if(!S&&!I)continue;const B=[];S&&B.push("max_age"),I&&B.push("max_runs"),h.push({path:u.join(U(e),ge,l.runId),reasons:B,runId:l.runId})}const y=new Map(p.map(l=>[l.runId,l.manifest.files.filter(S=>S.path.startsWith("files/")).map(S=>S.sha256)])),v=oe(e),j=[],b=[];for(const l of h){let S=null;try{i(l.path)}catch(B){S=L(B)}const I=!c.existsSync(l.path);!I&&S===null&&(S="Run bundle still exists after removal completed"),I&&j.push({reasons:l.reasons,runId:l.runId}),S!==null&&b.push({...l,error:S,removed:I})}const D=new Set(j.map(l=>l.runId)),E=new Set(p.filter(l=>!D.has(l.runId)).flatMap(l=>y.get(l.runId)??[])),T=ae(v,E,o),_=new Date().toISOString();return Ve({currentRunId:r,decidedAt:_,garbageCollection:T,policy:{maxRuns:a,maxAgeDays:f,preserveSuites:[...m].sort()},protectedRunIds:[...d].sort(),removed:j,removalErrors:b})}function Ee({resultsDir:e,tempDir:n,sensitiveValues:t}){let s=0,r=0,o=0,i=0,a=0;const f=u.join(e,Q,"sha256");c.mkdirSync(f,{recursive:!0});for(const m of W){const p=u.join(e,m);if(c.existsSync(p))for(const d of re(p)){const R=u.relative(p,d),g=c.readFileSync(d),h=De(g,d,t);h.scanned&&(o+=1),h.replacements>0&&(i+=1,a+=h.replacements);const y=C(h.buffer),v=u.join(f,y);c.existsSync(v)?r+=h.buffer.length:(je(v,h.buffer),c.chmodSync(v,292),s+=h.buffer.length);const j=u.join(n,"files",m,R);c.mkdirSync(u.dirname(j),{recursive:!0});try{c.linkSync(v,j)}catch{c.copyFileSync(v,j)}}}return{objectBytesWritten:s,deduplicatedBytes:r,redaction:{policyVersion:1,status:"applied",textualFilesScanned:o,filesRedacted:i,replacements:a,sensitiveEnvironmentKeys:t.map(m=>m.key).sort(),valuesStored:!1}}}function Be(e,n,t){const s=new Map(W.map(o=>[`.testkit/results/${o}/`,`${x(n,`files/${o}`)}/`])),r=K(e,s);return ke(Ae(r),t)}function Ae(e){if(!$(e))return e;const n={...e};if($(n.product)){const t={...n.product};delete t.directory,n.product=t}if($(n.git)){const t={...n.git};delete t.remoteUrl,delete t.repoRoot,delete t.worktreePath,n.git=t}return delete n.host,n}function K(e,n){if(Array.isArray(e))return e.map(s=>K(s,n));if($(e))return Object.fromEntries(Object.entries(e).map(([s,r])=>[s,K(r,n)]));if(typeof e!="string")return e;let t=e;for(const[s,r]of n)t.startsWith(s)&&(t=`${r}${t.slice(s.length)}`);return t}function ke(e,n){let t=0;function s(r){if(Array.isArray(r))return r.map(s);if($(r))return Object.fromEntries(Object.entries(r).map(([i,a])=>[i,s(a)]));if(typeof r!="string")return r;let o=Y(r);o!==r&&(t+=1);for(const i of n){if(!o.includes(i.value))continue;const a=o.split(i.value);t+=a.length-1,o=a.join("<redacted>")}return o}return{artifact:s(e),replacements:t}}function $e(e,n,t,s){const r=z(n.run.fileNames),o=[...new Set([...we,...r])].map(i=>Fe(e,i)).filter(i=>i!==null);return{schemaVersion:pe,runId:t,ci:be(s.env??process.env),source:{git:n.git??null,files:o},runtime:{controller:{testkitVersion:A(n.run.testkitVersion),node:process.versions.node,platform:process.platform,architecture:process.arch},runtimeStats:ye(n.run.runtimeStats)},scenario:{types:z(n.run.types),suiteSelectors:z(n.run.suiteSelectors),fileNames:r,serviceFilter:A(n.run.serviceFilter),planningDigest:C(Buffer.from(q(n.planning??null))),declaredEnvironmentKeys:[...new Set(s.declaredEnvironmentKeys??[])].sort()},redaction:s.redaction}}function xe(e){return re(e).map(n=>({path:X(u.relative(e,n)),bytes:c.statSync(n).size,sha256:F(n)})).sort((n,t)=>n.path.localeCompare(t.path))}function V(e,n){const t=u.join(e,"manifest.json"),s=J(P(t),t,n);le(P(u.join(e,"run.json")),u.join(e,"run.json"),n);const r=[];for(const i of s.files){let a;try{a=ee(e,i.path)}catch(m){r.push(k(m));continue}if(!c.existsSync(a)){r.push(`missing ${i.path}`);continue}const f=c.lstatSync(a);if(f.isSymbolicLink()){r.push(`symbolic link not allowed ${i.path}`);continue}if(!f.isFile()){r.push(`not a regular file ${i.path}`);continue}f.size!==i.bytes&&r.push(`size mismatch ${i.path}`),F(a)!==i.sha256&&r.push(`checksum mismatch ${i.path}`)}C(Buffer.from(q(s.files)))!==s.digest&&r.push("manifest digest mismatch"),s.storage.fileCount!==s.files.length&&r.push("manifest file count mismatch");for(const i of["run.json","provenance.json"])s.files.some(a=>a.path===i)||r.push(`manifest must include ${i}`);if(r.length===0){const i=ee(e,"provenance.json");ue(P(i),i,n)}return{runId:n,checkedFiles:s.files.length,storage:s.storage,ok:r.length===0,errors:r,manifestDigest:s.digest}}function Pe(e,n,t){const s={schemaVersion:1,runId:n,recordedAt:new Date().toISOString(),...t};return N(u.join(U(e),"publications",`${n}.json`),s),s}function Oe(e,n){return n.size===0?!1:Z(e.services).some(t=>Z(t.suites).some(s=>{const r=A(t.name)??"",o=A(s.name)??"",i=A(s.testType)??"";return[o,`${i}:${o}`,`${r}/${i}:${o}`].some(f=>n.has(f))}))}function Ce(e){const n=new Set;for(const t of listRunBundles(e)){const s=loadRunBundleManifest(e,t.runId);for(const r of s.files)r.path.startsWith("files/")&&n.add(r.sha256)}return ae(oe(e),n,ce)}function oe(e){const n=u.join(U(e),Q,"sha256");return c.existsSync(n)?c.readdirSync(n,{withFileTypes:!0}).filter(t=>t.isFile()).map(t=>{const s=u.join(n,t.name);return{bytes:c.statSync(s).size,digest:t.name,path:s}}):[]}function ae(e,n,t){const s=[];let r=0,o=0;for(const i of e){if(n.has(i.digest))continue;let a=null;try{t(i.path)}catch(m){a=L(m)}!c.existsSync(i.path)?(r+=1,o+=i.bytes):a===null&&(a="Object still exists after removal completed"),a!==null&&s.push({error:a,path:i.path})}return{failures:s,reclaimedBytes:o,removedObjects:r,status:s.length===0?"complete":"failed"}}function Ne(e){c.rmSync(e,{recursive:!0,force:!0})}function ce(e){c.rmSync(e,{force:!0})}function Ve({currentRunId:e,decidedAt:n,garbageCollection:t,policy:s,protectedRunIds:r,removed:o,removalErrors:i}){const a=[...i.map(f=>`remove ${f.runId}: ${f.error}`),...t.failures.map(f=>`garbage collect ${f.path}: ${f.error}`)];return{currentRunId:e,decidedAt:n,garbageCollection:t,policy:s,protectedRunIds:r,removed:o,removalErrors:i,schemaVersion:he,status:a.length===0?"complete":"failed",...a.length===0?{}:{error:a.join("; ")}}}function De(e,n,t){if(!_e(e,n)){for(const i of t)if(e.includes(Buffer.from(i.value)))throw new Error(`Refusing to archive binary file containing sensitive environment value: ${n}`);return{buffer:e,scanned:!1,replacements:0}}const s=e.toString("utf8");let r=Y(s),o=r===s?0:1;for(const i of t){if(!r.includes(i.value))continue;const a=r.split(i.value);o+=a.length-1,r=a.join("<redacted>")}return{buffer:Buffer.from(r),scanned:!0,replacements:o}}function Te(e){const n=[];for(const[t,s]of Object.entries(e))Ie.test(t)&&typeof s=="string"&&s.length>=8&&n.push({key:t,value:s});return n}function _e(e,n){if(e.includes(0))return!1;const t=u.extname(n).toLowerCase();return![".zip",".gz",".png",".jpg",".jpeg",".webm",".pdf"].includes(t)}function Fe(e,n){const t=u.resolve(e,n);return!t.startsWith(`${u.resolve(e)}${u.sep}`)||!c.existsSync(t)||!c.statSync(t).isFile()?null:{path:X(u.relative(e,t)),bytes:c.statSync(t).size,sha256:F(t)}}function ze(e){const n=[];for(const t of[...W.map(s=>u.join(e,s)),u.join(e,"live.json")])try{c.rmSync(t,{recursive:!0,force:!0})}catch(s){n.push(`${t}: ${L(s)}`)}return n.length===0?{status:"complete"}:{error:n.join("; "),status:"failed"}}function L(e){try{return k(e)}catch{return"Unknown post-seal archive operation failure"}}
@@ -138,14 +138,6 @@ export interface RunBundleVerification {
138
138
  runId: string;
139
139
  storage?: null | RunBundleStorage;
140
140
  }
141
- export interface RunBundleUploadFile extends RunBundleManifestEntry {
142
- absolutePath: string;
143
- }
144
- export interface VerifiedRunBundleUpload {
145
- files: RunBundleUploadFile[];
146
- manifest: RunBundleManifest;
147
- runId: string;
148
- }
149
141
  export interface SealRunBundleOptions {
150
142
  archiveConfig?: Partial<NormalizedArtifactArchiveConfig>;
151
143
  declaredEnvironmentKeys?: readonly string[];
@@ -1,6 +1,6 @@
1
1
  export { normalizeArtifactArchiveConfig, type NormalizedArtifactArchiveConfig, type NormalizedArtifactPublicationConfig, type NormalizedArtifactRetentionConfig, } from "./artifact-config.js";
2
2
  export { finalizeLiveRunArtifact, loadTimings, persistTaskArtifacts, persistTaskFileArtifacts, persistTaskOutputArtifacts, resetResultArtifacts, saveTimings, writeLiveRunArtifact, writeRunArtifact, type ArtifactTask, type PersistedArtifactReference, type WriteRunArtifactOptions, } from "./artifacts.js";
3
- export { applyRunBundleRetention, listRunBundles, loadRunBundleArtifact, loadRunBundleManifest, loadVerifiedRunBundleArtifactIfExists, prepareVerifiedRunBundleUpload, publishRunBundle, sealRunBundle, verifyRunBundle, type RunBundleVerification, type VerifiedRunBundleUpload, } from "./bundles.js";
3
+ export { applyRunBundleRetention, listRunBundles, loadRunBundleArtifact, loadRunBundleManifest, loadVerifiedRunBundleArtifactIfExists, publishRunBundle, sealRunBundle, verifyRunBundle, type RunBundleVerification, } from "./bundles.js";
4
4
  export { collectArtifactEntries, loadCurrentRunArtifact, loadLatestRunArtifact, loadRunArtifact, type CollectedArtifactEntry, type RunArtifactFileSubject, type RunArtifactPayload, } from "./reading.js";
5
5
  export { DEFAULT_REPORTED_ARCHIVE_CONFIG, type BuildLiveRunArtifactOptions, type BuildRunArtifactOptions, type ReportedRunArtifact, type ReportedRunService, type ReportingArtifactReference, type ReportingDiagnostic, type ReportingExecution, type ReportingFailureDetail, type ReportingFailureLocation, type ReportingFailureRequest, type ReportingFailureResponse, type ReportingFileSummary, type ReportingMetadata, type ReportingOperationTiming, type ReportingOperationTimingInput, type ReportingRecord, type ReportingRuntimeBatch, type ReportingRuntimeReset, type ReportingServiceLogReference, type ReportingServiceSummary, type ReportingSetupLogReference, type ReportingSetupOperation, type ReportingSuiteSelector, type ReportingSuiteSummary, type RunArtifactDocument, type RunArtifactFile, type RunArtifactRun, type RunArtifactService, type RunArtifactSuite, type RunArtifactSummary, type RunServiceResult, } from "./reporting-types.js";
6
6
  export { copyRegularFileSecurely, MAX_PERSISTED_FILE_ARTIFACT_BYTES, type SecureFileCopyOptions, type SecureFileCopyResult, } from "./secure-file-copy.js";
@@ -1 +1 @@
1
- export{normalizeArtifactArchiveConfig}from"./artifact-config.js";export{finalizeLiveRunArtifact,loadTimings,persistTaskArtifacts,persistTaskFileArtifacts,persistTaskOutputArtifacts,resetResultArtifacts,saveTimings,writeLiveRunArtifact,writeRunArtifact}from"./artifacts.js";export{applyRunBundleRetention,listRunBundles,loadRunBundleArtifact,loadRunBundleManifest,loadVerifiedRunBundleArtifactIfExists,prepareVerifiedRunBundleUpload,publishRunBundle,sealRunBundle,verifyRunBundle}from"./bundles.js";export{collectArtifactEntries,loadCurrentRunArtifact,loadLatestRunArtifact,loadRunArtifact}from"./reading.js";export{DEFAULT_REPORTED_ARCHIVE_CONFIG}from"./reporting-types.js";export{copyRegularFileSecurely,MAX_PERSISTED_FILE_ARTIFACT_BYTES}from"./secure-file-copy.js";export{collectTestkitStorageInventory}from"./storage-inventory.js";export{}from"./timing.js";
1
+ export{normalizeArtifactArchiveConfig}from"./artifact-config.js";export{finalizeLiveRunArtifact,loadTimings,persistTaskArtifacts,persistTaskFileArtifacts,persistTaskOutputArtifacts,resetResultArtifacts,saveTimings,writeLiveRunArtifact,writeRunArtifact}from"./artifacts.js";export{applyRunBundleRetention,listRunBundles,loadRunBundleArtifact,loadRunBundleManifest,loadVerifiedRunBundleArtifactIfExists,publishRunBundle,sealRunBundle,verifyRunBundle}from"./bundles.js";export{collectArtifactEntries,loadCurrentRunArtifact,loadLatestRunArtifact,loadRunArtifact}from"./reading.js";export{DEFAULT_REPORTED_ARCHIVE_CONFIG}from"./reporting-types.js";export{copyRegularFileSecurely,MAX_PERSISTED_FILE_ARTIFACT_BYTES}from"./secure-file-copy.js";export{collectTestkitStorageInventory}from"./storage-inventory.js";export{}from"./timing.js";
@@ -3,7 +3,7 @@
3
3
  "entrypoint": "dist/process/process.js",
4
4
  "sourceSha256": "a54f01864b603cee568b2945390dff35d578e07f6d26b5add06014169c3d17b0",
5
5
  "bundleSha256": "62f76cf9fd019f6c552bfde2787f058917caf279670a62e7a6bfa12be34cb4b1",
6
- "packageLockSha256": "199fa09ca74cbda901572a6d406108c5d987a057f48b55bae294e7a1d4bded4f",
6
+ "packageLockSha256": "21f0898586dbf3747862f6def92186117fbb1612fc057f4ec6b22d1670cdbd00",
7
7
  "esbuildVersion": "0.28.1",
8
8
  "options": {
9
9
  "format": "esm",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gfdelarue/testkit",
3
- "version": "0.3.7",
3
+ "version": "0.4.0",
4
4
  "description": "Test orchestration platform for product acceptance and QA workflows",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",
@@ -79,7 +79,6 @@
79
79
  "test:integration": "npm run build && tsx scripts/test/integration.ts",
80
80
  "test:system": "npm run build && vitest run --config scripts/test/system.config.ts",
81
81
  "test:consumer-smoke": "npm run build && tsx scripts/test/installed-consumer-smoke.ts",
82
- "test:cloud-account": "npm run build && vitest run --config scripts/test/cloud-account.config.ts",
83
82
  "test:live": "npm run build && vitest run --config scripts/test/live.config.ts",
84
83
  "release": "tsx scripts/release/index.ts",
85
84
  "prepack": "npm run build"
@@ -87,18 +86,15 @@
87
86
  "files": [
88
87
  "bin/",
89
88
  "dist/",
90
- "docs/cloud-control.md",
91
89
  "docs/engine-architecture.md",
92
90
  "docs/test-types.md",
93
- "docs/testkit-cloud-boundary.md",
94
91
  "testkit-config-schema.md"
95
92
  ],
96
93
  "bundleDependencies": [
97
94
  "diff",
98
95
  "tar",
99
96
  "typescript",
100
- "yauzl",
101
- "yazl"
97
+ "yauzl"
102
98
  ],
103
99
  "devDependencies": {
104
100
  "@eslint/js": "^10.0.1",
@@ -108,7 +104,6 @@
108
104
  "@types/k6": "1.6.0",
109
105
  "@types/picomatch": "4.0.3",
110
106
  "@types/yauzl": "3.4.0",
111
- "@types/yazl": "3.3.0",
112
107
  "globals": "^17.7.0",
113
108
  "prettier": "3.9.6",
114
109
  "strip-ansi": "^7.1.2",
@@ -137,12 +132,10 @@
137
132
  "tar": "7.5.21",
138
133
  "tinyglobby": "0.2.17",
139
134
  "typescript": "5.9.3",
140
- "undici": "7.29.1",
141
135
  "vite": "8.0.16",
142
136
  "vitest": "4.1.11",
143
137
  "wrap-ansi": "^10.0.0",
144
- "yauzl": "3.4.0",
145
- "yazl": "3.3.1"
138
+ "yauzl": "3.4.0"
146
139
  },
147
140
  "optionalDependencies": {
148
141
  "@gfdelarue/testkit-engine-k6-darwin-arm64": "1.0.0",
@@ -747,26 +747,3 @@ compact CLI output and should not be treated as the only identifier.
747
747
  - `skipCount`
748
748
  - `avgDurationMs`
749
749
  - `lastStatus`
750
-
751
- ## Optional local Cloud verification rule
752
-
753
- ```ts
754
- cloud?: {
755
- verification?: {
756
- minimumConfirmingRuns?: number;
757
- };
758
- };
759
- ```
760
-
761
- This setting is deprecated with the `testkit cloud` commands.
762
-
763
- `cloud.verification.minimumConfirmingRuns` is an integer from 1 through 64,
764
- defaulting to 2. It controls whether Testkit submits a new `finding.verify`
765
- campaign report. It counts distinct full run IDs from available cited accepted
766
- receipts. Explicit unverified reports do not require confirming evidence.
767
-
768
- Cloud never reads or receives this config and independently enforces its
769
- GUI-configured minimum. A local rejection and a Cloud rejection both produce
770
- nonzero CLI status with structured diagnostics. See
771
- [Campaign reporting and recovery](docs/testkit-cloud-boundary.md#report-an-externally-executed-campaign)
772
- for examples, config directory selection, and exact retry behavior.
@@ -1,6 +0,0 @@
1
- import { CloudClient, type CloudReceipt } from "../../cloud/client.js";
2
- import { type CampaignReport, type CampaignResult } from "../../cloud/campaigns.js";
3
- export declare function readCampaignReportFile(file: string): unknown;
4
- export declare function requiresVerificationPreflight(currentVersion: number, expectedVersion: number): boolean;
5
- export declare function checkVerificationSubmission(client: CloudClient, report: CampaignReport, productDir: string): Promise<void>;
6
- export declare function associateReceipt(client: CloudClient, receipt: CloudReceipt, campaignId: string): Promise<CampaignResult>;
@@ -1 +0,0 @@
1
- import c from"node:fs";import u from"node:path";import p from"node:crypto";import{CloudResponseError as m}from"../../cloud/client.js";import{campaignError as f}from"../../cloud/campaigns.js";import{loadTestkitConfig as l,minimumConfirmingRuns as g}from"../../config/loader/index.js";import{attachErrorDiagnostics as d}from"../../kernel/error-serialization.js";import{shellQuote as h}from"../../process/host/platform.js";export function readCampaignReportFile(r){const e=c.openSync(u.resolve(r),"r");try{if(!c.fstatSync(e).isFile())throw f("file","report must be a regular file");const n=Buffer.alloc(65537);let t=0;for(;t<n.length;){const i=c.readSync(e,n,t,n.length-t,null);if(!i)break;t+=i}if(t>65536)throw f("file","report exceeds 65,536 bytes");try{return JSON.parse(new TextDecoder("utf-8",{fatal:!0}).decode(n.subarray(0,t)))}catch{throw f("file","report must contain valid UTF-8 JSON")}}finally{c.closeSync(e)}}export function requiresVerificationPreflight(r,e){if(r<e)throw d(Object.assign(new Error("Verification report names a future campaign version; read current state before submitting"),{code:"campaign_version_conflict",status:409}),{source:"local",currentVersion:r,expectedVersion:e});return r===e}export async function checkVerificationSubmission(r,e,n){if(e.command.type==="finding.verify"&&requiresVerificationPreflight((await r.campaign(e.campaignId)).version,e.expectedVersion))try{const t=await r.finding(e.campaignId,e.command.findingId);if(!requiresVerificationPreflight(t.campaignVersion,e.expectedVersion))return;const i=g((await l(n)).config),o=new Set(t.evidence.filter(a=>a.available).map(a=>a.receiptId)),s=new Set;for(const a of o)s.add((await r.receipt(a)).runId);if(s.size<i)throw d(Object.assign(new Error(`Local verification submission requires ${String(i)} distinct confirming runs; ${String(s.size)} eligible`),{code:"local_insufficient_confirmations"}),{source:"local",required:i,eligible:s.size})}catch(t){if((await r.campaign(e.campaignId)).version>e.expectedVersion)return;throw t}}export async function associateReceipt(r,e,n){let t;try{const i=await r.campaign(n),o="link:"+p.createHash("sha256").update(JSON.stringify([r.projectId,n,e.id,i.version])).digest("hex");return t={schemaVersion:1,projectId:r.projectId,campaignId:n,expectedVersion:i.version,idempotencyKey:o,command:{type:"receipt.link",receiptId:e.id}},await r.report(t)}catch(i){const o=t?i instanceof m&&i.status<500?"rejected":"unknown":"not_attempted";throw i instanceof Error&&(i.message=`Published run ${e.runId} (${e.id}); campaign association did not return a confirmed result. ${i.message}. Use --json for receipt and report recovery details.`),d(i,{publication:{status:"accepted",receipt:e},association:{campaignId:n,status:o,...t?{report:t}:{}},recovery:o==="rejected"?"Resolve the rejection, then repeat publication to read the current campaign version and reuse the accepted receipt: testkit cloud publish --run "+h(e.runId)+" --campaign "+n+" --json":t?"Save association.report as JSON and retry: testkit cloud apply --campaign "+n+" --file report.json --json":"Read the campaign version, then submit receipt.link for accepted receipt "+e.id})}}
@@ -1,12 +0,0 @@
1
- import { type ControlOptions } from "./control.js";
2
- export declare function createCloudCampaign(options: ControlOptions & {
3
- requestKey?: string;
4
- focus?: string;
5
- targetBugCount?: number;
6
- }): Promise<unknown>;
7
- export declare function previewCloudPolicy(policy: string, file: string, options: ControlOptions): Promise<unknown>;
8
- export declare function downloadCloudArtifact(receipt: string, artifactPath: string, output: string, options: ControlOptions): Promise<unknown>;
9
- export declare function revokeCloudSession(id: string, options: ControlOptions): Promise<{
10
- id: string;
11
- revoked: true;
12
- }>;
@@ -1,2 +0,0 @@
1
- import p from"node:fs";import f from"node:path";import{randomUUID as d}from"node:crypto";import{controlClient as c,resolveOrganisation as w,resolveProject as h}from"./control.js";import{allItems as y,createdResourceDashboardLink as g}from"./control-links.js";import{invalidResponse as v}from"../../cloud/transport.js";export async function createCloudCampaign(e){const t=await c(e),r=await h(t,e),n=e.requestKey??d();e.notify?.(`Request key: ${n}. Reuse --request-key ${n} to retry the same campaign creation.
2
- `);const a=await t.request("POST",`/api/v1/projects/${encodeURIComponent(r)}/campaigns`,{schemaVersion:1,projectId:r,idempotencyKey:n,focus:e.focus??"",...e.targetBugCount!==void 0?{targetBugCount:e.targetBugCount}:{}});return g(t,"campaign",a,e.env??process.env)}export async function previewCloudPolicy(e,t,r){const n=await c(r),a=await w(n,r.organisation,r.choose),i=p.statSync(t);if(!i.isFile()||i.size>256*1024)throw new Error("Policy preview input must be a JSON file no larger than 256 KiB.");const l=JSON.parse(p.readFileSync(t,"utf8"));return n.request("POST",`/api/v1/organisations/${encodeURIComponent(a)}/policies/${encodeURIComponent(e)}/preview`,l)}export async function downloadCloudArtifact(e,t,r,n){const a=await c(n);if(t.split("/").some(s=>!s||s==="."||s==="..")||t.includes("\\"))throw new Error("Artifact path must be a normalized relative path.");const i=`/api/v1/receipts/${encodeURIComponent(e)}/artifacts`,o=(await y(a,i)).find(s=>s.path===t);if(!o)throw new Error("This receipt has no artifact at the requested path.");if(o.available!==!0)throw new Error("Artifact evidence has expired. Its manifest metadata is still available.");if(typeof o.bytes!="number"||typeof o.sha256!="string")throw v("artifact manifest");const u=f.resolve(r);if(p.existsSync(u))throw new Error("Output file already exists. Choose a new path.");const m=new URL(`${i}/${t.split("/").map(encodeURIComponent).join("/")}`,a.origin);return await a.downloadArtifact(m,u,{bytes:o.bytes,sha256:o.sha256}),{receiptId:e,path:t,output:u,bytes:o.bytes,sha256:o.sha256}}export async function revokeCloudSession(e,t){if(!e.trim())throw new Error("Session ID is required");return await(await c(t,!1)).request("DELETE",`/api/v1/auth/cli/sessions/${encodeURIComponent(e)}`),{id:e,revoked:!0}}
@@ -1,5 +0,0 @@
1
- import { type CloudAccountClient } from "../../cloud/account.js";
2
- export declare function allItems(client: CloudAccountClient, target: string): Promise<Record<string, unknown>[]>;
3
- export declare function resourceDashboardLinks(client: CloudAccountClient, resource: string, value: unknown, env: NodeJS.ProcessEnv): Promise<unknown>;
4
- export declare function cloudDashboardUrl(origin: string, organisation: string, project: string, campaign?: string): string;
5
- export declare function createdResourceDashboardLink(client: CloudAccountClient, resource: "project" | "campaign", result: unknown, env: NodeJS.ProcessEnv): Promise<unknown>;