@gfdelarue/testkit 0.3.7 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/README.md +125 -0
  2. package/dist/app/bugs/areas.d.ts +38 -0
  3. package/dist/app/bugs/areas.js +1 -0
  4. package/dist/app/bugs/assessment.d.ts +9 -0
  5. package/dist/app/bugs/assessment.js +1 -0
  6. package/dist/app/bugs/categories.d.ts +41 -0
  7. package/dist/app/bugs/categories.js +1 -0
  8. package/dist/app/bugs/ci-report.d.ts +21 -0
  9. package/dist/app/bugs/ci-report.js +2 -0
  10. package/dist/app/bugs/ci-summary.d.ts +13 -0
  11. package/dist/app/bugs/ci-summary.js +5 -0
  12. package/dist/app/bugs/draft.d.ts +70 -0
  13. package/dist/app/bugs/draft.js +4 -0
  14. package/dist/app/bugs/facts.d.ts +48 -0
  15. package/dist/app/bugs/facts.js +2 -0
  16. package/dist/app/bugs/file.d.ts +45 -0
  17. package/dist/app/bugs/file.js +1 -0
  18. package/dist/app/bugs/gate.d.ts +22 -0
  19. package/dist/app/bugs/gate.js +1 -0
  20. package/dist/app/bugs/guide.d.ts +20 -0
  21. package/dist/app/bugs/guide.js +8 -0
  22. package/dist/app/bugs/issue.d.ts +18 -0
  23. package/dist/app/bugs/issue.js +6 -0
  24. package/dist/app/bugs/known-failures.d.ts +49 -0
  25. package/dist/app/bugs/known-failures.js +1 -0
  26. package/dist/app/bugs/record.d.ts +14 -0
  27. package/dist/app/bugs/record.js +2 -0
  28. package/dist/app/bugs/run-reading.d.ts +51 -0
  29. package/dist/app/bugs/run-reading.js +1 -0
  30. package/dist/app/bugs/store.d.ts +34 -0
  31. package/dist/app/bugs/store.js +2 -0
  32. package/dist/app/campaigns/brief.d.ts +7 -0
  33. package/dist/app/campaigns/brief.js +10 -0
  34. package/dist/app/campaigns/campaign.d.ts +14 -0
  35. package/dist/app/campaigns/campaign.js +1 -0
  36. package/dist/app/campaigns/index.d.ts +38 -0
  37. package/dist/app/campaigns/index.js +2 -0
  38. package/dist/app/campaigns/store.d.ts +5 -0
  39. package/dist/app/campaigns/store.js +1 -0
  40. package/dist/app/campaigns/summary.d.ts +13 -0
  41. package/dist/app/campaigns/summary.js +8 -0
  42. package/dist/app/run/engine-execution.d.ts +1 -0
  43. package/dist/app/run/engine-execution.js +1 -1
  44. package/dist/app/run/failure-details.d.ts +1 -0
  45. package/dist/app/run/failure-details.js +1 -1
  46. package/dist/app/tracker/index.d.ts +8 -0
  47. package/dist/app/tracker/index.js +1 -0
  48. package/dist/app/tracker/remote.d.ts +13 -0
  49. package/dist/app/tracker/remote.js +1 -0
  50. package/dist/app/tracker/token.d.ts +7 -0
  51. package/dist/app/tracker/token.js +5 -0
  52. package/dist/cli/command-catalog.d.ts +21 -122
  53. package/dist/cli/command-catalog.js +1 -1
  54. package/dist/cli/command-registry.d.ts +1 -0
  55. package/dist/cli/command-registry.js +1 -1
  56. package/dist/cli/commands/bug/file.d.ts +16 -0
  57. package/dist/cli/commands/bug/file.js +2 -0
  58. package/dist/cli/commands/bug/guide.d.ts +5 -0
  59. package/dist/cli/commands/bug/guide.js +1 -0
  60. package/dist/cli/commands/bug/record.d.ts +9 -0
  61. package/dist/cli/commands/bug/record.js +2 -0
  62. package/dist/cli/commands/campaign/finish.d.ts +10 -0
  63. package/dist/cli/commands/campaign/finish.js +1 -0
  64. package/dist/cli/commands/campaign/start.d.ts +7 -0
  65. package/dist/cli/commands/campaign/start.js +1 -0
  66. package/dist/cli/commands/campaign/status.d.ts +13 -0
  67. package/dist/cli/commands/campaign/status.js +2 -0
  68. package/dist/cli/commands/ci/report.d.ts +15 -0
  69. package/dist/cli/commands/ci/report.js +2 -0
  70. package/dist/cli/commands/run.d.ts +1 -1
  71. package/dist/cli/commands/run.js +2 -1
  72. package/dist/cli/entrypoint.js +1 -1
  73. package/dist/cli/framework/execute.d.ts +1 -0
  74. package/dist/cli/framework/execute.js +6 -6
  75. package/dist/cli/index.js +1 -1
  76. package/dist/cli/renderers/bug/text.d.ts +8 -0
  77. package/dist/cli/renderers/bug/text.js +1 -0
  78. package/dist/cli/renderers/campaign/text.d.ts +2 -0
  79. package/dist/cli/renderers/campaign/text.js +1 -0
  80. package/dist/cli/renderers/ci/text.d.ts +2 -0
  81. package/dist/cli/renderers/ci/text.js +1 -0
  82. package/dist/cli/version-drift.js +7 -7
  83. package/dist/config/authoring/index.d.ts +10 -5
  84. package/dist/config/loader/bugs.d.ts +15 -0
  85. package/dist/config/loader/bugs.js +1 -0
  86. package/dist/config/loader/index.d.ts +0 -1
  87. package/dist/config/loader/index.js +1 -1
  88. package/dist/engines/adapters/execution.d.ts +4 -0
  89. package/dist/engines/adapters/execution.js +1 -1
  90. package/dist/engines/k6/adapter/executor.js +1 -1
  91. package/dist/engines/k6/runtime/checks.js +1 -1
  92. package/dist/engines/k6/runtime/http-assertions.d.ts +2 -2
  93. package/dist/engines/k6/runtime/http-assertions.js +1 -1
  94. package/dist/engines/k6/runtime/types.d.ts +2 -0
  95. package/dist/engines/node-vitest/adapter/evidence.js +2 -2
  96. package/dist/engines/node-vitest/adapter/failure-details.d.ts +1 -0
  97. package/dist/engines/node-vitest/adapter/failure-details.js +2 -2
  98. package/dist/engines/playwright/adapter/adapter.js +1 -1
  99. package/dist/engines/playwright/adapter/attachments.d.ts +1 -0
  100. package/dist/engines/playwright/adapter/config.js +3 -3
  101. package/dist/engines/playwright/adapter/evidence.js +1 -1
  102. package/dist/engines/playwright/adapter/reporter.js +1 -1
  103. package/dist/evidence/bundles.d.ts +2 -3
  104. package/dist/evidence/bundles.js +1 -1
  105. package/dist/evidence/contracts.d.ts +0 -8
  106. package/dist/evidence/index.d.ts +1 -1
  107. package/dist/evidence/index.js +1 -1
  108. package/dist/kernel/redaction.d.ts +1 -0
  109. package/dist/kernel/redaction.js +1 -1
  110. package/dist/process/execa-bundle.json +1 -1
  111. package/dist/process/files/json-file.d.ts +1 -0
  112. package/dist/process/files/json-file.js +1 -1
  113. package/dist/repo/history.d.ts +17 -0
  114. package/dist/repo/history.js +4 -0
  115. package/dist/repo/tree-listing.d.ts +10 -0
  116. package/dist/repo/tree-listing.js +1 -0
  117. package/dist/tracker/gitea.d.ts +14 -0
  118. package/dist/tracker/gitea.js +1 -0
  119. package/dist/tracker/github.d.ts +14 -0
  120. package/dist/tracker/github.js +1 -0
  121. package/dist/tracker/http.d.ts +33 -0
  122. package/dist/tracker/http.js +1 -0
  123. package/dist/tracker/index.d.ts +4 -0
  124. package/dist/tracker/index.js +1 -0
  125. package/dist/tracker/redaction.d.ts +4 -0
  126. package/dist/tracker/redaction.js +2 -0
  127. package/dist/tracker/requests.d.ts +17 -0
  128. package/dist/tracker/requests.js +1 -0
  129. package/dist/tracker/responses.d.ts +24 -0
  130. package/dist/tracker/responses.js +1 -0
  131. package/dist/tracker/types.d.ts +51 -0
  132. package/docs/bug-records.md +303 -0
  133. package/package.json +4 -10
  134. package/testkit-config-schema.md +50 -23
  135. package/dist/app/cloud/campaigns.d.ts +0 -6
  136. package/dist/app/cloud/campaigns.js +0 -1
  137. package/dist/app/cloud/control-actions.d.ts +0 -12
  138. package/dist/app/cloud/control-actions.js +0 -2
  139. package/dist/app/cloud/control-links.d.ts +0 -5
  140. package/dist/app/cloud/control-links.js +0 -1
  141. package/dist/app/cloud/control-read.d.ts +0 -24
  142. package/dist/app/cloud/control-read.js +0 -1
  143. package/dist/app/cloud/control-resources.d.ts +0 -3
  144. package/dist/app/cloud/control-resources.js +0 -1
  145. package/dist/app/cloud/control.d.ts +0 -34
  146. package/dist/app/cloud/control.js +0 -5
  147. package/dist/app/cloud/credentials.d.ts +0 -11
  148. package/dist/app/cloud/credentials.js +0 -2
  149. package/dist/app/cloud/index.d.ts +0 -22
  150. package/dist/app/cloud/index.js +0 -2
  151. package/dist/app/cloud/login.d.ts +0 -63
  152. package/dist/app/cloud/login.js +0 -2
  153. package/dist/app/cloud/migration/archive.d.ts +0 -13
  154. package/dist/app/cloud/migration/archive.js +0 -1
  155. package/dist/app/cloud/migration/commands.d.ts +0 -8
  156. package/dist/app/cloud/migration/commands.js +0 -1
  157. package/dist/app/cloud/migration/evidence.d.ts +0 -13
  158. package/dist/app/cloud/migration/evidence.js +0 -2
  159. package/dist/app/cloud/migration/files.d.ts +0 -16
  160. package/dist/app/cloud/migration/files.js +0 -2
  161. package/dist/app/cloud/migration/index.d.ts +0 -6
  162. package/dist/app/cloud/migration/index.js +0 -1
  163. package/dist/app/cloud/migration/journal.d.ts +0 -64
  164. package/dist/app/cloud/migration/journal.js +0 -1
  165. package/dist/app/cloud/migration/mapping-dispositions.d.ts +0 -3
  166. package/dist/app/cloud/migration/mapping-dispositions.js +0 -1
  167. package/dist/app/cloud/migration/mapping.d.ts +0 -11
  168. package/dist/app/cloud/migration/mapping.js +0 -2
  169. package/dist/app/cloud/migration/model.d.ts +0 -175
  170. package/dist/app/cloud/migration/plan.d.ts +0 -7
  171. package/dist/app/cloud/migration/plan.js +0 -1
  172. package/dist/app/cloud/migration/progress.d.ts +0 -19
  173. package/dist/app/cloud/migration/progress.js +0 -1
  174. package/dist/app/cloud/migration/reconcile.d.ts +0 -22
  175. package/dist/app/cloud/migration/reconcile.js +0 -1
  176. package/dist/app/cloud/migration/reconciliation-spool.d.ts +0 -4
  177. package/dist/app/cloud/migration/reconciliation-spool.js +0 -1
  178. package/dist/app/cloud/migration/state.d.ts +0 -15
  179. package/dist/app/cloud/migration/state.js +0 -1
  180. package/dist/app/cloud/migration/transfer.d.ts +0 -6
  181. package/dist/app/cloud/migration/transfer.js +0 -1
  182. package/dist/cli/cloud-apply-help.d.ts +0 -1
  183. package/dist/cli/cloud-apply-help.js +0 -56
  184. package/dist/cli/cloud-selection.d.ts +0 -2
  185. package/dist/cli/cloud-selection.js +0 -3
  186. package/dist/cli/commands/cloud/apply.d.ts +0 -5
  187. package/dist/cli/commands/cloud/apply.js +0 -1
  188. package/dist/cli/commands/cloud/campaign/create.d.ts +0 -5
  189. package/dist/cli/commands/cloud/campaign/create.js +0 -1
  190. package/dist/cli/commands/cloud/download.d.ts +0 -5
  191. package/dist/cli/commands/cloud/download.js +0 -1
  192. package/dist/cli/commands/cloud/list.d.ts +0 -5
  193. package/dist/cli/commands/cloud/list.js +0 -1
  194. package/dist/cli/commands/cloud/login.d.ts +0 -13
  195. package/dist/cli/commands/cloud/login.js +0 -2
  196. package/dist/cli/commands/cloud/logout.d.ts +0 -7
  197. package/dist/cli/commands/cloud/logout.js +0 -1
  198. package/dist/cli/commands/cloud/migrate/apply.d.ts +0 -2
  199. package/dist/cli/commands/cloud/migrate/apply.js +0 -1
  200. package/dist/cli/commands/cloud/migrate/plan.d.ts +0 -7
  201. package/dist/cli/commands/cloud/migrate/plan.js +0 -1
  202. package/dist/cli/commands/cloud/migrate/progress.d.ts +0 -2
  203. package/dist/cli/commands/cloud/migrate/progress.js +0 -2
  204. package/dist/cli/commands/cloud/migrate/reconcile.d.ts +0 -2
  205. package/dist/cli/commands/cloud/migrate/reconcile.js +0 -1
  206. package/dist/cli/commands/cloud/policy/preview.d.ts +0 -5
  207. package/dist/cli/commands/cloud/policy/preview.js +0 -1
  208. package/dist/cli/commands/cloud/project/create.d.ts +0 -5
  209. package/dist/cli/commands/cloud/project/create.js +0 -1
  210. package/dist/cli/commands/cloud/publish.d.ts +0 -7
  211. package/dist/cli/commands/cloud/publish.js +0 -1
  212. package/dist/cli/commands/cloud/session/revoke.d.ts +0 -8
  213. package/dist/cli/commands/cloud/session/revoke.js +0 -1
  214. package/dist/cli/commands/cloud/show.d.ts +0 -5
  215. package/dist/cli/commands/cloud/show.js +0 -1
  216. package/dist/cli/commands/cloud/snapshot.d.ts +0 -5
  217. package/dist/cli/commands/cloud/snapshot.js +0 -1
  218. package/dist/cli/commands/cloud/status.d.ts +0 -33
  219. package/dist/cli/commands/cloud/status.js +0 -1
  220. package/dist/cloud/account.d.ts +0 -34
  221. package/dist/cloud/account.js +0 -1
  222. package/dist/cloud/archive-contract.d.ts +0 -1
  223. package/dist/cloud/archive-contract.js +0 -1
  224. package/dist/cloud/archive.d.ts +0 -12
  225. package/dist/cloud/archive.js +0 -1
  226. package/dist/cloud/campaigns.d.ts +0 -200
  227. package/dist/cloud/campaigns.js +0 -2
  228. package/dist/cloud/client.d.ts +0 -68
  229. package/dist/cloud/client.js +0 -4
  230. package/dist/cloud/download.d.ts +0 -5
  231. package/dist/cloud/download.js +0 -1
  232. package/dist/cloud/historical-contract.d.ts +0 -224
  233. package/dist/cloud/historical-contract.js +0 -1
  234. package/dist/cloud/historical-json.d.ts +0 -16
  235. package/dist/cloud/historical-json.js +0 -1
  236. package/dist/cloud/historical.d.ts +0 -69
  237. package/dist/cloud/historical.js +0 -1
  238. package/dist/cloud/pagination.d.ts +0 -5
  239. package/dist/cloud/pagination.js +0 -1
  240. package/dist/cloud/transport.d.ts +0 -18
  241. package/dist/cloud/transport.js +0 -1
  242. package/dist/config/loader/cloud.d.ts +0 -2
  243. package/dist/config/loader/cloud.js +0 -1
  244. package/docs/cloud-control.md +0 -108
  245. package/docs/testkit-cloud-boundary.md +0 -628
  246. package/node_modules/buffer-crc32/LICENSE +0 -19
  247. package/node_modules/buffer-crc32/README.md +0 -55
  248. package/node_modules/buffer-crc32/dist/index.cjs +0 -306
  249. package/node_modules/buffer-crc32/dist/index.d.cts +0 -35
  250. package/node_modules/buffer-crc32/dist/index.d.mts +0 -35
  251. package/node_modules/buffer-crc32/dist/index.mjs +0 -304
  252. package/node_modules/buffer-crc32/index.d.ts +0 -35
  253. package/node_modules/buffer-crc32/package.json +0 -54
  254. package/node_modules/yazl/LICENSE +0 -21
  255. package/node_modules/yazl/README.md +0 -457
  256. package/node_modules/yazl/index.js +0 -826
  257. package/node_modules/yazl/package.json +0 -34
  258. /package/dist/{app/cloud/migration/model.js → tracker/types.js} +0 -0
@@ -1 +1 @@
1
- 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";
@@ -1 +1,2 @@
1
+ export declare const SENSITIVE_KEY_PATTERN: RegExp;
1
2
  export declare function redactSensitiveText(value: unknown): string;
@@ -1 +1 @@
1
- const S=/\b([A-Z0-9_]+)=([^\s]+)/giu,T=/PASSWORD|TOKEN|SECRET|PRIVATE_KEY|AUTH|COOKIE|SESSION|API_KEY/iu,a=/(:\/\/[^:\s/@]+:)([^@\s/]+)(@)/gu,o=String;export function redactSensitiveText(r){return u(o(r??"").replace(S,(e,t,s)=>T.test(t)?/(^|_)URL$/iu.test(t)?`${t}=${u(s)}`:`${t}=<redacted>`:e))}function u(r){return r.replace(a,(e,t,s,c,E)=>{for(let n=E-1;n>=0;n--){const i=r[n];if(/[a-z]/iu.test(i))return`${t}<redacted>${c}`;if(!/[0-9+.-]/u.test(i))break}return e})}
1
+ const S=/\b([A-Z0-9_]+)=([^\s]+)/giu;export const SENSITIVE_KEY_PATTERN=/PASSWORD|TOKEN|SECRET|PRIVATE_KEY|AUTH|COOKIE|SESSION|API_KEY/iu;const T=/(:\/\/[^:\s/@]+:)([^@\s/]+)(@)/gu,a=String;export function redactSensitiveText(r){return u(a(r??"").replace(S,(e,t,s)=>SENSITIVE_KEY_PATTERN.test(t)?/(^|_)URL$/iu.test(t)?`${t}=${u(s)}`:`${t}=<redacted>`:e))}function u(r){return r.replace(T,(e,t,s,c,E)=>{for(let n=E-1;n>=0;n--){const i=r[n];if(/[a-z]/iu.test(i))return`${t}<redacted>${c}`;if(!/[0-9+.-]/u.test(i))break}return e})}
@@ -3,7 +3,7 @@
3
3
  "entrypoint": "dist/process/process.js",
4
4
  "sourceSha256": "a54f01864b603cee568b2945390dff35d578e07f6d26b5add06014169c3d17b0",
5
5
  "bundleSha256": "62f76cf9fd019f6c552bfde2787f058917caf279670a62e7a6bfa12be34cb4b1",
6
- "packageLockSha256": "199fa09ca74cbda901572a6d406108c5d987a057f48b55bae294e7a1d4bded4f",
6
+ "packageLockSha256": "7d8833d3b8528d73331845635995428ce3be97f369c1c1b1c05d92eec833b073",
7
7
  "esbuildVersion": "0.28.1",
8
8
  "options": {
9
9
  "format": "esm",
@@ -8,4 +8,5 @@ export interface AtomicWriteOptions {
8
8
  maxDelayMs?: number;
9
9
  }
10
10
  export declare function writeJsonFileAtomicWithRetry(filePath: string, value: unknown, options?: AtomicWriteOptions): void;
11
+ export declare function writeTextFileAtomicWithRetry(filePath: string, text: string, options?: AtomicWriteOptions): void;
11
12
  export declare function isRetryableAtomicReplaceError(error: unknown): boolean;
@@ -1 +1 @@
1
- import s from"node:fs";import c from"node:path";const p=String;export function isPropertyContainer(t){return typeof t=="object"&&t!==null||typeof t=="function"}export function readProperty(t,r){return Reflect.get(t,r)}export function serializeJson(t,r){let e;try{e=JSON.stringify(t,null,2)}catch(o){throw new Error(`${r} is not JSON serializable`,{cause:o})}if(typeof e!="string")throw new Error(`${r} is not JSON serializable`);return e}export function errorMessage(t){if(t instanceof Error)return t.message;if(isPropertyContainer(t)){const r=readProperty(t,"message");if(typeof r=="string"&&r.length>0)return r}return p(t)}const y=new Set(["EPERM","EACCES","EBUSY"]),u=process.platform==="win32"?100:25,l=10,S=100;class A extends Error{attempts;code="TESTKIT_ATOMIC_WRITE_FAILED";cause;path;tempPath;constructor(r,e,o,n){super(`Failed to atomically replace ${r} after ${o.toString()} attempt${o===1?"":"s"}: ${errorMessage(n)}`,{cause:n}),this.name="AtomicWriteError",this.attempts=o,this.cause=n,this.path=r,this.tempPath=e}}export function writeJsonFileAtomicWithRetry(t,r,e={}){const o=c.dirname(t),n=c.join(o,`.${c.basename(t)}.${process.pid.toString()}.${Date.now().toString()}.${Math.random().toString(16).slice(2)}.tmp`),a=e.attempts??u,E=e.baseDelayMs??l,m=e.maxDelayMs??S;s.writeFileSync(n,serializeJson(r,`atomic JSON file ${t}`));try{for(let i=1;i<=a;i+=1)try{s.renameSync(n,t);return}catch(f){if(!isRetryableAtomicReplaceError(f)||i>=a||R(t))throw new A(t,n,i,f);_(Math.min(m,E*i))}}finally{s.rmSync(n,{force:!0})}}export function isRetryableAtomicReplaceError(t){if(!isPropertyContainer(t))return!1;const r=readProperty(t,"code");return typeof r=="string"&&y.has(r)}function R(t){try{return s.lstatSync(t).isDirectory()}catch{return!1}}function _(t){if(t<=0)return;const r=new SharedArrayBuffer(4);Atomics.wait(new Int32Array(r),0,0,t)}
1
+ import s from"node:fs";import c from"node:path";const p=String;export function isPropertyContainer(t){return typeof t=="object"&&t!==null||typeof t=="function"}export function readProperty(t,r){return Reflect.get(t,r)}export function serializeJson(t,r){let e;try{e=JSON.stringify(t,null,2)}catch(o){throw new Error(`${r} is not JSON serializable`,{cause:o})}if(typeof e!="string")throw new Error(`${r} is not JSON serializable`);return e}export function errorMessage(t){if(t instanceof Error)return t.message;if(isPropertyContainer(t)){const r=readProperty(t,"message");if(typeof r=="string"&&r.length>0)return r}return p(t)}const y=new Set(["EPERM","EACCES","EBUSY"]),u=process.platform==="win32"?100:25,l=10,A=100;class S extends Error{attempts;code="TESTKIT_ATOMIC_WRITE_FAILED";cause;path;tempPath;constructor(r,e,o,n){super(`Failed to atomically replace ${r} after ${o.toString()} attempt${o===1?"":"s"}: ${errorMessage(n)}`,{cause:n}),this.name="AtomicWriteError",this.attempts=o,this.cause=n,this.path=r,this.tempPath=e}}export function writeJsonFileAtomicWithRetry(t,r,e={}){writeTextFileAtomicWithRetry(t,serializeJson(r,`atomic JSON file ${t}`),e)}export function writeTextFileAtomicWithRetry(t,r,e={}){const o=c.dirname(t),n=c.join(o,`.${c.basename(t)}.${process.pid.toString()}.${Date.now().toString()}.${Math.random().toString(16).slice(2)}.tmp`),a=e.attempts??u,E=e.baseDelayMs??l,m=e.maxDelayMs??A;s.writeFileSync(n,r);try{for(let i=1;i<=a;i+=1)try{s.renameSync(n,t);return}catch(f){if(!isRetryableAtomicReplaceError(f)||i>=a||R(t))throw new S(t,n,i,f);T(Math.min(m,E*i))}}finally{s.rmSync(n,{force:!0})}}export function isRetryableAtomicReplaceError(t){if(!isPropertyContainer(t))return!1;const r=readProperty(t,"code");return typeof r=="string"&&y.has(r)}function R(t){try{return s.lstatSync(t).isDirectory()}catch{return!1}}function T(t){if(t<=0)return;const r=new SharedArrayBuffer(4);Atomics.wait(new Int32Array(r),0,0,t)}
@@ -0,0 +1,17 @@
1
+ export interface BlamedCommit {
2
+ readonly boundary: boolean;
3
+ readonly commit: string;
4
+ readonly committedAt: number;
5
+ }
6
+ export type IntroducingCommit = {
7
+ readonly commit: string;
8
+ readonly status: "found";
9
+ } | {
10
+ readonly status: "outside-history";
11
+ };
12
+ export declare function findIntroducingCommit(repoRoot: string, commit: string, path: string, lines: readonly [number, number]): Promise<IntroducingCommit>;
13
+ export declare function parseBlame(output: string): readonly BlamedCommit[];
14
+ export declare function newestBlamedCommit(commits: readonly BlamedCommit[]): BlamedCommit | null;
15
+ export declare function listRemoteRefsContaining(repoRoot: string, commit: string): Promise<readonly string[]>;
16
+ export declare function listUncommittedTrackedFiles(repoRoot: string): Promise<readonly string[]>;
17
+ export declare function listFilesChangedSince(repoRoot: string, base: string): Promise<readonly string[] | null>;
@@ -0,0 +1,4 @@
1
+ import{requireManagedHostCommandSuccess as a}from"../process/index.js";import{runManagedGitCommand as s}from"./managed-git.js";const u=/^([0-9a-f]{40}|[0-9a-f]{64}) \d+ \d+(?: \d+)?$/u,l=/^committer-time (\d+)$/u;export async function findIntroducingCommit(e,t,n,r){const i=`${String(r[0])},${String(r[1])}`,o=await s(e,["blame","-w","-M","--porcelain","-L",i,t,"--",n]);if(o.exitCode!==0)throw new Error(`Git cannot blame lines ${String(r[0])} to ${String(r[1])} of ${n} at ${t}: ${m(o.stderr)}`);const c=newestBlamedCommit(parseBlame(o.stdout));if(c===null)throw new Error(`Git blamed no lines of ${n} at ${t}`);return c.boundary&&await f(e)?{status:"outside-history"}:{commit:c.commit,status:"found"}}export function parseBlame(e){const t=new Map;let n=null;for(const r of e.split(`
2
+ `)){if(r.startsWith(" "))continue;const i=u.exec(r);if(i!==null){n=t.get(i[1])??{boundary:!1,committedAt:0},t.set(i[1],n);continue}if(n===null)continue;const o=l.exec(r);o!==null?n.committedAt=Number(o[1]):r==="boundary"&&(n.boundary=!0)}return[...t].map(([r,i])=>({commit:r,...i}))}export function newestBlamedCommit(e){let t=null;for(const n of e)(t===null||n.committedAt>t.committedAt)&&(t=n);return t}export async function listRemoteRefsContaining(e,t){return a("git for-each-ref",await s(e,["for-each-ref","--format=%(refname)","--contains",t,"refs/remotes"])).stdout.split(`
3
+ `).map(i=>i.trim()).filter(i=>i.length>0&&!i.endsWith("/HEAD"))}export async function listUncommittedTrackedFiles(e){return a("git diff",await s(e,["diff","--name-only","-z","HEAD","--"])).stdout.split("\0").filter(r=>r.length>0)}export async function listFilesChangedSince(e,t){if((await s(e,["merge-base","--is-ancestor",t,"HEAD"])).exitCode!==0)return null;const r=["diff","--name-only","--diff-filter=d","-z",`${t}..HEAD`,"--"];return a("git diff",await s(e,r)).stdout.split("\0").filter(o=>o.length>0)}async function f(e){return a("git rev-parse",await s(e,["rev-parse","--is-shallow-repository"])).stdout.trim()==="true"}function m(e){return e.trim().split(`
4
+ `).at(-1)??""}
@@ -0,0 +1,10 @@
1
+ export interface CommitFile {
2
+ readonly path: string;
3
+ readonly size: number;
4
+ }
5
+ export interface TreeListing {
6
+ readonly directories: readonly string[];
7
+ readonly files: readonly CommitFile[];
8
+ }
9
+ export declare function listCommitFiles(repoRoot: string, commit: string): Promise<readonly CommitFile[]>;
10
+ export declare function parseTreeListing(output: string, directory?: string): TreeListing;
@@ -0,0 +1 @@
1
+ import{isUnknownRecord as m}from"../kernel/unknown-values.js";import{requireManagedHostCommandSuccess as h}from"../process/index.js";import{runManagedGitCommand as p}from"./managed-git.js";const w=/^(\d{6}) (blob|commit|tree) [0-9a-f]+ +(-|\d+)\t([\s\S]+)$/u,E=new Set(["100644","100755"]);export function listCommitFiles(t,e){return a(t,e,"")}export function parseTreeListing(t,e=""){const r=e===""?"":`${e}/`,o=[],s=[];for(const i of t.split("\0")){if(i.length===0)continue;const n=w.exec(i);if(n===null)throw new Error("Git returned an unreadable ls-tree record");const[,f,u,d,l]=n;u==="tree"?o.push(`${r}${l}`):u==="blob"&&E.has(f)&&s.push({path:`${r}${l}`,size:Number(d)})}return{directories:o,files:s}}async function a(t,e,r){const o=await c(t,e,r,!0);if(o!==null)return o.files;const s=await c(t,e,r,!1);if(s===null)throw new Error(`Git lists too many entries directly under ${r||"the repository root"} to read`);const i=[...s.files];for(const n of s.directories)i.push(...await a(t,e,n));return i}async function c(t,e,r,o){const s=r===""?e:`${e}:${r}`,i=["ls-tree","--full-tree","-l","-z",...o?["-r"]:[],s];try{const n=h("git ls-tree",await p(t,i));return parseTreeListing(n.stdout,r)}catch(n){if($(n))return null;throw n}}function $(t){if(!(t instanceof Error)||!("result"in t))return!1;const e=t.result;return m(e)&&e.outputLimitExceeded===!0}
@@ -0,0 +1,14 @@
1
+ import { type TrackerRequest } from "./http.js";
2
+ import { type TrackerRepository } from "./requests.js";
3
+ import type { Tracker, TrackerIssueDraft, TrackerLabel, TrackerTarget } from "./types.js";
4
+ export declare function giteaHeaders(token: string): Record<string, string>;
5
+ export declare function giteaRequests(repository: TrackerRepository): {
6
+ routes: import("./requests.js").TrackerRoutes;
7
+ createIssue: (draft: TrackerIssueDraft, labelIds: readonly number[]) => TrackerRequest;
8
+ createLabel: (label: TrackerLabel) => TrackerRequest;
9
+ listComments: (pullRequest: number, number: number) => TrackerRequest;
10
+ listLabels: (number: number) => TrackerRequest;
11
+ listOpenIssues: (label: string, number: number) => TrackerRequest;
12
+ };
13
+ export declare function readGiteaVersion(origin: string): Promise<string | null>;
14
+ export declare function createGiteaTracker(target: TrackerTarget): Tracker;
@@ -0,0 +1 @@
1
+ import{createTrackerHttp as b,TRACKER_PAGE_LIMIT as y,TrackerError as f}from"./http.js";import{commentRequest as T,commitStatusRequest as g,getRequest as k,TRACKER_USER_AGENT as h,trackerRepository as w,trackerRoutes as I}from"./requests.js";import{decodeGiteaVersion as R,decodeTrackerComment as G,decodeTrackerComments as S,decodeTrackerCommitStatusContext as C,decodeTrackerIssue as A,decodeTrackerIssues as E,decodeTrackerLabel as L,decodeTrackerLabels as q,requireIssueLabels as x}from"./responses.js";const $="50";export function giteaHeaders(a){return{Accept:"application/json",Authorization:`token ${a}`,"User-Agent":h}}export function giteaRequests(a){const o=I(a),r=(s,i,m={})=>k(s,{...m,limit:$,page:String(i)});return{routes:o,createIssue:(s,i)=>({body:{body:s.body,labels:i,title:s.title},method:"POST",url:o.issues}),createLabel:s=>({body:{color:`#${s.color}`,description:s.description,name:s.name},method:"POST",url:o.labels}),listComments:(s,i)=>r(o.comments(s),i),listLabels:s=>r(o.labels,s),listOpenIssues:(s,i)=>r(o.issues,i,{labels:s,state:"open",type:"issues"})}}export async function readGiteaVersion(a){const r=await b({headers:{Accept:"application/json","User-Agent":h},label:"Gitea version probe",secrets:[]}).exchange({method:"GET",url:`${a}/api/v1/version`});return r.ok?R(r.body):null}function O(a){const o=a.get("x-total-count");return o!==null&&/^\d+$/u.test(o)?Number(o):Number.POSITIVE_INFINITY}export function createGiteaTracker(a){const o=w(a),r=giteaRequests(o),s=b({headers:giteaHeaders(a.token),label:"Gitea",secrets:[a.token]});async function i(t,c,e){const n=new Map;for(let u=1;u<=y;u++){const l=await s.send(t(u)),p=n.size;for(const d of c(l.body,l.source))n.has(e(d))||n.set(e(d),d);if(n.size===p||n.size>=O(l.headers))return[...n.values()]}throw new f("rejected",`Gitea returned more than ${String(y)} pages`)}const m=()=>i(r.listLabels,q,t=>t.id);return{target:o,async createIssue(t){const c=t.labels.length===0?[]:await m(),e=t.labels.map(l=>{const p=c.find(d=>d.name===l);if(p===void 0)throw new f("rejected",`Gitea repository ${o.owner}/${o.name} has no label named ${JSON.stringify(l)}`);return p.id}),n=await s.send(r.createIssue(t,e)),{issue:u}=A(n.body,n.source);return x(u,t.labels,"Gitea",o)},async ensureLabels(t){if(t.length===0)return;const c=new Set((await m()).map(e=>e.name));for(const e of t){if(c.has(e.name))continue;const n=await s.send(r.createLabel(e));L(n.body,n.source),c.add(e.name)}},async listOpenIssues(t){return(await i(e=>r.listOpenIssues(t,e),E,e=>e.issue.number)).filter(e=>!e.pullRequest&&e.issue.labels.includes(t)).map(e=>e.issue)},async setCommitStatus(t){const c=await s.send(g(r.routes,t));C(c.body,c.source)},async upsertPullRequestComment(t){const e=(await i(u=>r.listComments(t.pullRequest,u),S,u=>u.id)).find(u=>u.body.startsWith(t.marker)),n=await s.send(T(r.routes,t,e));return{created:e===void 0,url:G(n.body,n.source).url}}}}
@@ -0,0 +1,14 @@
1
+ import { type TrackerRequest } from "./http.js";
2
+ import { type TrackerRepository } from "./requests.js";
3
+ import type { Tracker, TrackerIssueDraft, TrackerLabel, TrackerTarget } from "./types.js";
4
+ export declare function githubHeaders(token: string): Record<string, string>;
5
+ export declare function githubRequests(repository: TrackerRepository): {
6
+ routes: import("./requests.js").TrackerRoutes;
7
+ createIssue: (draft: TrackerIssueDraft) => TrackerRequest;
8
+ createLabel: (label: TrackerLabel) => TrackerRequest;
9
+ listComments: (pullRequest: number) => TrackerRequest;
10
+ listLabels: () => TrackerRequest;
11
+ listOpenIssues: (label: string) => TrackerRequest;
12
+ nextPage(link: string | null): TrackerRequest | null;
13
+ };
14
+ export declare function createGithubTracker(target: TrackerTarget): Tracker;
@@ -0,0 +1 @@
1
+ import{createTrackerHttp as g,nextPageLink as w,TRACKER_PAGE_LIMIT as m,TrackerError as b}from"./http.js";import{commentRequest as f,commitStatusRequest as L,getRequest as d,TRACKER_USER_AGENT as h,trackerRepository as k,trackerRoutes as y}from"./requests.js";import{decodeTrackerComment as R,decodeTrackerComments as T,decodeTrackerCommitStatusContext as I,decodeTrackerIssue as C,decodeTrackerIssues as x,decodeTrackerLabel as A,decodeTrackerLabels as E,isDuplicateLabelRejection as P,requireIssueLabels as q}from"./responses.js";const p="100",G=100;export function githubHeaders(u){return{Accept:"application/vnd.github+json",Authorization:`Bearer ${u}`,"User-Agent":h,"X-GitHub-Api-Version":"2022-11-28"}}export function githubRequests(u){const i=y(u);return{routes:i,createIssue:e=>({body:{body:e.body,labels:e.labels,title:e.title},method:"POST",url:i.issues}),createLabel:e=>({body:{color:e.color,description:Array.from(e.description).slice(0,G).join(""),name:e.name},method:"POST",url:i.labels}),listComments:e=>d(i.comments(e),{per_page:p}),listLabels:()=>d(i.labels,{per_page:p}),listOpenIssues:e=>d(i.issues,{labels:e,per_page:p,state:"open"}),nextPage(e){const a=w(e);if(a===null)return null;if(!URL.canParse(a)||new URL(a).origin!==new URL(u.apiUrl).origin)throw new b("rejected",`GitHub returned a next page outside ${u.apiUrl}`);return{method:"GET",url:a}}}}export function createGithubTracker(u){const i=k(u),e=githubRequests(i),a=g({headers:githubHeaders(u.token),label:"GitHub",secrets:[u.token]});async function c(t,o){const s=[];let r=t;for(let n=0;r!==null;n++){if(n===m)throw new b("rejected",`GitHub returned more than ${String(m)} pages`);const l=await a.send(r);s.push(...o(l.body,l.source)),r=e.nextPage(l.headers.get("link"))}return s}return{target:i,async createIssue(t){const o=await a.send(e.createIssue(t)),{issue:s}=C(o.body,o.source);return q(s,t.labels,"GitHub",i)},async ensureLabels(t){if(t.length===0)return;const o=new Set((await c(e.listLabels(),E)).map(s=>s.name.toLowerCase()));for(const s of t){const r=s.name.toLowerCase();if(o.has(r))continue;const n=await a.exchange(e.createLabel(s));if(n.ok)A(n.body,n.source);else if(n.status!==422||!P(n.body))throw a.failure(n);o.add(r)}},async listOpenIssues(t){const o=t.toLowerCase(),s=new Map;for(const r of await c(e.listOpenIssues(t),x))r.pullRequest||!r.issue.labels.some(n=>n.toLowerCase()===o)||s.set(r.issue.number,r.issue);return[...s.values()]},async setCommitStatus(t){const o=await a.send(L(e.routes,t));I(o.body,o.source)},async upsertPullRequestComment(t){const s=(await c(e.listComments(t.pullRequest),T)).find(n=>n.body.startsWith(t.marker)),r=await a.send(f(e.routes,t,s));return{created:s===void 0,url:R(r.body,r.source).url}}}}
@@ -0,0 +1,33 @@
1
+ import type { TrackerFailureKind } from "./types.js";
2
+ export declare const TRACKER_PAGE_LIMIT = 1000;
3
+ export declare class TrackerError extends Error {
4
+ readonly kind: TrackerFailureKind;
5
+ readonly status: number | null;
6
+ constructor(kind: TrackerFailureKind, message: string, status?: number | null);
7
+ }
8
+ export interface TrackerRequest {
9
+ readonly body?: unknown;
10
+ readonly method: "GET" | "PATCH" | "POST";
11
+ readonly url: string;
12
+ }
13
+ export interface TrackerResponse {
14
+ readonly body: unknown;
15
+ readonly headers: Headers;
16
+ readonly json: boolean;
17
+ readonly ok: boolean;
18
+ readonly source: string;
19
+ readonly status: number;
20
+ }
21
+ export interface TrackerHttpOptions {
22
+ readonly headers: Readonly<Record<string, string>>;
23
+ readonly label: string;
24
+ readonly secrets: readonly string[];
25
+ }
26
+ export interface TrackerHttp {
27
+ exchange(request: TrackerRequest): Promise<TrackerResponse>;
28
+ failure(response: TrackerResponse): TrackerError;
29
+ send(request: TrackerRequest): Promise<TrackerResponse>;
30
+ }
31
+ export declare function createTrackerHttp(options: TrackerHttpOptions): TrackerHttp;
32
+ export declare function nextPageLink(link: string | null): string | null;
33
+ export declare function scrubFailureDetail(text: string, secrets: readonly string[]): string;
@@ -0,0 +1 @@
1
+ import{isUnknownRecord as g}from"../kernel/unknown-values.js";import{redactBugText as m}from"./redaction.js";const l=3e4,d=300,f=Symbol("not JSON");export const TRACKER_PAGE_LIMIT=1e3;export class TrackerError extends Error{kind;status;constructor(t,s,o=null){super(s),this.name="TrackerError",this.kind=t,this.status=o}}export function createTrackerHttp(e){const t=r=>scrubFailureDetail(r,e.secrets);async function s(r){const n=`${e.label} ${r.method} ${T(r.url)}`;let i,c;try{i=await fetch(r.url,{body:r.body===void 0?void 0:JSON.stringify(r.body),headers:r.body===void 0?e.headers:{...e.headers,"Content-Type":"application/json"},method:r.method,signal:AbortSignal.timeout(l)}),c=await i.text()}catch(h){throw new TrackerError("unreachable",`${n} did not complete: ${t(y(h))}`)}const u=p(c);return{body:u===f?c:u,headers:i.headers,json:u!==f,ok:i.ok,source:n,status:i.status}}function o(r){const n=t(w(r.body));return new TrackerError($(r.status),`${r.source} returned ${String(r.status)}${n.length>0?`: ${n}`:""}`,r.status)}async function a(r){const n=await s(r);if(!n.ok)throw o(n);if(!n.json)throw new TrackerError("rejected",`${n.source} returned a response that is not JSON`,n.status);return n}return{exchange:s,failure:o,send:a}}export function nextPageLink(e){if(e===null)return null;for(const t of e.matchAll(/<([^>]*)>([^,]*)/gu)){const s=/;\s*rel\s*=\s*(?:"([^"]*)"|([^;\s]*))/iu.exec(t[2]);if((s?.[1]??s?.[2]??"").toLowerCase().split(/\s+/u).includes("next"))return t[1]}return null}export function scrubFailureDetail(e,t){let s=e;for(const a of t)a.length>0&&(s=s.split(a).join("<redacted>"));const o=m(s.replace(/\s+/gu," ").trim());return o.length>d?`${o.slice(0,d)}...`:o}function p(e){if(e.trim().length===0)return null;try{return JSON.parse(e)}catch{return f}}function $(e){return e===401?"unauthorized":e===403?"forbidden":e===404?"not-found":"rejected"}function w(e){if(typeof e=="string")return e;if(!g(e))return"";const t=typeof e.message=="string"?e.message:"";return Array.isArray(e.errors)?`${t} ${JSON.stringify(e.errors)}`:t}function y(e){if(!(e instanceof Error))return"the request failed";if(e.name==="TimeoutError")return`no answer within ${String(l/1e3)} seconds`;const t=e.cause;if(t instanceof Error){if(t.message.length>0)return t.message;if("code"in t&&typeof t.code=="string")return t.code}return e.message}function T(e){const t=new URL(e);return`${t.origin}${t.pathname}`}
@@ -0,0 +1,4 @@
1
+ import type { Tracker, TrackerTarget } from "./types.js";
2
+ export { TrackerError } from "./http.js";
3
+ export type * from "./types.js";
4
+ export declare function createTracker(target: TrackerTarget): Tracker;
@@ -0,0 +1 @@
1
+ import{createGiteaTracker as e}from"./gitea.js";import{createGithubTracker as o}from"./github.js";export{TrackerError}from"./http.js";export function createTracker(r){return r.provider==="github"?o(r):e(r)}
@@ -0,0 +1,4 @@
1
+ export declare function redactBugText(value: string): string;
2
+ export declare function redactRunResultText(value: string): string;
3
+ export declare function holdsRedaction(value: string): boolean;
4
+ export declare function redactUntilUnchanged(value: string, redact: (text: string) => string): string;
@@ -0,0 +1,2 @@
1
+ import{redactSensitiveText as d,SENSITIVE_KEY_PATTERN as T}from"../kernel/redaction.js";const c="<redacted>",E=/<redacted>(?!@)/u,A=/\b([A-Z0-9_]+)=(?=\S)/giu,R=/(?:^|_)URL$/iu,f=/["']?[a-z][a-z0-9+.-]*:\/\//iuy,_=/(?:basic|bearer|digest|negotiate|token)[ \t]/iuy,l=/(:\/\/[^:\s/@]*:)[^\s/]+(?=@)/gu,a=/\s/u,g=/(["'`]?)\b(?:(?:proxy-)?authorization|(?:set-)?cookie)[ \t]*:[ \t]*(?=\S)/giu,S=/"([^"\\\r\n]+)"(\s*:\s*)"(?:[^"\\\r\n]|\\.?)*("|$)/gmu,h=8;export function redactBugText(t){return redactUntilUnchanged(t,r=>P(I(p(d(x(r))))))}export function redactRunResultText(t){const r=t.replace(/\s+/gu," ").trim(),e=E.exec(r);return redactBugText(e===null?r:r.slice(0,e.index+c.length))}export function holdsRedaction(t){return t.includes(c)}export function redactUntilUnchanged(t,r){let e=t;for(let n=0;n<h;n+=1){const s=r(e);if(s===e)return e;e=s}return c}function x(t){const r=[];let e=0;for(const n of t.matchAll(A)){if(n.index<e)continue;const s=n[1];if(!T.test(s))continue;const o=n.index+n[0].length;R.test(s)&&u(f,t,o)||(r.push(t.slice(e,o),c),e=N(t,o))}return r.push(t.slice(e)),r.join("")}function N(t,r){const e=t[r];if(e==='"'||e==="'"){const n=i(t,r+1,e);return t[n]===e?n+1:n}if(u(_,t,r))return i(t,r,"");for(let n=r;n<t.length;n+=1)if(a.test(t[n]))return n;return t.length}function u(t,r,e){return t.lastIndex=e,t.test(r)}function p(t){return t.replace(l,`$1${c}`)}function I(t){const r=[];let e=0;for(const n of t.matchAll(g)){if(n.index<e)continue;const s=n.index+n[0].length;r.push(t.slice(e,s),c),e=i(t,s,n[1])}return r.push(t.slice(e)),r.join("")}function i(t,r,e){for(let n=r;n<t.length;n+=1){const s=t[n];if(s==="\r"||s===`
2
+ `||s===e&&t[n-1]!=="\\")return n}return t.length}function P(t){return t.replace(S,(r,e,n,s)=>T.test(e)?`"${e}"${n}"${c}${s}`:r)}
@@ -0,0 +1,17 @@
1
+ import type { TrackerRequest } from "./http.js";
2
+ import type { TrackerCommentRecord } from "./responses.js";
3
+ import type { TrackerCommitStatus, TrackerPullRequestComment, TrackerTarget } from "./types.js";
4
+ export declare const TRACKER_USER_AGENT = "testkit";
5
+ export type TrackerRepository = Omit<TrackerTarget, "token">;
6
+ export interface TrackerRoutes {
7
+ readonly issues: string;
8
+ readonly labels: string;
9
+ comment(id: number): string;
10
+ comments(pullRequest: number): string;
11
+ status(sha: string): string;
12
+ }
13
+ export declare function trackerRepository(target: TrackerTarget): TrackerRepository;
14
+ export declare function trackerRoutes(repository: TrackerRepository): TrackerRoutes;
15
+ export declare function getRequest(url: string, query: Readonly<Record<string, string>>): TrackerRequest;
16
+ export declare function commitStatusRequest(routes: TrackerRoutes, status: TrackerCommitStatus): TrackerRequest;
17
+ export declare function commentRequest(routes: TrackerRoutes, comment: TrackerPullRequestComment, existing: TrackerCommentRecord | undefined): TrackerRequest;
@@ -0,0 +1 @@
1
+ export const TRACKER_USER_AGENT="testkit";export function trackerRepository(t){return{apiUrl:t.apiUrl.replace(/\/+$/u,""),name:t.name,owner:t.owner,provider:t.provider}}export function trackerRoutes(t){const e=`${t.apiUrl}/repos/${encodeURIComponent(t.owner)}/${encodeURIComponent(t.name)}`;return{comment:o=>`${e}/issues/comments/${String(o)}`,comments:o=>`${e}/issues/${String(o)}/comments`,issues:`${e}/issues`,labels:`${e}/labels`,status:o=>`${e}/statuses/${encodeURIComponent(o)}`}}export function getRequest(t,e){return{method:"GET",url:`${t}?${new URLSearchParams(e).toString()}`}}export function commitStatusRequest(t,e){return{body:{context:e.context,description:e.description,state:e.state,...e.targetUrl===null?{}:{target_url:e.targetUrl}},method:"POST",url:t.status(e.sha)}}export function commentRequest(t,e,o){return o===void 0?{body:{body:e.body},method:"POST",url:t.comments(e.pullRequest)}:{body:{body:e.body},method:"PATCH",url:t.comment(o.id)}}
@@ -0,0 +1,24 @@
1
+ import type { TrackerIssue, TrackerTarget } from "./types.js";
2
+ export interface TrackerLabelRecord {
3
+ readonly id: number;
4
+ readonly name: string;
5
+ }
6
+ export interface TrackerIssueRecord {
7
+ readonly issue: TrackerIssue;
8
+ readonly pullRequest: boolean;
9
+ }
10
+ export interface TrackerCommentRecord {
11
+ readonly body: string;
12
+ readonly id: number;
13
+ readonly url: string;
14
+ }
15
+ export declare function decodeTrackerLabel(value: unknown, source: string): TrackerLabelRecord;
16
+ export declare function decodeTrackerLabels(value: unknown, source: string): TrackerLabelRecord[];
17
+ export declare function decodeTrackerIssue(value: unknown, source: string): TrackerIssueRecord;
18
+ export declare function requireIssueLabels(issue: TrackerIssue, labels: readonly string[], tracker: string, repository: Pick<TrackerTarget, "name" | "owner">): TrackerIssue;
19
+ export declare function decodeTrackerIssues(value: unknown, source: string): TrackerIssueRecord[];
20
+ export declare function decodeTrackerComment(value: unknown, source: string): TrackerCommentRecord;
21
+ export declare function decodeTrackerComments(value: unknown, source: string): TrackerCommentRecord[];
22
+ export declare function decodeTrackerCommitStatusContext(value: unknown, source: string): string;
23
+ export declare function isDuplicateLabelRejection(value: unknown): boolean;
24
+ export declare function decodeGiteaVersion(value: unknown): string | null;
@@ -0,0 +1 @@
1
+ import{isUnknownRecord as u}from"../kernel/unknown-values.js";import{TrackerError as c}from"./http.js";export function decodeTrackerLabel(t,e){const n=s(t,e,"label");return{id:m(n.id,e,"label.id"),name:i(n.name,e,"label.name")}}export function decodeTrackerLabels(t,e){return a(t,e,"labels").map(n=>decodeTrackerLabel(n,e))}export function decodeTrackerIssue(t,e){const n=s(t,e,"issue");return{issue:{body:f(n.body,e,"issue.body"),labels:(n.labels===void 0?[]:a(n.labels,e,"issue.labels")).map(r=>typeof r=="string"?r:i(s(r,e,"issue.labels[]").name,e,"issue.labels[].name")),number:m(n.number,e,"issue.number"),title:i(n.title,e,"issue.title"),url:b(n.html_url,e,"issue.html_url")},pullRequest:n.pull_request!==void 0&&n.pull_request!==null}}export function requireIssueLabels(t,e,n,r){const p=new Set(t.labels.map(l=>l.toLowerCase())),d=e.filter(l=>!p.has(l.toLowerCase()));if(d.length===0)return t;const h=`${r.owner}/${r.name}`;throw new c("forbidden",`${n} created ${t.url} without these labels: ${d.join(", ")}. The token's user may not label issues in ${h}. Close that issue, then run this again with a token whose user may label issues.`)}export function decodeTrackerIssues(t,e){return a(t,e,"issues").map(n=>decodeTrackerIssue(n,e))}export function decodeTrackerComment(t,e){const n=s(t,e,"comment");return{body:f(n.body,e,"comment.body"),id:m(n.id,e,"comment.id"),url:b(n.html_url,e,"comment.html_url")}}export function decodeTrackerComments(t,e){return a(t,e,"comments").map(n=>decodeTrackerComment(n,e))}export function decodeTrackerCommitStatusContext(t,e){return i(s(t,e,"commit status").context,e,"commit status.context")}export function isDuplicateLabelRejection(t){return!u(t)||!Array.isArray(t.errors)?!1:t.errors.some(e=>u(e)&&e.code==="already_exists")}export function decodeGiteaVersion(t){return u(t)&&typeof t.version=="string"&&t.version.length>0?t.version:null}function o(t,e,n){return new c("rejected",`${t} returned a response Testkit cannot read: ${e} must be ${n}`)}function s(t,e,n){if(!u(t))throw o(e,n,"an object");return t}function a(t,e,n){if(!Array.isArray(t))throw o(e,n,"a list");return t}function i(t,e,n){if(typeof t!="string")throw o(e,n,"a string");return t}function f(t,e,n){return t==null?"":i(t,e,n)}function m(t,e,n){if(typeof t!="number"||!Number.isSafeInteger(t)||t<=0)throw o(e,n,"a positive integer");return t}function b(t,e,n){const r=i(t,e,n);if(!/^https?:\/\/[^\s/]/u.test(r)||!URL.canParse(r))throw o(e,n,"an http or https URL");return r}
@@ -0,0 +1,51 @@
1
+ export type TrackerProvider = "gitea" | "github";
2
+ export interface TrackerTarget {
3
+ readonly apiUrl: string;
4
+ readonly name: string;
5
+ readonly owner: string;
6
+ readonly provider: TrackerProvider;
7
+ readonly token: string;
8
+ }
9
+ export interface TrackerLabel {
10
+ readonly color: string;
11
+ readonly description: string;
12
+ readonly name: string;
13
+ }
14
+ export interface TrackerIssue {
15
+ readonly body: string;
16
+ readonly labels: readonly string[];
17
+ readonly number: number;
18
+ readonly title: string;
19
+ readonly url: string;
20
+ }
21
+ export interface TrackerIssueDraft {
22
+ readonly body: string;
23
+ readonly labels: readonly string[];
24
+ readonly title: string;
25
+ }
26
+ export type TrackerCommitState = "failure" | "success";
27
+ export interface TrackerCommitStatus {
28
+ readonly context: string;
29
+ readonly description: string;
30
+ readonly sha: string;
31
+ readonly state: TrackerCommitState;
32
+ readonly targetUrl: string | null;
33
+ }
34
+ export interface TrackerPullRequestComment {
35
+ readonly body: string;
36
+ readonly marker: string;
37
+ readonly pullRequest: number;
38
+ }
39
+ export interface TrackerCommentReference {
40
+ readonly created: boolean;
41
+ readonly url: string;
42
+ }
43
+ export type TrackerFailureKind = "forbidden" | "not-found" | "rejected" | "unauthorized" | "unreachable";
44
+ export interface Tracker {
45
+ readonly target: Omit<TrackerTarget, "token">;
46
+ createIssue(draft: TrackerIssueDraft): Promise<TrackerIssue>;
47
+ ensureLabels(labels: readonly TrackerLabel[]): Promise<void>;
48
+ listOpenIssues(label: string): Promise<readonly TrackerIssue[]>;
49
+ setCommitStatus(status: TrackerCommitStatus): Promise<void>;
50
+ upsertPullRequestComment(comment: TrackerPullRequestComment): Promise<TrackerCommentReference>;
51
+ }