@mannyc1/ts-release 0.0.7 → 0.2.2

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 (635) hide show
  1. package/ARCHITECTURE.md +124 -92
  2. package/CHANGELOG.md +115 -0
  3. package/LICENSE +21 -0
  4. package/README.md +278 -324
  5. package/SPEC.md +206 -310
  6. package/apps/release-ts/README.md +32 -0
  7. package/apps/release-ts/src/cli/command.ts +164 -0
  8. package/apps/release-ts/src/cli/commands.ts +351 -0
  9. package/apps/release-ts/src/cli/main.ts +43 -0
  10. package/apps/release-ts/src/cli/node-main.ts +49 -0
  11. package/apps/ts-release-agents/LICENSE +21 -0
  12. package/apps/ts-release-agents/README.md +21 -0
  13. package/apps/ts-release-agents/evals/cases.json +11 -0
  14. package/apps/ts-release-agents/manifests/claude.json +5 -0
  15. package/apps/ts-release-agents/manifests/codex.json +5 -0
  16. package/apps/ts-release-agents/package.json +11 -0
  17. package/apps/ts-release-agents/skills/release/SKILL.md +48 -0
  18. package/apps/ts-release-agents/skills/release/references/configuration.md +18 -0
  19. package/apps/ts-release-agents/skills/release/references/recovery.md +10 -0
  20. package/apps/ts-release-agents/skills/release/references/staged-workflow.md +12 -0
  21. package/apps/ts-release-agents/skills/release/references/target-selection.md +13 -0
  22. package/apps/ts-release-agents/skills/release/references/verification.md +14 -0
  23. package/apps/ts-release-agents/src/build.ts +105 -0
  24. package/apps/ts-release-agents/src/check.ts +117 -0
  25. package/apps/ts-release-agents/src/install.ts +121 -0
  26. package/apps/ts-release-agents/test/contract.test.ts +56 -0
  27. package/apps/ts-release-agents/tsconfig.json +5 -0
  28. package/dist/api/api.d.ts +15 -0
  29. package/dist/api/api.d.ts.map +1 -0
  30. package/dist/api/api.js +244 -0
  31. package/dist/api/api.js.map +1 -0
  32. package/dist/api/errors.d.ts +39 -0
  33. package/dist/api/errors.d.ts.map +1 -0
  34. package/dist/api/errors.js +32 -0
  35. package/dist/api/errors.js.map +1 -0
  36. package/dist/api/input.d.ts +10 -0
  37. package/dist/api/input.d.ts.map +1 -0
  38. package/dist/api/input.js +73 -0
  39. package/dist/api/input.js.map +1 -0
  40. package/dist/api/runtime.d.ts +18 -0
  41. package/dist/api/runtime.d.ts.map +1 -0
  42. package/dist/api/runtime.js +7 -0
  43. package/dist/api/runtime.js.map +1 -0
  44. package/dist/api/types.d.ts +76 -0
  45. package/dist/api/types.d.ts.map +1 -0
  46. package/dist/api/types.js +17 -0
  47. package/dist/api/types.js.map +1 -0
  48. package/dist/bin/ts-release.js +39239 -0
  49. package/dist/capabilities/bun-targets.d.ts +3 -0
  50. package/dist/capabilities/bun-targets.d.ts.map +1 -0
  51. package/dist/capabilities/bun-targets.js +3 -0
  52. package/dist/capabilities/bun-targets.js.map +1 -0
  53. package/dist/capabilities/field-ownership.d.ts +17 -0
  54. package/dist/capabilities/field-ownership.d.ts.map +1 -0
  55. package/dist/capabilities/field-ownership.js +40 -0
  56. package/dist/capabilities/field-ownership.js.map +1 -0
  57. package/dist/capabilities/module.d.ts +91 -0
  58. package/dist/capabilities/module.d.ts.map +1 -0
  59. package/dist/capabilities/module.js +2 -0
  60. package/dist/capabilities/module.js.map +1 -0
  61. package/dist/capabilities/registry.d.ts +829 -0
  62. package/dist/capabilities/registry.d.ts.map +1 -0
  63. package/dist/capabilities/registry.js +291 -0
  64. package/dist/capabilities/registry.js.map +1 -0
  65. package/dist/config/config.d.ts +6 -0
  66. package/dist/config/config.d.ts.map +1 -0
  67. package/dist/config/config.js +42 -0
  68. package/dist/config/config.js.map +1 -0
  69. package/dist/correction/coordinator.d.ts +248 -0
  70. package/dist/correction/coordinator.d.ts.map +1 -0
  71. package/dist/correction/coordinator.js +274 -0
  72. package/dist/correction/coordinator.js.map +1 -0
  73. package/dist/correction/index.d.ts +3 -0
  74. package/dist/correction/index.d.ts.map +1 -0
  75. package/dist/correction/index.js +3 -0
  76. package/dist/correction/index.js.map +1 -0
  77. package/dist/correction/intent.d.ts +111 -0
  78. package/dist/correction/intent.d.ts.map +1 -0
  79. package/dist/correction/intent.js +146 -0
  80. package/dist/correction/intent.js.map +1 -0
  81. package/dist/drivers/archive.d.ts +8 -0
  82. package/dist/drivers/archive.d.ts.map +1 -0
  83. package/dist/drivers/archive.js +101 -0
  84. package/dist/drivers/archive.js.map +1 -0
  85. package/dist/drivers/contain.d.ts +6 -0
  86. package/dist/drivers/contain.d.ts.map +1 -0
  87. package/dist/drivers/contain.js +17 -0
  88. package/dist/drivers/contain.js.map +1 -0
  89. package/dist/drivers/environment.d.ts +4 -0
  90. package/dist/drivers/environment.d.ts.map +1 -0
  91. package/dist/drivers/environment.js +18 -0
  92. package/dist/drivers/environment.js.map +1 -0
  93. package/dist/drivers/errors.d.ts +9 -0
  94. package/dist/drivers/errors.d.ts.map +1 -0
  95. package/dist/drivers/errors.js +6 -0
  96. package/dist/drivers/errors.js.map +1 -0
  97. package/dist/drivers/glob.d.ts +2 -0
  98. package/dist/drivers/glob.d.ts.map +1 -0
  99. package/dist/drivers/glob.js +162 -0
  100. package/dist/drivers/glob.js.map +1 -0
  101. package/dist/drivers/process.d.ts +48 -0
  102. package/dist/drivers/process.d.ts.map +1 -0
  103. package/dist/drivers/process.js +257 -0
  104. package/dist/drivers/process.js.map +1 -0
  105. package/dist/drivers/redact.d.ts +4 -0
  106. package/dist/drivers/redact.d.ts.map +1 -0
  107. package/dist/drivers/redact.js +24 -0
  108. package/dist/drivers/redact.js.map +1 -0
  109. package/dist/drivers/seccomp-helper-source.d.ts +7 -0
  110. package/dist/drivers/seccomp-helper-source.d.ts.map +1 -0
  111. package/dist/drivers/seccomp-helper-source.js +121 -0
  112. package/dist/drivers/seccomp-helper-source.js.map +1 -0
  113. package/dist/drivers/utils.d.ts +4 -0
  114. package/dist/drivers/utils.d.ts.map +1 -0
  115. package/dist/drivers/utils.js +5 -0
  116. package/dist/drivers/utils.js.map +1 -0
  117. package/dist/drivers/workspace.d.ts +6 -0
  118. package/dist/drivers/workspace.d.ts.map +1 -0
  119. package/dist/drivers/workspace.js +49 -0
  120. package/dist/drivers/workspace.js.map +1 -0
  121. package/dist/extensions/provider-adapter.d.ts +48 -0
  122. package/dist/extensions/provider-adapter.d.ts.map +1 -0
  123. package/dist/extensions/provider-adapter.js +64 -0
  124. package/dist/extensions/provider-adapter.js.map +1 -0
  125. package/dist/host.d.ts +33 -0
  126. package/dist/host.d.ts.map +1 -0
  127. package/dist/host.js +20 -0
  128. package/dist/host.js.map +1 -0
  129. package/dist/index.d.ts +19 -1
  130. package/dist/index.d.ts.map +1 -1
  131. package/dist/index.js +12 -1
  132. package/dist/index.js.map +1 -1
  133. package/dist/model/artifact-collection.d.ts +84 -0
  134. package/dist/model/artifact-collection.d.ts.map +1 -0
  135. package/dist/model/artifact-collection.js +185 -0
  136. package/dist/model/artifact-collection.js.map +1 -0
  137. package/dist/model/authority.d.ts +89 -0
  138. package/dist/model/authority.d.ts.map +1 -0
  139. package/dist/model/authority.js +80 -0
  140. package/dist/model/authority.js.map +1 -0
  141. package/dist/model/bun-targets.d.ts +34 -0
  142. package/dist/model/bun-targets.d.ts.map +1 -0
  143. package/dist/model/bun-targets.js +17 -0
  144. package/dist/model/bun-targets.js.map +1 -0
  145. package/dist/model/canonical.d.ts +8 -0
  146. package/dist/model/canonical.d.ts.map +1 -0
  147. package/dist/model/canonical.js +205 -0
  148. package/dist/model/canonical.js.map +1 -0
  149. package/dist/model/catalog.d.ts +76 -0
  150. package/dist/model/catalog.d.ts.map +1 -0
  151. package/dist/model/catalog.js +174 -0
  152. package/dist/model/catalog.js.map +1 -0
  153. package/dist/model/digest.d.ts +53 -0
  154. package/dist/model/digest.d.ts.map +1 -0
  155. package/dist/model/digest.js +127 -0
  156. package/dist/model/digest.js.map +1 -0
  157. package/dist/model/errors.d.ts +14 -0
  158. package/dist/model/errors.d.ts.map +1 -0
  159. package/dist/model/errors.js +8 -0
  160. package/dist/model/errors.js.map +1 -0
  161. package/dist/model/primitives.d.ts +19 -0
  162. package/dist/model/primitives.d.ts.map +1 -0
  163. package/dist/model/primitives.js +18 -0
  164. package/dist/model/primitives.js.map +1 -0
  165. package/dist/model/pypi.d.ts +12 -0
  166. package/dist/model/pypi.d.ts.map +1 -0
  167. package/dist/model/pypi.js +20 -0
  168. package/dist/model/pypi.js.map +1 -0
  169. package/dist/model/python-distribution.d.ts +33 -0
  170. package/dist/model/python-distribution.d.ts.map +1 -0
  171. package/dist/model/python-distribution.js +367 -0
  172. package/dist/model/python-distribution.js.map +1 -0
  173. package/dist/model/secret-patterns.d.ts +2 -0
  174. package/dist/model/secret-patterns.d.ts.map +1 -0
  175. package/dist/model/secret-patterns.js +12 -0
  176. package/dist/model/secret-patterns.js.map +1 -0
  177. package/dist/platform/bun.d.ts +7 -0
  178. package/dist/platform/bun.d.ts.map +1 -0
  179. package/dist/platform/bun.js +15 -0
  180. package/dist/platform/bun.js.map +1 -0
  181. package/dist/platform/credentials.d.ts +32 -0
  182. package/dist/platform/credentials.d.ts.map +1 -0
  183. package/dist/platform/credentials.js +772 -0
  184. package/dist/platform/credentials.js.map +1 -0
  185. package/dist/platform/host-support.d.ts +2 -0
  186. package/dist/platform/host-support.d.ts.map +1 -0
  187. package/dist/platform/host-support.js +4 -0
  188. package/dist/platform/host-support.js.map +1 -0
  189. package/dist/platform/node.d.ts +7 -0
  190. package/dist/platform/node.d.ts.map +1 -0
  191. package/dist/platform/node.js +15 -0
  192. package/dist/platform/node.js.map +1 -0
  193. package/dist/platform/release-runtime.d.ts +11 -0
  194. package/dist/platform/release-runtime.d.ts.map +1 -0
  195. package/dist/platform/release-runtime.js +34 -0
  196. package/dist/platform/release-runtime.js.map +1 -0
  197. package/dist/platform/services.d.ts +15 -0
  198. package/dist/platform/services.d.ts.map +1 -0
  199. package/dist/platform/services.js +30 -0
  200. package/dist/platform/services.js.map +1 -0
  201. package/dist/platform/source-observer.d.ts +5 -0
  202. package/dist/platform/source-observer.d.ts.map +1 -0
  203. package/dist/platform/source-observer.js +159 -0
  204. package/dist/platform/source-observer.js.map +1 -0
  205. package/dist/provider-sdk.d.ts +14 -0
  206. package/dist/provider-sdk.d.ts.map +1 -0
  207. package/dist/provider-sdk.js +10 -0
  208. package/dist/provider-sdk.js.map +1 -0
  209. package/dist/publication/adapter.d.ts +212 -0
  210. package/dist/publication/adapter.d.ts.map +1 -0
  211. package/dist/publication/adapter.js +83 -0
  212. package/dist/publication/adapter.js.map +1 -0
  213. package/dist/publication/authority.d.ts +137 -0
  214. package/dist/publication/authority.d.ts.map +1 -0
  215. package/dist/publication/authority.js +233 -0
  216. package/dist/publication/authority.js.map +1 -0
  217. package/dist/publication/catalog-git.d.ts +37 -0
  218. package/dist/publication/catalog-git.d.ts.map +1 -0
  219. package/dist/publication/catalog-git.js +451 -0
  220. package/dist/publication/catalog-git.js.map +1 -0
  221. package/dist/publication/claim.d.ts +39 -0
  222. package/dist/publication/claim.d.ts.map +1 -0
  223. package/dist/publication/claim.js +31 -0
  224. package/dist/publication/claim.js.map +1 -0
  225. package/dist/publication/coordinator.d.ts +246 -0
  226. package/dist/publication/coordinator.d.ts.map +1 -0
  227. package/dist/publication/coordinator.js +370 -0
  228. package/dist/publication/coordinator.js.map +1 -0
  229. package/dist/publication/github.d.ts +36 -0
  230. package/dist/publication/github.d.ts.map +1 -0
  231. package/dist/publication/github.js +644 -0
  232. package/dist/publication/github.js.map +1 -0
  233. package/dist/publication/http.d.ts +69 -0
  234. package/dist/publication/http.d.ts.map +1 -0
  235. package/dist/publication/http.js +29 -0
  236. package/dist/publication/http.js.map +1 -0
  237. package/dist/publication/index.d.ts +5 -0
  238. package/dist/publication/index.d.ts.map +1 -0
  239. package/dist/publication/index.js +5 -0
  240. package/dist/publication/index.js.map +1 -0
  241. package/dist/publication/npm.d.ts +40 -0
  242. package/dist/publication/npm.d.ts.map +1 -0
  243. package/dist/publication/npm.js +359 -0
  244. package/dist/publication/npm.js.map +1 -0
  245. package/dist/publication/profiles.d.ts +136 -0
  246. package/dist/publication/profiles.d.ts.map +1 -0
  247. package/dist/publication/profiles.js +90 -0
  248. package/dist/publication/profiles.js.map +1 -0
  249. package/dist/publication/publisher.d.ts +82 -0
  250. package/dist/publication/publisher.d.ts.map +1 -0
  251. package/dist/publication/publisher.js +53 -0
  252. package/dist/publication/publisher.js.map +1 -0
  253. package/dist/publication/pypi.d.ts +45 -0
  254. package/dist/publication/pypi.d.ts.map +1 -0
  255. package/dist/publication/pypi.js +369 -0
  256. package/dist/publication/pypi.js.map +1 -0
  257. package/dist/publication/recovery.d.ts +663 -0
  258. package/dist/publication/recovery.d.ts.map +1 -0
  259. package/dist/publication/recovery.js +252 -0
  260. package/dist/publication/recovery.js.map +1 -0
  261. package/dist/publication/report.d.ts +1061 -0
  262. package/dist/publication/report.d.ts.map +1 -0
  263. package/dist/publication/report.js +441 -0
  264. package/dist/publication/report.js.map +1 -0
  265. package/dist/recipes/config.d.ts +296 -0
  266. package/dist/recipes/config.d.ts.map +1 -0
  267. package/dist/recipes/config.js +325 -0
  268. package/dist/recipes/config.js.map +1 -0
  269. package/dist/release/capabilities.d.ts +12 -0
  270. package/dist/release/capabilities.d.ts.map +1 -0
  271. package/dist/release/capabilities.js +395 -0
  272. package/dist/release/capabilities.js.map +1 -0
  273. package/dist/release/compiler.d.ts +5 -0
  274. package/dist/release/compiler.d.ts.map +1 -0
  275. package/dist/release/compiler.js +19 -0
  276. package/dist/release/compiler.js.map +1 -0
  277. package/dist/release/config.d.ts +5 -0
  278. package/dist/release/config.d.ts.map +1 -0
  279. package/dist/release/config.js +10 -0
  280. package/dist/release/config.js.map +1 -0
  281. package/dist/release/context.d.ts +96 -0
  282. package/dist/release/context.d.ts.map +1 -0
  283. package/dist/release/context.js +168 -0
  284. package/dist/release/context.js.map +1 -0
  285. package/dist/release/graph.d.ts +271 -0
  286. package/dist/release/graph.d.ts.map +1 -0
  287. package/dist/release/graph.js +766 -0
  288. package/dist/release/graph.js.map +1 -0
  289. package/dist/release/inspect.d.ts +88 -0
  290. package/dist/release/inspect.d.ts.map +1 -0
  291. package/dist/release/inspect.js +75 -0
  292. package/dist/release/inspect.js.map +1 -0
  293. package/dist/release/prepare.d.ts +24 -0
  294. package/dist/release/prepare.d.ts.map +1 -0
  295. package/dist/release/prepare.js +1116 -0
  296. package/dist/release/prepare.js.map +1 -0
  297. package/dist/release/prepared-ref.d.ts +60 -0
  298. package/dist/release/prepared-ref.d.ts.map +1 -0
  299. package/dist/release/prepared-ref.js +131 -0
  300. package/dist/release/prepared-ref.js.map +1 -0
  301. package/dist/release/prepared-store.d.ts +90 -0
  302. package/dist/release/prepared-store.d.ts.map +1 -0
  303. package/dist/release/prepared-store.js +337 -0
  304. package/dist/release/prepared-store.js.map +1 -0
  305. package/dist/release/prepared.d.ts +172 -0
  306. package/dist/release/prepared.d.ts.map +1 -0
  307. package/dist/release/prepared.js +268 -0
  308. package/dist/release/prepared.js.map +1 -0
  309. package/dist/release/staging.d.ts +31 -0
  310. package/dist/release/staging.d.ts.map +1 -0
  311. package/dist/release/staging.js +218 -0
  312. package/dist/release/staging.js.map +1 -0
  313. package/dist/resolve/authored.d.ts +53 -0
  314. package/dist/resolve/authored.d.ts.map +1 -0
  315. package/dist/resolve/authored.js +55 -0
  316. package/dist/resolve/authored.js.map +1 -0
  317. package/dist/resolve/encode.d.ts +3 -0
  318. package/dist/resolve/encode.d.ts.map +1 -0
  319. package/dist/resolve/encode.js +21 -0
  320. package/dist/resolve/encode.js.map +1 -0
  321. package/dist/resolve/errors.d.ts +7 -0
  322. package/dist/resolve/errors.d.ts.map +1 -0
  323. package/dist/resolve/errors.js +17 -0
  324. package/dist/resolve/errors.js.map +1 -0
  325. package/dist/resolve/facts.d.ts +12 -0
  326. package/dist/resolve/facts.d.ts.map +1 -0
  327. package/dist/resolve/facts.js +18 -0
  328. package/dist/resolve/facts.js.map +1 -0
  329. package/dist/resolve/resolve.d.ts +10 -0
  330. package/dist/resolve/resolve.d.ts.map +1 -0
  331. package/dist/resolve/resolve.js +242 -0
  332. package/dist/resolve/resolve.js.map +1 -0
  333. package/dist/store.d.ts +11 -0
  334. package/dist/store.d.ts.map +1 -0
  335. package/dist/store.js +9 -0
  336. package/dist/store.js.map +1 -0
  337. package/examples/README.md +20 -30
  338. package/examples/github-release/release.config.json +11 -18
  339. package/examples/homebrew-tap/README.md +11 -0
  340. package/examples/homebrew-tap/release.config.json +40 -28
  341. package/examples/multi-target/release.config.json +28 -53
  342. package/examples/npm-first-publish/release.config.json +23 -25
  343. package/examples/npm-only/release.config.json +22 -30
  344. package/examples/portable-cli/README.md +17 -0
  345. package/examples/portable-cli/artifacts/portable-example-0.1.0-darwin-arm64 +1 -0
  346. package/examples/portable-cli/artifacts/portable-example-0.1.0-darwin-x64 +1 -0
  347. package/examples/portable-cli/artifacts/portable-example-0.1.0-linux-arm64 +1 -0
  348. package/examples/portable-cli/artifacts/portable-example-0.1.0-linux-x64 +1 -0
  349. package/examples/portable-cli/artifacts/portable-example-0.1.0-windows-x64.exe +1 -0
  350. package/examples/portable-cli/artifacts/portable_example-0.1.0-py3-none-macosx_10_15_x86_64.whl +1 -0
  351. package/examples/portable-cli/artifacts/portable_example-0.1.0-py3-none-macosx_11_0_arm64.whl +1 -0
  352. package/examples/portable-cli/artifacts/portable_example-0.1.0-py3-none-manylinux2014_aarch64.whl +1 -0
  353. package/examples/portable-cli/artifacts/portable_example-0.1.0-py3-none-manylinux2014_x86_64.whl +1 -0
  354. package/examples/portable-cli/artifacts/portable_example-0.1.0-py3-none-win_amd64.whl +1 -0
  355. package/examples/portable-cli/package.json +9 -0
  356. package/examples/portable-cli/release.config.json +53 -0
  357. package/examples/portable-cli/src/cli.ts +3 -0
  358. package/examples/pypi-registry/README.md +8 -0
  359. package/examples/scoop-bucket/README.md +10 -0
  360. package/examples/scoop-bucket/release.config.json +40 -30
  361. package/package.json +60 -156
  362. package/src/api/api.ts +365 -0
  363. package/src/api/errors.ts +36 -0
  364. package/src/api/input.ts +92 -0
  365. package/src/api/runtime.ts +14 -0
  366. package/src/api/types.ts +103 -0
  367. package/src/capabilities/bun-targets.ts +2 -0
  368. package/src/capabilities/field-ownership.ts +60 -0
  369. package/src/capabilities/module.ts +158 -0
  370. package/src/capabilities/registry.ts +333 -0
  371. package/src/config/config.ts +48 -0
  372. package/src/correction/coordinator.ts +336 -0
  373. package/src/correction/index.ts +2 -0
  374. package/src/correction/intent.ts +172 -0
  375. package/src/drivers/archive.ts +102 -0
  376. package/src/drivers/contain.ts +20 -0
  377. package/src/drivers/environment.ts +28 -0
  378. package/src/drivers/errors.ts +6 -0
  379. package/src/drivers/glob.ts +137 -0
  380. package/src/drivers/process.ts +340 -0
  381. package/src/drivers/redact.ts +31 -0
  382. package/src/drivers/seccomp-helper-source.ts +121 -0
  383. package/src/drivers/utils.ts +10 -0
  384. package/src/drivers/workspace.ts +40 -0
  385. package/src/extensions/provider-adapter.ts +99 -0
  386. package/src/host.ts +140 -0
  387. package/src/index.ts +64 -0
  388. package/src/model/artifact-collection.ts +237 -0
  389. package/src/model/authority.ts +116 -0
  390. package/src/model/bun-targets.ts +34 -0
  391. package/src/model/canonical.ts +205 -0
  392. package/src/model/catalog.ts +220 -0
  393. package/src/model/digest.ts +172 -0
  394. package/src/model/errors.ts +6 -0
  395. package/src/model/primitives.ts +37 -0
  396. package/src/model/pypi.ts +30 -0
  397. package/src/model/python-distribution.ts +360 -0
  398. package/src/model/secret-patterns.ts +11 -0
  399. package/src/platform/bun.ts +38 -0
  400. package/src/platform/credentials.ts +1083 -0
  401. package/src/platform/host-support.ts +4 -0
  402. package/src/platform/node.ts +38 -0
  403. package/src/platform/release-runtime.ts +47 -0
  404. package/src/platform/services.ts +57 -0
  405. package/src/platform/source-observer.ts +193 -0
  406. package/src/provider-sdk.ts +48 -0
  407. package/src/publication/adapter.ts +132 -0
  408. package/src/publication/authority.ts +397 -0
  409. package/src/publication/catalog-git.ts +603 -0
  410. package/src/publication/claim.ts +51 -0
  411. package/src/publication/coordinator.ts +578 -0
  412. package/src/publication/github.ts +954 -0
  413. package/src/publication/http.ts +101 -0
  414. package/src/publication/index.ts +4 -0
  415. package/src/publication/npm.ts +525 -0
  416. package/src/publication/profiles.ts +94 -0
  417. package/src/publication/publisher.ts +130 -0
  418. package/src/publication/pypi.ts +463 -0
  419. package/src/publication/recovery.ts +349 -0
  420. package/src/publication/report.ts +628 -0
  421. package/src/recipes/config.ts +395 -0
  422. package/src/release/capabilities.ts +459 -0
  423. package/src/release/compiler.ts +28 -0
  424. package/src/release/config.ts +12 -0
  425. package/src/release/context.ts +223 -0
  426. package/src/release/graph.ts +866 -0
  427. package/src/release/inspect.ts +83 -0
  428. package/src/release/prepare.ts +1331 -0
  429. package/src/release/prepared-ref.ts +193 -0
  430. package/src/release/prepared-store.ts +377 -0
  431. package/src/release/prepared.ts +299 -0
  432. package/src/release/staging.ts +257 -0
  433. package/src/resolve/authored.ts +64 -0
  434. package/src/resolve/encode.ts +22 -0
  435. package/src/resolve/errors.ts +15 -0
  436. package/src/resolve/facts.ts +18 -0
  437. package/src/resolve/resolve.ts +328 -0
  438. package/src/store.ts +28 -0
  439. package/templates/README.md +55 -72
  440. package/templates/bun-cli-github/README.md +7 -9
  441. package/templates/bun-cli-github/release.config.json +35 -73
  442. package/templates/github-actions/release.yml +57 -0
  443. package/templates/github-actions/reviewed-release.yml +80 -0
  444. package/templates/multi-target-homebrew/README.md +10 -0
  445. package/templates/multi-target-homebrew/release.config.json +43 -60
  446. package/templates/multi-target-scoop/README.md +10 -0
  447. package/templates/multi-target-scoop/release.config.json +43 -62
  448. package/templates/npm-github/release.config.json +25 -38
  449. package/templates/npm-github/reviewed-release.config.json +37 -0
  450. package/templates/npm-only/README.md +10 -0
  451. package/templates/npm-only/release.config.json +15 -30
  452. package/templates/portable-cli/README.md +13 -0
  453. package/templates/portable-cli/release.config.json +53 -0
  454. package/dist/artifacts/adapter.d.ts +0 -39
  455. package/dist/artifacts/adapter.d.ts.map +0 -1
  456. package/dist/artifacts/adapter.js +0 -23
  457. package/dist/artifacts/adapter.js.map +0 -1
  458. package/dist/artifacts/registry.d.ts +0 -21
  459. package/dist/artifacts/registry.d.ts.map +0 -1
  460. package/dist/artifacts/registry.js +0 -21
  461. package/dist/artifacts/registry.js.map +0 -1
  462. package/dist/config/errors.d.ts +0 -24
  463. package/dist/config/errors.d.ts.map +0 -1
  464. package/dist/config/errors.js +0 -19
  465. package/dist/config/errors.js.map +0 -1
  466. package/dist/config/load.d.ts +0 -9
  467. package/dist/config/load.d.ts.map +0 -1
  468. package/dist/config/load.js +0 -29
  469. package/dist/config/load.js.map +0 -1
  470. package/dist/config/schema.d.ts +0 -12
  471. package/dist/config/schema.d.ts.map +0 -1
  472. package/dist/config/schema.js +0 -19
  473. package/dist/config/schema.js.map +0 -1
  474. package/dist/domain/artifact.d.ts +0 -118
  475. package/dist/domain/artifact.d.ts.map +0 -1
  476. package/dist/domain/artifact.js +0 -197
  477. package/dist/domain/artifact.js.map +0 -1
  478. package/dist/domain/evidence.d.ts +0 -59
  479. package/dist/domain/evidence.d.ts.map +0 -1
  480. package/dist/domain/evidence.js +0 -51
  481. package/dist/domain/evidence.js.map +0 -1
  482. package/dist/domain/operation.d.ts +0 -143
  483. package/dist/domain/operation.d.ts.map +0 -1
  484. package/dist/domain/operation.js +0 -198
  485. package/dist/domain/operation.js.map +0 -1
  486. package/dist/domain/release.d.ts +0 -93
  487. package/dist/domain/release.d.ts.map +0 -1
  488. package/dist/domain/release.js +0 -82
  489. package/dist/domain/release.js.map +0 -1
  490. package/dist/domain/remote-state.d.ts +0 -58
  491. package/dist/domain/remote-state.d.ts.map +0 -1
  492. package/dist/domain/remote-state.js +0 -55
  493. package/dist/domain/remote-state.js.map +0 -1
  494. package/dist/domain/target.d.ts +0 -148
  495. package/dist/domain/target.d.ts.map +0 -1
  496. package/dist/domain/target.js +0 -151
  497. package/dist/domain/target.js.map +0 -1
  498. package/dist/host/host.d.ts +0 -32
  499. package/dist/host/host.d.ts.map +0 -1
  500. package/dist/host/host.js +0 -24
  501. package/dist/host/host.js.map +0 -1
  502. package/dist/host/http-live.d.ts +0 -6
  503. package/dist/host/http-live.d.ts.map +0 -1
  504. package/dist/host/http-live.js +0 -86
  505. package/dist/host/http-live.js.map +0 -1
  506. package/dist/host/http.d.ts +0 -43
  507. package/dist/host/http.d.ts.map +0 -1
  508. package/dist/host/http.js +0 -59
  509. package/dist/host/http.js.map +0 -1
  510. package/dist/host/platform.d.ts +0 -10
  511. package/dist/host/platform.d.ts.map +0 -1
  512. package/dist/host/platform.js +0 -114
  513. package/dist/host/platform.js.map +0 -1
  514. package/dist/host/test.d.ts +0 -23
  515. package/dist/host/test.d.ts.map +0 -1
  516. package/dist/host/test.js +0 -222
  517. package/dist/host/test.js.map +0 -1
  518. package/dist/internal/workspace-path.d.ts +0 -15
  519. package/dist/internal/workspace-path.d.ts.map +0 -1
  520. package/dist/internal/workspace-path.js +0 -36
  521. package/dist/internal/workspace-path.js.map +0 -1
  522. package/dist/planner/artifact-inventory.d.ts +0 -9
  523. package/dist/planner/artifact-inventory.d.ts.map +0 -1
  524. package/dist/planner/artifact-inventory.js +0 -86
  525. package/dist/planner/artifact-inventory.js.map +0 -1
  526. package/dist/planner/create-release-plan.d.ts +0 -5
  527. package/dist/planner/create-release-plan.d.ts.map +0 -1
  528. package/dist/planner/create-release-plan.js +0 -25
  529. package/dist/planner/create-release-plan.js.map +0 -1
  530. package/dist/planner/errors.d.ts +0 -67
  531. package/dist/planner/errors.d.ts.map +0 -1
  532. package/dist/planner/errors.js +0 -58
  533. package/dist/planner/errors.js.map +0 -1
  534. package/dist/planner/evidence-recorder.d.ts +0 -28
  535. package/dist/planner/evidence-recorder.d.ts.map +0 -1
  536. package/dist/planner/evidence-recorder.js +0 -362
  537. package/dist/planner/evidence-recorder.js.map +0 -1
  538. package/dist/planner/executor.d.ts +0 -58
  539. package/dist/planner/executor.d.ts.map +0 -1
  540. package/dist/planner/executor.js +0 -147
  541. package/dist/planner/executor.js.map +0 -1
  542. package/dist/planner/normalize-release.d.ts +0 -19
  543. package/dist/planner/normalize-release.d.ts.map +0 -1
  544. package/dist/planner/normalize-release.js +0 -342
  545. package/dist/planner/normalize-release.js.map +0 -1
  546. package/dist/planner/reconcile.d.ts +0 -17
  547. package/dist/planner/reconcile.d.ts.map +0 -1
  548. package/dist/planner/reconcile.js +0 -259
  549. package/dist/planner/reconcile.js.map +0 -1
  550. package/dist/planner/render-plan.d.ts +0 -15
  551. package/dist/planner/render-plan.d.ts.map +0 -1
  552. package/dist/planner/render-plan.js +0 -231
  553. package/dist/planner/render-plan.js.map +0 -1
  554. package/dist/targets/adapter-helpers.d.ts +0 -79
  555. package/dist/targets/adapter-helpers.d.ts.map +0 -1
  556. package/dist/targets/adapter-helpers.js +0 -194
  557. package/dist/targets/adapter-helpers.js.map +0 -1
  558. package/dist/targets/adapter.d.ts +0 -17
  559. package/dist/targets/adapter.d.ts.map +0 -1
  560. package/dist/targets/adapter.js +0 -2
  561. package/dist/targets/adapter.js.map +0 -1
  562. package/dist/targets/github-release.d.ts +0 -25
  563. package/dist/targets/github-release.d.ts.map +0 -1
  564. package/dist/targets/github-release.js +0 -82
  565. package/dist/targets/github-release.js.map +0 -1
  566. package/dist/targets/github.d.ts +0 -11
  567. package/dist/targets/github.d.ts.map +0 -1
  568. package/dist/targets/github.js +0 -117
  569. package/dist/targets/github.js.map +0 -1
  570. package/dist/targets/homebrew.d.ts +0 -11
  571. package/dist/targets/homebrew.d.ts.map +0 -1
  572. package/dist/targets/homebrew.js +0 -210
  573. package/dist/targets/homebrew.js.map +0 -1
  574. package/dist/targets/live.d.ts +0 -5
  575. package/dist/targets/live.d.ts.map +0 -1
  576. package/dist/targets/live.js +0 -50
  577. package/dist/targets/live.js.map +0 -1
  578. package/dist/targets/npm.d.ts +0 -10
  579. package/dist/targets/npm.d.ts.map +0 -1
  580. package/dist/targets/npm.js +0 -112
  581. package/dist/targets/npm.js.map +0 -1
  582. package/dist/targets/pypi.d.ts +0 -11
  583. package/dist/targets/pypi.d.ts.map +0 -1
  584. package/dist/targets/pypi.js +0 -141
  585. package/dist/targets/pypi.js.map +0 -1
  586. package/dist/targets/registry.d.ts +0 -25
  587. package/dist/targets/registry.d.ts.map +0 -1
  588. package/dist/targets/registry.js +0 -34
  589. package/dist/targets/registry.js.map +0 -1
  590. package/dist/targets/scoop.d.ts +0 -11
  591. package/dist/targets/scoop.d.ts.map +0 -1
  592. package/dist/targets/scoop.js +0 -88
  593. package/dist/targets/scoop.js.map +0 -1
  594. package/dist/types/effect-internal.d.ts +0 -5
  595. package/dist/types/effect-internal.d.ts.map +0 -1
  596. package/dist/types/effect-internal.js +0 -2
  597. package/dist/types/effect-internal.js.map +0 -1
  598. package/dist/workflows/config.d.ts +0 -202
  599. package/dist/workflows/config.d.ts.map +0 -1
  600. package/dist/workflows/config.js +0 -420
  601. package/dist/workflows/config.js.map +0 -1
  602. package/dist/workflows/diagnostics.d.ts +0 -62
  603. package/dist/workflows/diagnostics.d.ts.map +0 -1
  604. package/dist/workflows/diagnostics.js +0 -530
  605. package/dist/workflows/diagnostics.js.map +0 -1
  606. package/dist/workflows/distribution.d.ts +0 -25
  607. package/dist/workflows/distribution.d.ts.map +0 -1
  608. package/dist/workflows/distribution.js +0 -24
  609. package/dist/workflows/distribution.js.map +0 -1
  610. package/dist/workflows/evidence.d.ts +0 -17
  611. package/dist/workflows/evidence.d.ts.map +0 -1
  612. package/dist/workflows/evidence.js +0 -32
  613. package/dist/workflows/evidence.js.map +0 -1
  614. package/dist/workflows/index.d.ts +0 -8
  615. package/dist/workflows/index.d.ts.map +0 -1
  616. package/dist/workflows/index.js +0 -7
  617. package/dist/workflows/index.js.map +0 -1
  618. package/dist/workflows/init.d.ts +0 -84
  619. package/dist/workflows/init.d.ts.map +0 -1
  620. package/dist/workflows/init.js +0 -487
  621. package/dist/workflows/init.js.map +0 -1
  622. package/dist/workflows/live.d.ts +0 -9
  623. package/dist/workflows/live.d.ts.map +0 -1
  624. package/dist/workflows/live.js +0 -7
  625. package/dist/workflows/live.js.map +0 -1
  626. package/dist/workflows/options.d.ts +0 -30
  627. package/dist/workflows/options.d.ts.map +0 -1
  628. package/dist/workflows/options.js +0 -16
  629. package/dist/workflows/options.js.map +0 -1
  630. package/examples/non-strict-skips/artifacts/release-example-skips-0.1.0.tgz +0 -1
  631. package/examples/non-strict-skips/release.config.json +0 -31
  632. package/examples/pypi-registry/release.config.json +0 -31
  633. package/templates/github-actions/plan-and-approved-execute.yml +0 -58
  634. package/templates/github-actions/plan-only.yml +0 -33
  635. package/templates/github-actions/trusted-publishing.yml +0 -61
@@ -0,0 +1,866 @@
1
+ import * as Schema from "effect/Schema"
2
+ import * as Semver from "semver"
3
+ import {
4
+ AnonymousAuthStrategy,
5
+ CanonicalAudience,
6
+ CredentialRef,
7
+ EnvironmentName,
8
+ ExternalTrustedPublishingAuthStrategy,
9
+ ProviderId,
10
+ ResolvedAuthStrategy,
11
+ SubjectId,
12
+ TokenAuthStrategy,
13
+ TrustedPublishingAuthStrategy,
14
+ TrustedPublishingSourceCommit
15
+ } from "../model/authority.js"
16
+ import {
17
+ ArtifactCollectionContract,
18
+ ArtifactCollectionSelector,
19
+ cardinalitiesOverlap,
20
+ cardinalityIssue,
21
+ collectionContractIssue
22
+ } from "../model/artifact-collection.js"
23
+ import {
24
+ CatalogRenderSource,
25
+ CatalogRenderer,
26
+ CatalogRepositoryPath,
27
+ GitBranchName,
28
+ GitHubRepositoryCoordinate
29
+ } from "../model/catalog.js"
30
+ import { NonEmptyName, OperationId, OutputId, SafeRelativePath, Version } from "../model/primitives.js"
31
+ import {
32
+ CanonicalNpmRegistryEndpoint,
33
+ NpmAccess,
34
+ NpmAuthentication,
35
+ NpmDistTag,
36
+ NpmProvenancePolicy,
37
+ PyPiAuthentication,
38
+ PyPiProjectName,
39
+ PyPiRepository,
40
+ canonicalizeNpmRegistryEndpoint,
41
+ normalizePyPiProjectName,
42
+ pypiRepositoryEndpoints,
43
+ type NpmAuthentication as NpmAuthenticationValue,
44
+ type PyPiAuthentication as PyPiAuthenticationValue
45
+ } from "../recipes/config.js"
46
+
47
+ export class OutputDeclaration extends Schema.Class<OutputDeclaration>("OutputDeclaration")({
48
+ id: OutputId, path: SafeRelativePath,
49
+ kind: Schema.Literals(["file", "executable", "archive", "digest", "package"]),
50
+ mediaType: Schema.optionalKey(Schema.NonEmptyString)
51
+ }) {}
52
+ const optional = Schema.optionalKey
53
+ const argv = Schema.NonEmptyArray(Schema.String)
54
+
55
+ export class GraphCommandCheck extends Schema.TaggedClass<GraphCommandCheck>()("GraphCommandCheck", {
56
+ id: OperationId, argv, cwd: SafeRelativePath, inputs: Schema.Array(OutputId)
57
+ }) {}
58
+
59
+ export class GraphCommandArtifact extends Schema.TaggedClass<GraphCommandArtifact>()("GraphCommandArtifact", {
60
+ id: OperationId, argv, cwd: SafeRelativePath,
61
+ inputs: Schema.Array(OutputId), outputs: Schema.NonEmptyArray(OutputDeclaration)
62
+ }) {}
63
+
64
+ export class GraphCommandCollection extends Schema.TaggedClass<GraphCommandCollection>()("GraphCommandCollection", {
65
+ id: OperationId, argv, cwd: SafeRelativePath,
66
+ inputs: Schema.Array(OutputId), collection: ArtifactCollectionContract
67
+ }) {}
68
+
69
+ /** Package-scoped build whose directory outputs are consumed only by npm pack. */
70
+ export class GraphNpmPackageBuild extends Schema.TaggedClass<GraphNpmPackageBuild>()("GraphNpmPackageBuild", {
71
+ id: OperationId,
72
+ argv,
73
+ cwd: SafeRelativePath,
74
+ inputs: Schema.Array(OutputId),
75
+ outputRoots: Schema.NonEmptyArray(SafeRelativePath)
76
+ }) {}
77
+
78
+ export class GraphArchive extends Schema.TaggedClass<GraphArchive>()("GraphArchive", {
79
+ id: OperationId, inputs: Schema.Array(OutputId), output: OutputDeclaration,
80
+ format: Schema.Literals(["tar.gz", "zip"])
81
+ }) {}
82
+
83
+ export class GraphChecksum extends Schema.TaggedClass<GraphChecksum>()("GraphChecksum", {
84
+ id: OperationId, inputs: Schema.NonEmptyArray(OutputId), output: OutputDeclaration,
85
+ algorithm: Schema.Literals(["sha256", "sha512"])
86
+ }) {}
87
+
88
+ export class GraphCatalogRender extends Schema.TaggedClass<GraphCatalogRender>()("GraphCatalogRender", {
89
+ id: OperationId,
90
+ inputs: Schema.NonEmptyArray(OutputId),
91
+ output: OutputDeclaration,
92
+ version: Version,
93
+ renderer: CatalogRenderer,
94
+ sources: Schema.NonEmptyArray(CatalogRenderSource)
95
+ }) {}
96
+
97
+ export const GraphPreparation = Schema.Union([
98
+ GraphCommandCheck, GraphCommandArtifact, GraphCommandCollection, GraphNpmPackageBuild,
99
+ GraphArchive, GraphChecksum, GraphCatalogRender
100
+ ])
101
+ export type GraphPreparation = typeof GraphPreparation.Type
102
+
103
+ /** Safe, durable authority intent resolved before a publication enters the graph. */
104
+ export class PublicationAuthorityIntent
105
+ extends Schema.Class<PublicationAuthorityIntent>("PublicationAuthorityIntent")({
106
+ subject: SubjectId,
107
+ provider: ProviderId,
108
+ audience: CanonicalAudience,
109
+ observationStrategies: Schema.NonEmptyArray(ResolvedAuthStrategy),
110
+ publishStrategy: ResolvedAuthStrategy
111
+ }) {}
112
+
113
+ export class GraphNpmPublication extends Schema.TaggedClass<GraphNpmPublication>()("GraphNpmPublication", {
114
+ id: OperationId,
115
+ packageArtifact: OutputId,
116
+ packageName: NonEmptyName,
117
+ version: Version,
118
+ registryUrl: CanonicalNpmRegistryEndpoint,
119
+ distTag: NpmDistTag,
120
+ access: NpmAccess,
121
+ authentication: NpmAuthentication,
122
+ provenance: NpmProvenancePolicy,
123
+ authority: PublicationAuthorityIntent
124
+ }) {}
125
+
126
+ export class GraphGitHubPublication extends Schema.TaggedClass<GraphGitHubPublication>()("GraphGitHubPublication", {
127
+ id: OperationId, repository: Schema.NonEmptyString, tag: NonEmptyName, title: NonEmptyName,
128
+ draft: Schema.Boolean, prerelease: Schema.Boolean, body: optional(Schema.String), bodyArtifact: optional(OutputId),
129
+ assetIds: Schema.Array(OutputId), assetCollections: Schema.Array(ArtifactCollectionSelector),
130
+ authority: PublicationAuthorityIntent
131
+ }) {}
132
+
133
+ export class GraphPyPiDistribution extends Schema.Class<GraphPyPiDistribution>("GraphPyPiDistribution")({
134
+ artifactId: OutputId,
135
+ filename: NonEmptyName,
136
+ authority: PublicationAuthorityIntent
137
+ }) {}
138
+
139
+ export class GraphPyPiPublication extends Schema.TaggedClass<GraphPyPiPublication>()("GraphPyPiPublication", {
140
+ id: OperationId,
141
+ project: PyPiProjectName,
142
+ version: Version,
143
+ repository: PyPiRepository,
144
+ simpleBaseUrl: Schema.NonEmptyString,
145
+ projectUrl: Schema.NonEmptyString,
146
+ uploadUrl: Schema.NonEmptyString,
147
+ authentication: PyPiAuthentication,
148
+ files: Schema.NonEmptyArray(GraphPyPiDistribution)
149
+ }) {}
150
+
151
+ export class GraphCatalogPublication extends Schema.TaggedClass<GraphCatalogPublication>()("GraphCatalogPublication", {
152
+ id: OperationId,
153
+ catalogId: NonEmptyName,
154
+ targetArtifact: OutputId,
155
+ repository: GitHubRepositoryCoordinate,
156
+ branch: GitBranchName,
157
+ targetPath: CatalogRepositoryPath,
158
+ statePath: CatalogRepositoryPath,
159
+ version: Version,
160
+ sourceRepository: GitHubRepositoryCoordinate,
161
+ sourceTag: NonEmptyName,
162
+ renderer: CatalogRenderer,
163
+ sources: Schema.NonEmptyArray(CatalogRenderSource),
164
+ authority: PublicationAuthorityIntent
165
+ }) {}
166
+
167
+ export const GraphPublication = Schema.Union([
168
+ GraphNpmPublication, GraphPyPiPublication, GraphGitHubPublication, GraphCatalogPublication
169
+ ])
170
+ export type GraphPublication = typeof GraphPublication.Type
171
+
172
+ export class CapabilityContribution extends Schema.Class<CapabilityContribution>("CapabilityContribution")({
173
+ artifacts: Schema.Array(OutputDeclaration), preparations: Schema.Array(GraphPreparation),
174
+ publications: Schema.Array(GraphPublication)
175
+ }) {}
176
+
177
+ export class ReleaseGraph extends Schema.Class<ReleaseGraph>("ReleaseGraph")({
178
+ artifacts: Schema.Array(OutputDeclaration), collections: Schema.Array(ArtifactCollectionContract),
179
+ preparations: Schema.Array(GraphPreparation),
180
+ publications: Schema.Array(GraphPublication)
181
+ }) {}
182
+
183
+ export class GraphLinkError
184
+ extends Schema.TaggedErrorClass<GraphLinkError>()("GraphLinkError", {
185
+ kind: Schema.Literals(["duplicate", "missing", "cycle", "path", "reference"]),
186
+ value: Schema.String, reason: Schema.String
187
+ }) {}
188
+
189
+ const authorityError = (value: string, reason: string): GraphLinkError =>
190
+ new GraphLinkError({ kind: "reference", value, reason })
191
+
192
+ /**
193
+ * Canonical registry authority includes the complete normalized base path.
194
+ * Query, fragment, and user-info components can never enter a credential
195
+ * audience.
196
+ */
197
+ export const canonicalizeRegistryUrl = (value: string): string => {
198
+ try { return canonicalizeNpmRegistryEndpoint(value) } catch (cause) {
199
+ throw authorityError(
200
+ "publish.npm.registry",
201
+ cause instanceof Error ? cause.message : String(cause)
202
+ )
203
+ }
204
+ }
205
+
206
+ const tokenStrategy = (value: string, field: string): TokenAuthStrategy => {
207
+ let name: EnvironmentName
208
+ try { name = EnvironmentName.make(value) } catch {
209
+ throw authorityError(field, "Credential references derived from tokenEnv must be portable environment variable names.")
210
+ }
211
+ return TokenAuthStrategy.make({ kind: "token", credential: CredentialRef.make(name) })
212
+ }
213
+
214
+ const anonymousStrategy = (): AnonymousAuthStrategy => AnonymousAuthStrategy.make({ kind: "anonymous" })
215
+
216
+ export interface NpmPublicationAuthorityInput {
217
+ readonly packageName: string
218
+ readonly version: string
219
+ readonly registryUrl: string
220
+ readonly distTag: string
221
+ readonly authentication: NpmAuthenticationValue
222
+ readonly sourceCommit?: string
223
+ }
224
+
225
+ /** Resolve npm auth once; no secret value enters the graph or prepared state. */
226
+ export const makeNpmPublicationAuthorityIntent = (
227
+ input: NpmPublicationAuthorityInput
228
+ ): PublicationAuthorityIntent => {
229
+ const version = input.version
230
+ let tag: string
231
+ try { tag = NpmDistTag.make(input.distTag) } catch (cause) {
232
+ throw authorityError(
233
+ "publish.npm.distTag",
234
+ cause instanceof Error ? cause.message : String(cause)
235
+ )
236
+ }
237
+ if (Semver.prerelease(version) !== null && tag === "latest") {
238
+ throw authorityError("publish.npm.distTag", "npm prerelease publication requires a non-latest dist-tag.")
239
+ }
240
+ const provider = ProviderId.make("npm")
241
+ const publishStrategy: ResolvedAuthStrategy = input.authentication.strategy === "token"
242
+ ? tokenStrategy(input.authentication.credential, "publish.npm.authentication.credential")
243
+ : (() => {
244
+ let sourceCommit: TrustedPublishingSourceCommit
245
+ try {
246
+ sourceCommit = TrustedPublishingSourceCommit.make(input.sourceCommit ?? "")
247
+ } catch {
248
+ throw authorityError(
249
+ "source.commit",
250
+ "npm trusted publishing requires a lowercase full Git SHA (40 or 64 hex characters)."
251
+ )
252
+ }
253
+ return TrustedPublishingAuthStrategy.make({
254
+ kind: "trusted-publishing", identityProvider: input.authentication.attestation.provider,
255
+ runnerClass: input.authentication.attestation.runner,
256
+ repository: input.authentication.attestation.repository,
257
+ workflow: `.github/workflows/${input.authentication.attestation.workflow}`,
258
+ workflowRef: input.authentication.attestation.workflowRef,
259
+ sourceCommit,
260
+ provenanceEnvironmentContract: "github-actions-npm-provenance-v1",
261
+ allowedAction: input.authentication.attestation.allowedAction,
262
+ publisherSink: "certified-npm-cli"
263
+ })
264
+ })()
265
+ return PublicationAuthorityIntent.make({
266
+ subject: SubjectId.make(`npm:${input.packageName}@${input.version}`),
267
+ provider,
268
+ audience: CanonicalAudience.make(canonicalizeRegistryUrl(input.registryUrl)),
269
+ // Public registry observation is anonymous. This graph deliberately has
270
+ // no authenticated npm read strategy: custom/private reads remain
271
+ // unsupported until they receive a distinct typed read credential.
272
+ observationStrategies: [anonymousStrategy()],
273
+ publishStrategy
274
+ })
275
+ }
276
+
277
+ export interface PyPiPublicationAuthorityInput {
278
+ readonly project: string
279
+ readonly version: string
280
+ readonly filename: string
281
+ readonly repository: PyPiRepository
282
+ readonly authentication: PyPiAuthenticationValue
283
+ readonly sourceCommit?: string
284
+ }
285
+
286
+ const externalPyPiStrategy = (
287
+ input: PyPiPublicationAuthorityInput,
288
+ authentication: Extract<PyPiAuthenticationValue, { readonly strategy: "trusted-publishing" }>
289
+ ): ExternalTrustedPublishingAuthStrategy => {
290
+ let sourceCommit: TrustedPublishingSourceCommit
291
+ try { sourceCommit = TrustedPublishingSourceCommit.make(input.sourceCommit ?? "") } catch {
292
+ throw authorityError("source.commit", "External PyPI trusted publishing requires a lowercase full Git SHA.")
293
+ }
294
+ return ExternalTrustedPublishingAuthStrategy.make({
295
+ kind: "trusted-publishing",
296
+ identityProvider: "github-actions",
297
+ runnerClass: "github-hosted",
298
+ repository: authentication.repository,
299
+ workflow: `.github/workflows/${authentication.workflow}`,
300
+ workflowRef: authentication.workflowRef,
301
+ sourceCommit,
302
+ provenanceEnvironmentContract: "external-pypa-action-v1",
303
+ allowedAction: "external-pypa-action",
304
+ publisherSink: "external-host-owned",
305
+ environment: authentication.environment,
306
+ projects: authentication.projects.map(normalizePyPiProjectName) as [string, ...Array<string>]
307
+ })
308
+ }
309
+
310
+ /** Exact per-file PyPI authority; OIDC remains external-host-owned. */
311
+ export const makePyPiPublicationAuthorityIntent = (
312
+ input: PyPiPublicationAuthorityInput
313
+ ): PublicationAuthorityIntent => {
314
+ const endpoints = pypiRepositoryEndpoints(input.repository)
315
+ const project = PyPiProjectName.make(normalizePyPiProjectName(input.project))
316
+ const provider = ProviderId.make("pypi")
317
+ const publishStrategy: ResolvedAuthStrategy = input.authentication.strategy === "token"
318
+ ? tokenStrategy(input.authentication.credential, "publish.pypi.authentication.credential")
319
+ : externalPyPiStrategy(input, input.authentication)
320
+ return PublicationAuthorityIntent.make({
321
+ subject: SubjectId.make(`pypi:${input.repository}:${project}@${input.version}#${input.filename}`),
322
+ provider,
323
+ audience: CanonicalAudience.make(endpoints.uploadUrl),
324
+ observationStrategies: [anonymousStrategy()],
325
+ publishStrategy
326
+ })
327
+ }
328
+
329
+ export const makeCatalogPublicationAuthorityIntent = (input: {
330
+ readonly repository: string
331
+ readonly branch: string
332
+ readonly targetPath: string
333
+ readonly statePath: string
334
+ readonly tokenEnv: string
335
+ }): PublicationAuthorityIntent => {
336
+ let repository: GitHubRepositoryCoordinate
337
+ let branch: GitBranchName
338
+ let targetPath: CatalogRepositoryPath
339
+ let statePath: CatalogRepositoryPath
340
+ try {
341
+ repository = GitHubRepositoryCoordinate.make(input.repository)
342
+ branch = GitBranchName.make(input.branch)
343
+ targetPath = CatalogRepositoryPath.make(input.targetPath)
344
+ statePath = CatalogRepositoryPath.make(input.statePath)
345
+ } catch (cause) {
346
+ throw authorityError("publish.catalogGit", cause instanceof Error ? cause.message : String(cause))
347
+ }
348
+ if (targetPath.toString() === statePath.toString()) {
349
+ throw authorityError("publish.catalogGit", "Catalog target and managed-state paths must be distinct.")
350
+ }
351
+ const provider = ProviderId.make("catalog-git")
352
+ const audience = CanonicalAudience.make(`https://api.github.com/repos/${repository}`)
353
+ const token = tokenStrategy(input.tokenEnv, "publish.catalogGit[].tokenEnv")
354
+ return PublicationAuthorityIntent.make({
355
+ subject: SubjectId.make(`catalog-git:${repository}#refs/heads/${branch}:${targetPath}+${statePath}`),
356
+ provider,
357
+ audience,
358
+ observationStrategies: [anonymousStrategy(), token],
359
+ publishStrategy: token
360
+ })
361
+ }
362
+
363
+ const githubCoordinate = (value: string): string => {
364
+ if (!/^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/u.test(value)) {
365
+ throw authorityError("publish.github.repository", "GitHub repository must be an owner/repository coordinate.")
366
+ }
367
+ return value
368
+ }
369
+
370
+ export interface GitHubPublicationAuthorityInput {
371
+ readonly repository: string
372
+ readonly tag: string
373
+ readonly tokenEnv?: string
374
+ }
375
+
376
+ /** Resolve GitHub auth once; anonymous observation is always attempted first. */
377
+ export const makeGitHubPublicationAuthorityIntent = (
378
+ input: GitHubPublicationAuthorityInput
379
+ ): PublicationAuthorityIntent => {
380
+ const repository = githubCoordinate(input.repository)
381
+ const provider = ProviderId.make("github")
382
+ const publishStrategy = tokenStrategy(input.tokenEnv ?? "GITHUB_TOKEN", "publish.github.tokenEnv")
383
+ return PublicationAuthorityIntent.make({
384
+ subject: SubjectId.make(`github:${repository}#${input.tag}`),
385
+ provider,
386
+ audience: CanonicalAudience.make(`https://api.github.com/repos/${repository}`),
387
+ observationStrategies: [anonymousStrategy(), publishStrategy],
388
+ publishStrategy
389
+ })
390
+ }
391
+
392
+ const sameTokenStrategy = (left: ResolvedAuthStrategy | undefined, right: TokenAuthStrategy): boolean =>
393
+ left?.kind === "token" && left.credential === right.credential
394
+
395
+ export const npmPublicationAuthorityIssue = (publication: {
396
+ readonly packageName: { readonly toString: () => string }
397
+ readonly version: { readonly toString: () => string }
398
+ readonly registryUrl: string
399
+ readonly distTag: string
400
+ readonly access: "public" | "restricted"
401
+ readonly authentication: NpmAuthenticationValue
402
+ readonly provenance: "automatic" | "required" | "disabled"
403
+ readonly authority: PublicationAuthorityIntent
404
+ }): string | undefined => {
405
+ const version = publication.version.toString()
406
+ if (Semver.valid(version) !== version) {
407
+ return "npm publication version must be canonical SemVer."
408
+ }
409
+ if (Semver.prerelease(version) !== null && publication.distTag === "latest") {
410
+ return "npm prerelease publication must use an explicit non-latest dist-tag."
411
+ }
412
+ if (publication.access === "restricted" && !publication.packageName.toString().startsWith("@")) {
413
+ return "npm restricted access is valid only for scoped package names."
414
+ }
415
+ const expectedSubject = `npm:${publication.packageName}@${publication.version}`
416
+ let expectedAudience: string
417
+ try { expectedAudience = canonicalizeRegistryUrl(publication.registryUrl) } catch {
418
+ return "npm publication must carry a canonical absolute registry endpoint."
419
+ }
420
+ const { authority } = publication
421
+ if (publication.registryUrl !== expectedAudience || authority.subject !== expectedSubject ||
422
+ authority.provider !== "npm" || authority.audience !== expectedAudience) {
423
+ return "npm publication authority must match its exact package, version, provider, and canonical registry audience."
424
+ }
425
+ if (authority.observationStrategies[0]?.kind !== "anonymous") {
426
+ return "npm observation authority must attempt anonymous access first."
427
+ }
428
+ if (authority.publishStrategy.kind === "token") {
429
+ return publication.authentication.strategy === "token" && publication.provenance !== "automatic" &&
430
+ authority.publishStrategy.credential === publication.authentication.credential &&
431
+ authority.observationStrategies.length === 1
432
+ ? undefined
433
+ : "npm token publication requires the exact configured mutation token, explicit provenance, and anonymous-only observation."
434
+ }
435
+ if (authority.publishStrategy.kind === "trusted-publishing") {
436
+ const authentication = publication.authentication
437
+ return authentication.strategy === "trusted-publishing" &&
438
+ publication.registryUrl === "https://registry.npmjs.org/" &&
439
+ authentication.attestation.allowedAction === "npm-publish-direct" &&
440
+ authority.observationStrategies.length === 1 &&
441
+ authority.publishStrategy.identityProvider === "github-actions" &&
442
+ authority.publishStrategy.runnerClass === authentication.attestation.runner &&
443
+ authority.publishStrategy.repository === authentication.attestation.repository &&
444
+ authority.publishStrategy.workflow === `.github/workflows/${authentication.attestation.workflow}` &&
445
+ authority.publishStrategy.workflowRef === authentication.attestation.workflowRef &&
446
+ /^[a-f0-9]{40}(?:[a-f0-9]{24})?$/u.test(authority.publishStrategy.sourceCommit) &&
447
+ authority.publishStrategy.provenanceEnvironmentContract === "github-actions-npm-provenance-v1" &&
448
+ authority.publishStrategy.allowedAction === authentication.attestation.allowedAction &&
449
+ authority.publishStrategy.publisherSink === "certified-npm-cli"
450
+ ? undefined
451
+ : "npm trusted publishing requires the exact direct-publish attestation, hosted workflow identity/ref, and certified npm sink."
452
+ }
453
+ return "npm publication requires token or trusted-publishing mutation authority."
454
+ }
455
+
456
+ export const githubPublicationAuthorityIssue = (publication: {
457
+ readonly repository: string
458
+ readonly tag: { readonly toString: () => string }
459
+ readonly authority: PublicationAuthorityIntent
460
+ }): string | undefined => {
461
+ let repository: string
462
+ try { repository = githubCoordinate(publication.repository) } catch {
463
+ return "GitHub publication must carry an owner/repository coordinate."
464
+ }
465
+ const { authority } = publication
466
+ if (authority.subject !== `github:${repository}#${publication.tag}` || authority.provider !== "github" ||
467
+ authority.audience !== `https://api.github.com/repos/${repository}`) {
468
+ return "GitHub publication authority must match its exact repository, tag, provider, and API audience."
469
+ }
470
+ if (authority.publishStrategy.kind !== "token" || authority.observationStrategies.length !== 2 ||
471
+ authority.observationStrategies[0]?.kind !== "anonymous" ||
472
+ !sameTokenStrategy(authority.observationStrategies[1], authority.publishStrategy)) {
473
+ return "GitHub publication requires anonymous observation followed by its exact configured token reference."
474
+ }
475
+ return undefined
476
+ }
477
+
478
+ export const pyPiPublicationAuthorityIssue = (publication: {
479
+ readonly project: string
480
+ readonly version: { readonly toString: () => string }
481
+ readonly repository: PyPiRepository
482
+ readonly simpleBaseUrl: string
483
+ readonly projectUrl: string
484
+ readonly uploadUrl: string
485
+ readonly authentication: PyPiAuthenticationValue
486
+ readonly files: ReadonlyArray<{
487
+ readonly filename: { readonly toString: () => string }
488
+ readonly authority: PublicationAuthorityIntent
489
+ }>
490
+ }): string | undefined => {
491
+ let project: string
492
+ try { project = normalizePyPiProjectName(publication.project) } catch {
493
+ return "PyPI publication must carry a valid normalized project name."
494
+ }
495
+ const endpoints = pypiRepositoryEndpoints(publication.repository)
496
+ const expectedProjectUrl = `${endpoints.simpleBaseUrl}${encodeURIComponent(project)}/`
497
+ if (publication.project !== project || publication.simpleBaseUrl !== endpoints.simpleBaseUrl ||
498
+ publication.projectUrl !== expectedProjectUrl || publication.uploadUrl !== endpoints.uploadUrl) {
499
+ return "PyPI publication endpoints must match its closed repository and normalized project coordinate."
500
+ }
501
+ if (publication.files.length === 0) return "PyPI publication requires at least one distribution file."
502
+ const names = new Set<string>()
503
+ for (const file of publication.files) {
504
+ const filename = file.filename.toString()
505
+ if (filename.length === 0 || filename.includes("/") || filename.includes("\\") || names.has(filename)) {
506
+ return "PyPI publication filenames must be nonempty, unique basenames."
507
+ }
508
+ names.add(filename)
509
+ const authority = file.authority
510
+ if (authority.subject !== `pypi:${publication.repository}:${project}@${publication.version}#${filename}` ||
511
+ authority.provider !== "pypi" || authority.audience !== endpoints.uploadUrl ||
512
+ authority.observationStrategies.length !== 1 || authority.observationStrategies[0]?.kind !== "anonymous") {
513
+ return "PyPI file authority must match its exact repository, project, version, filename, upload audience, and anonymous read strategy."
514
+ }
515
+ if (publication.authentication.strategy === "token") {
516
+ if (authority.publishStrategy.kind !== "token" ||
517
+ authority.publishStrategy.credential !== publication.authentication.credential) {
518
+ return "PyPI token publication requires its exact project-scoped configured credential."
519
+ }
520
+ } else if (authority.publishStrategy.kind !== "trusted-publishing" ||
521
+ authority.publishStrategy.publisherSink !== "external-host-owned" ||
522
+ authority.publishStrategy.allowedAction !== "external-pypa-action" ||
523
+ authority.publishStrategy.repository !== publication.authentication.repository ||
524
+ authority.publishStrategy.workflow !== `.github/workflows/${publication.authentication.workflow}` ||
525
+ authority.publishStrategy.workflowRef !== publication.authentication.workflowRef ||
526
+ authority.publishStrategy.environment !== publication.authentication.environment ||
527
+ authority.publishStrategy.projects.join("\n") !== publication.authentication.projects.map(normalizePyPiProjectName).join("\n")) {
528
+ return "PyPI trusted publication must remain bound to the exact external PyPA Action workflow and complete bundled project set."
529
+ }
530
+ }
531
+ return undefined
532
+ }
533
+
534
+ export const catalogPublicationAuthorityIssue = (publication: {
535
+ readonly repository: string
536
+ readonly branch: { readonly toString: () => string }
537
+ readonly targetPath: { readonly toString: () => string }
538
+ readonly statePath: { readonly toString: () => string }
539
+ readonly authority: PublicationAuthorityIntent
540
+ }): string | undefined => {
541
+ let repository: string
542
+ let branch: string
543
+ let targetPath: string
544
+ let statePath: string
545
+ try {
546
+ repository = GitHubRepositoryCoordinate.make(publication.repository)
547
+ branch = GitBranchName.make(publication.branch.toString())
548
+ targetPath = CatalogRepositoryPath.make(publication.targetPath.toString())
549
+ statePath = CatalogRepositoryPath.make(publication.statePath.toString())
550
+ } catch {
551
+ return "Catalog publication must carry canonical GitHub repository, branch, and file paths."
552
+ }
553
+ const { authority } = publication
554
+ const token = authority.publishStrategy
555
+ if (targetPath === statePath || targetPath.startsWith(`${statePath}/`) || statePath.startsWith(`${targetPath}/`) ||
556
+ authority.subject !== `catalog-git:${repository}#refs/heads/${branch}:${targetPath}+${statePath}` ||
557
+ authority.provider !== "catalog-git" || authority.audience !== `https://api.github.com/repos/${repository}` ||
558
+ authority.observationStrategies.length !== 2 || authority.observationStrategies[0]?.kind !== "anonymous" ||
559
+ token.kind !== "token" || !sameTokenStrategy(authority.observationStrategies[1], token)) {
560
+ return "Catalog publication authority must bind its exact GitHub repository/ref/path pair and one bundled read/write token."
561
+ }
562
+ return undefined
563
+ }
564
+
565
+ // `localeCompare` is environment-dependent. Graph order is an internal
566
+ // identity, so it uses the repository's plain code-point order instead.
567
+ const byId = (left: { readonly id: { toString(): string } }, right: { readonly id: { toString(): string } }) => {
568
+ const a = left.id.toString()
569
+ const b = right.id.toString()
570
+ return a < b ? -1 : a > b ? 1 : 0
571
+ }
572
+
573
+ const preparationInputs = (preparation: GraphPreparation): ReadonlyArray<OutputId> => preparation.inputs
574
+ const preparationOutputs = (preparation: GraphPreparation): ReadonlyArray<OutputDeclaration> =>
575
+ preparation._tag === "GraphCommandArtifact" ? preparation.outputs
576
+ : preparation._tag === "GraphArchive" || preparation._tag === "GraphChecksum" || preparation._tag === "GraphCatalogRender"
577
+ ? [preparation.output] : []
578
+
579
+ const pathsOverlap = (left: string, right: string): boolean =>
580
+ left === right || left.startsWith(`${right}/`) || right.startsWith(`${left}/`)
581
+ const portablePathsOverlap = (left: string, right: string): boolean =>
582
+ pathsOverlap(left.toLocaleLowerCase("en-US"), right.toLocaleLowerCase("en-US"))
583
+
584
+ export const linkContributions = (
585
+ contributions: ReadonlyArray<CapabilityContribution>
586
+ ): ReleaseGraph => {
587
+ const contributedPreparations = contributions.flatMap((item) => item.preparations)
588
+ const collections = contributedPreparations.flatMap((preparation) =>
589
+ preparation._tag === "GraphCommandCollection" ? [preparation.collection] : [])
590
+ .sort(byId)
591
+ const collectionIds = new Map<string, ArtifactCollectionContract>()
592
+ const collectionRoots = new Map<string, string>()
593
+ for (const collection of collections) {
594
+ const id = collection.id.toString()
595
+ const previousId = [...collectionIds.keys()].find((candidate) =>
596
+ candidate.toLocaleLowerCase("en-US") === id.toLocaleLowerCase("en-US"))
597
+ if (previousId !== undefined) throw new GraphLinkError({
598
+ kind: "duplicate", value: id,
599
+ reason: `Artifact collection id collides with ${previousId} under portable case folding.`
600
+ })
601
+ const issue = cardinalityIssue(collection.cardinality)
602
+ if (issue !== undefined) throw new GraphLinkError({ kind: "reference", value: id, reason: issue })
603
+ const contractIssue = collectionContractIssue(collection)
604
+ if (contractIssue !== undefined) throw new GraphLinkError({
605
+ kind: "reference", value: id, reason: contractIssue
606
+ })
607
+ const root = collection.root.toString()
608
+ const previousRoot = [...collectionRoots.entries()].find(([candidate]) => portablePathsOverlap(candidate, root))
609
+ if (previousRoot !== undefined) throw new GraphLinkError({
610
+ kind: "path", value: root,
611
+ reason: `Artifact collection roots ${previousRoot[0]} (${previousRoot[1]}) and ${root} (${id}) overlap.`
612
+ })
613
+ collectionIds.set(id, collection)
614
+ collectionRoots.set(root, id)
615
+ }
616
+ const declared = contributions.flatMap((item) => item.artifacts)
617
+ const allPreparationOutputs = contributions.flatMap((item) => item.preparations.flatMap(preparationOutputs))
618
+ const artifactIds = new Set<string>()
619
+ for (const artifact of [...declared, ...allPreparationOutputs]) {
620
+ if (artifactIds.has(artifact.id.toString())) throw new GraphLinkError({
621
+ kind: "duplicate", value: artifact.id.toString(), reason: "Artifact id has more than one declaration or producer."
622
+ })
623
+ artifactIds.add(artifact.id.toString())
624
+ }
625
+ const artifacts = [...declared, ...allPreparationOutputs].sort(byId)
626
+ const paths = new Map<string, string>()
627
+ for (const artifact of artifacts) {
628
+ const path = artifact.path.toString()
629
+ const previous = paths.get(path)
630
+ if (previous !== undefined && previous !== artifact.id.toString()) throw new GraphLinkError({
631
+ kind: "path", value: path, reason: `Artifacts ${previous} and ${artifact.id} share one output path.`
632
+ })
633
+ paths.set(path, artifact.id.toString())
634
+ if (artifact.kind !== "package") {
635
+ const collection = [...collectionRoots.entries()].find(([root]) => portablePathsOverlap(root, path))
636
+ if (collection !== undefined) throw new GraphLinkError({
637
+ kind: "path", value: path,
638
+ reason: `Artifact ${artifact.id} overlaps collection root ${collection[0]} (${collection[1]}).`
639
+ })
640
+ }
641
+ }
642
+ const preparations = contributedPreparations.sort(byId)
643
+ const operationIds = new Set<string>()
644
+ const producers = new Map<string, string>()
645
+ for (const preparation of preparations) {
646
+ const id = preparation.id.toString()
647
+ if (operationIds.has(id)) throw new GraphLinkError({
648
+ kind: "duplicate", value: id, reason: "Preparation id has more than one producer."
649
+ })
650
+ operationIds.add(id)
651
+ if (preparation._tag === "GraphNpmPackageBuild") {
652
+ const cwd = preparation.cwd.toString()
653
+ const roots = preparation.outputRoots.map(String)
654
+ if (new Set(roots.map((root) => root.toLocaleLowerCase("en-US"))).size !== roots.length) {
655
+ throw new GraphLinkError({ kind: "path", value: id, reason: "npm package build output roots must be unique." })
656
+ }
657
+ for (let index = 0; index < roots.length; index += 1) {
658
+ const root = roots[index]!
659
+ if (cwd !== "." && root !== cwd && !root.startsWith(`${cwd}/`)) throw new GraphLinkError({
660
+ kind: "path", value: root, reason: "npm package build output roots must remain inside npmPackage.path."
661
+ })
662
+ if (artifacts.some((artifact) => artifact.kind !== "package" && portablePathsOverlap(root, artifact.path.toString())) ||
663
+ [...collectionRoots.keys()].some((collection) => portablePathsOverlap(root, collection))) throw new GraphLinkError({
664
+ kind: "path", value: root, reason: "npm package build output roots cannot overlap captured artifacts or collections."
665
+ })
666
+ for (const other of roots.slice(index + 1)) if (portablePathsOverlap(root, other)) throw new GraphLinkError({
667
+ kind: "path", value: root, reason: `npm package build output roots ${root} and ${other} overlap.`
668
+ })
669
+ }
670
+ }
671
+ if (preparation._tag === "GraphCommandCollection" && preparation.collection.producer.toString() !== id) {
672
+ throw new GraphLinkError({
673
+ kind: "reference", value: preparation.collection.id.toString(),
674
+ reason: `Artifact collection declares producer ${preparation.collection.producer} but is owned by ${id}.`
675
+ })
676
+ }
677
+ if (preparation._tag === "GraphCommandCollection") {
678
+ const root = preparation.collection.root.toString()
679
+ const overlap = preparation.inputs.map((input) => artifacts.find((artifact) => artifact.id === input))
680
+ .find((artifact) => artifact !== undefined && portablePathsOverlap(root, artifact.path.toString()))
681
+ if (overlap !== undefined) throw new GraphLinkError({
682
+ kind: "path", value: root,
683
+ reason: `Artifact collection root overlaps read-only input ${overlap.id} at ${overlap.path}.`
684
+ })
685
+ }
686
+ for (const input of preparationInputs(preparation)) {
687
+ if (!artifactIds.has(input.toString())) throw new GraphLinkError({
688
+ kind: "missing", value: input.toString(), reason: `Preparation ${id} references no artifact.`
689
+ })
690
+ }
691
+ for (const output of preparationOutputs(preparation)) {
692
+ const outputId = output.id.toString()
693
+ if (producers.has(outputId)) throw new GraphLinkError({
694
+ kind: "duplicate", value: outputId, reason: "Output has more than one producer."
695
+ })
696
+ if (preparation._tag === "GraphCommandArtifact" && ["directory", "package"].includes(output.kind)) throw new GraphLinkError({
697
+ kind: "path", value: outputId, reason: "Generic command outputs must be regular files."
698
+ })
699
+ if (preparation._tag === "GraphCommandArtifact" && preparation.inputs.some((input) => input.toString() === outputId)) throw new GraphLinkError({
700
+ kind: "reference", value: outputId, reason: "Command inputs and outputs must be disjoint."
701
+ })
702
+ if (preparation._tag === "GraphCommandArtifact" && preparation.inputs.some((input) =>
703
+ artifacts.find((artifact) => artifact.id.toString() === input.toString())?.path === output.path)) throw new GraphLinkError({
704
+ kind: "path", value: outputId, reason: "Command outputs must not overwrite input paths."
705
+ })
706
+ producers.set(outputId, id)
707
+ }
708
+ const references = preparation._tag === "GraphCommandCheck" || preparation._tag === "GraphCommandArtifact" ||
709
+ preparation._tag === "GraphCommandCollection" || preparation._tag === "GraphNpmPackageBuild"
710
+ ? preparation.argv.flatMap((part) => [...part.matchAll(/\{(input|output|collection):([^}]+)\}/gu)]) : []
711
+ for (const reference of references) {
712
+ const direction = reference[1]
713
+ const referenced = reference[2] ?? ""
714
+ if (direction === "input" && !preparationInputs(preparation).some((input) => input.toString() === referenced)) {
715
+ throw new GraphLinkError({ kind: "reference", value: referenced, reason: "Command input reference is undeclared." })
716
+ }
717
+ if (direction === "output" && !preparationOutputs(preparation).some((output) => output.id.toString() === referenced)) {
718
+ throw new GraphLinkError({ kind: "reference", value: referenced, reason: "Command output reference is undeclared." })
719
+ }
720
+ if (direction === "collection" && (preparation._tag !== "GraphCommandCollection" ||
721
+ preparation.collection.id.toString() !== referenced)) {
722
+ throw new GraphLinkError({ kind: "reference", value: referenced, reason: "Command collection reference is undeclared." })
723
+ }
724
+ }
725
+ if (preparation._tag === "GraphCommandCheck" || preparation._tag === "GraphCommandArtifact" ||
726
+ preparation._tag === "GraphCommandCollection" || preparation._tag === "GraphNpmPackageBuild") {
727
+ for (const part of preparation.argv) {
728
+ for (const token of part.matchAll(/\{([^}]+)\}/gu)) {
729
+ const value = token[1] ?? ""
730
+ if (!value.startsWith("input:") && !value.startsWith("output:") && !value.startsWith("collection:")) throw new GraphLinkError({
731
+ kind: "reference", value, reason: "Command paths support only declared input, output, and collection references."
732
+ })
733
+ }
734
+ }
735
+ }
736
+ }
737
+ const publications = contributions.flatMap((item) => item.publications).sort(byId)
738
+ for (const publication of publications) {
739
+ const authorityIssue = publication._tag === "GraphNpmPublication"
740
+ ? npmPublicationAuthorityIssue(publication)
741
+ : publication._tag === "GraphPyPiPublication"
742
+ ? pyPiPublicationAuthorityIssue(publication)
743
+ : publication._tag === "GraphGitHubPublication"
744
+ ? githubPublicationAuthorityIssue(publication)
745
+ : catalogPublicationAuthorityIssue(publication)
746
+ if (authorityIssue !== undefined) throw new GraphLinkError({
747
+ kind: "reference", value: publication.id.toString(), reason: authorityIssue
748
+ })
749
+ if (publication._tag === "GraphGitHubPublication" && publication.body !== undefined && publication.bodyArtifact !== undefined) {
750
+ throw new GraphLinkError({ kind: "reference", value: publication.id.toString(), reason: "GitHub body must be inline text or one text artifact, not both." })
751
+ }
752
+ if (publication._tag === "GraphGitHubPublication") {
753
+ const selected = new Set<string>()
754
+ for (const selector of publication.assetCollections) {
755
+ const id = selector.collection.toString()
756
+ if (selected.has(id)) throw new GraphLinkError({
757
+ kind: "duplicate", value: id, reason: "GitHub publication repeats one artifact collection selector."
758
+ })
759
+ selected.add(id)
760
+ const contract = collectionIds.get(id)
761
+ if (contract === undefined) throw new GraphLinkError({
762
+ kind: "missing", value: id, reason: "GitHub publication selector references no artifact collection."
763
+ })
764
+ const issue = cardinalityIssue(selector.cardinality)
765
+ if (issue !== undefined) throw new GraphLinkError({ kind: "reference", value: id, reason: issue })
766
+ if (!cardinalitiesOverlap(contract.cardinality, selector.cardinality)) throw new GraphLinkError({
767
+ kind: "reference", value: id,
768
+ reason: "GitHub publication cardinality cannot be satisfied by the producer collection contract."
769
+ })
770
+ if (selector.artifactKind !== contract.artifactKind || selector.pathSuffix !== contract.pathSuffix ||
771
+ selector.mediaType !== contract.mediaType) throw new GraphLinkError({
772
+ kind: "reference", value: id,
773
+ reason: "GitHub publication selector kind, path suffix, and media type must exactly match its collection contract."
774
+ })
775
+ }
776
+ }
777
+ const ids = publication._tag === "GraphGitHubPublication"
778
+ ? [...publication.assetIds, ...(publication.bodyArtifact === undefined ? [] : [publication.bodyArtifact])]
779
+ : publication._tag === "GraphPyPiPublication"
780
+ ? publication.files.map((file) => file.artifactId)
781
+ : publication._tag === "GraphCatalogPublication"
782
+ ? [publication.targetArtifact]
783
+ : [publication.packageArtifact]
784
+ for (const id of ids) if (!artifactIds.has(id.toString()) && !producers.has(id.toString())) {
785
+ throw new GraphLinkError({ kind: "missing", value: id.toString(), reason: "Publication references no artifact." })
786
+ }
787
+ if (publication._tag === "GraphNpmPublication") {
788
+ const packageArtifact = artifacts.find((artifact) => artifact.id === publication.packageArtifact)
789
+ if (packageArtifact?.kind !== "package") throw new GraphLinkError({
790
+ kind: "reference", value: publication.packageArtifact.toString(),
791
+ reason: "npm publication must reference exactly one declared package artifact."
792
+ })
793
+ if (publication.access === "restricted" && !publication.packageName.startsWith("@")) throw new GraphLinkError({
794
+ kind: "reference", value: publication.packageName.toString(),
795
+ reason: "npm restricted access is valid only for scoped package names."
796
+ })
797
+ }
798
+ if (publication._tag === "GraphPyPiPublication") {
799
+ for (const file of publication.files) {
800
+ const artifact = artifacts.find((candidate) => candidate.id.toString() === file.artifactId.toString())
801
+ if (artifact === undefined || artifact.kind === "package") throw new GraphLinkError({
802
+ kind: "reference", value: file.artifactId.toString(),
803
+ reason: "PyPI distributions must reference capturable file artifacts."
804
+ })
805
+ if (artifact.path.toString().split("/").at(-1) !== file.filename.toString()) throw new GraphLinkError({
806
+ kind: "reference", value: file.artifactId.toString(),
807
+ reason: "PyPI distribution authority filename must equal the declared artifact basename."
808
+ })
809
+ }
810
+ }
811
+ if (publication._tag === "GraphGitHubPublication" && publication.bodyArtifact !== undefined) {
812
+ const body = artifacts.find((artifact) => artifact.id.toString() === publication.bodyArtifact!.toString())
813
+ if (body === undefined || body.mediaType === undefined || !body.mediaType.startsWith("text/")) throw new GraphLinkError({
814
+ kind: "reference", value: publication.bodyArtifact.toString(), reason: "GitHub body artifacts must declare a text/* media type."
815
+ })
816
+ }
817
+ if (publication._tag === "GraphGitHubPublication") {
818
+ for (const id of publication.assetIds) {
819
+ const asset = artifacts.find((artifact) => artifact.id.toString() === id.toString())
820
+ if (asset === undefined || asset.kind === "package") throw new GraphLinkError({
821
+ kind: "reference", value: id.toString(), reason: "GitHub assets must be capturable file artifacts."
822
+ })
823
+ }
824
+ }
825
+ if (publication._tag === "GraphCatalogPublication") {
826
+ const artifact = artifacts.find((candidate) => candidate.id.toString() === publication.targetArtifact.toString())
827
+ const producer = preparations.find((candidate) => preparationOutputs(candidate).some((output) =>
828
+ output.id.toString() === publication.targetArtifact.toString()))
829
+ if (artifact === undefined || artifact.kind !== "file" || producer?._tag !== "GraphCatalogRender" ||
830
+ producer.renderer._tag !== publication.renderer._tag ||
831
+ producer.sources.map((source) => source.artifactId.toString()).join("\n") !==
832
+ publication.sources.map((source) => source.artifactId.toString()).join("\n")) {
833
+ throw new GraphLinkError({
834
+ kind: "reference", value: publication.targetArtifact.toString(),
835
+ reason: "Catalog delivery must reference its exact typed catalog-render output."
836
+ })
837
+ }
838
+ if (publication.targetPath.toString() === publication.statePath.toString()) throw new GraphLinkError({
839
+ kind: "path", value: publication.targetPath.toString(),
840
+ reason: "Catalog target and managed-state paths must be distinct."
841
+ })
842
+ }
843
+ }
844
+ const dependencies = new Map<string, Set<string>>(
845
+ preparations.map((preparation) => [preparation.id.toString(), new Set(
846
+ preparationInputs(preparation).map((input) => producers.get(input.toString())).filter(
847
+ (value): value is string => value !== undefined
848
+ ))])
849
+ )
850
+ const ordered: GraphPreparation[] = []
851
+ const pending = new Map(preparations.map((preparation) => [preparation.id.toString(), preparation]))
852
+ while (pending.size > 0) {
853
+ const ready = [...pending.values()].filter((preparation) =>
854
+ [...dependencies.get(preparation.id.toString())!].every((dependency) =>
855
+ !pending.has(dependency)
856
+ )).sort(byId)
857
+ if (ready.length === 0) throw new GraphLinkError({
858
+ kind: "cycle", value: [...pending.keys()].sort().join(","), reason: "Preparation dependency cycle."
859
+ })
860
+ for (const preparation of ready) {
861
+ pending.delete(preparation.id.toString())
862
+ ordered.push(preparation)
863
+ }
864
+ }
865
+ return ReleaseGraph.make({ artifacts, collections, preparations: ordered, publications })
866
+ }