@mannyc1/ts-release 0.0.7 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (695) hide show
  1. package/ARCHITECTURE.md +184 -92
  2. package/CHANGELOG.md +197 -0
  3. package/LICENSE +21 -0
  4. package/README.md +404 -327
  5. package/SPEC.md +257 -310
  6. package/apps/release-ts/README.md +39 -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 +122 -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 +39346 -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 +9 -0
  82. package/dist/drivers/archive.d.ts.map +1 -0
  83. package/dist/drivers/archive.js +105 -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/python-wheel.d.ts +21 -0
  106. package/dist/drivers/python-wheel.d.ts.map +1 -0
  107. package/dist/drivers/python-wheel.js +109 -0
  108. package/dist/drivers/python-wheel.js.map +1 -0
  109. package/dist/drivers/redact.d.ts +4 -0
  110. package/dist/drivers/redact.d.ts.map +1 -0
  111. package/dist/drivers/redact.js +24 -0
  112. package/dist/drivers/redact.js.map +1 -0
  113. package/dist/drivers/seccomp-helper-source.d.ts +7 -0
  114. package/dist/drivers/seccomp-helper-source.d.ts.map +1 -0
  115. package/dist/drivers/seccomp-helper-source.js +121 -0
  116. package/dist/drivers/seccomp-helper-source.js.map +1 -0
  117. package/dist/drivers/utils.d.ts +4 -0
  118. package/dist/drivers/utils.d.ts.map +1 -0
  119. package/dist/drivers/utils.js +5 -0
  120. package/dist/drivers/utils.js.map +1 -0
  121. package/dist/drivers/workspace.d.ts +9 -0
  122. package/dist/drivers/workspace.d.ts.map +1 -0
  123. package/dist/drivers/workspace.js +53 -0
  124. package/dist/drivers/workspace.js.map +1 -0
  125. package/dist/extensions/provider-adapter.d.ts +48 -0
  126. package/dist/extensions/provider-adapter.d.ts.map +1 -0
  127. package/dist/extensions/provider-adapter.js +64 -0
  128. package/dist/extensions/provider-adapter.js.map +1 -0
  129. package/dist/host.d.ts +33 -0
  130. package/dist/host.d.ts.map +1 -0
  131. package/dist/host.js +20 -0
  132. package/dist/host.js.map +1 -0
  133. package/dist/index.d.ts +19 -1
  134. package/dist/index.d.ts.map +1 -1
  135. package/dist/index.js +12 -1
  136. package/dist/index.js.map +1 -1
  137. package/dist/model/artifact-collection.d.ts +84 -0
  138. package/dist/model/artifact-collection.d.ts.map +1 -0
  139. package/dist/model/artifact-collection.js +185 -0
  140. package/dist/model/artifact-collection.js.map +1 -0
  141. package/dist/model/authority.d.ts +89 -0
  142. package/dist/model/authority.d.ts.map +1 -0
  143. package/dist/model/authority.js +80 -0
  144. package/dist/model/authority.js.map +1 -0
  145. package/dist/model/bun-targets.d.ts +34 -0
  146. package/dist/model/bun-targets.d.ts.map +1 -0
  147. package/dist/model/bun-targets.js +17 -0
  148. package/dist/model/bun-targets.js.map +1 -0
  149. package/dist/model/canonical.d.ts +8 -0
  150. package/dist/model/canonical.d.ts.map +1 -0
  151. package/dist/model/canonical.js +205 -0
  152. package/dist/model/canonical.js.map +1 -0
  153. package/dist/model/catalog.d.ts +76 -0
  154. package/dist/model/catalog.d.ts.map +1 -0
  155. package/dist/model/catalog.js +174 -0
  156. package/dist/model/catalog.js.map +1 -0
  157. package/dist/model/digest.d.ts +53 -0
  158. package/dist/model/digest.d.ts.map +1 -0
  159. package/dist/model/digest.js +127 -0
  160. package/dist/model/digest.js.map +1 -0
  161. package/dist/model/errors.d.ts +14 -0
  162. package/dist/model/errors.d.ts.map +1 -0
  163. package/dist/model/errors.js +8 -0
  164. package/dist/model/errors.js.map +1 -0
  165. package/dist/model/primitives.d.ts +19 -0
  166. package/dist/model/primitives.d.ts.map +1 -0
  167. package/dist/model/primitives.js +18 -0
  168. package/dist/model/primitives.js.map +1 -0
  169. package/dist/model/pypi.d.ts +12 -0
  170. package/dist/model/pypi.d.ts.map +1 -0
  171. package/dist/model/pypi.js +20 -0
  172. package/dist/model/pypi.js.map +1 -0
  173. package/dist/model/python-distribution.d.ts +33 -0
  174. package/dist/model/python-distribution.d.ts.map +1 -0
  175. package/dist/model/python-distribution.js +367 -0
  176. package/dist/model/python-distribution.js.map +1 -0
  177. package/dist/model/secret-patterns.d.ts +2 -0
  178. package/dist/model/secret-patterns.d.ts.map +1 -0
  179. package/dist/model/secret-patterns.js +12 -0
  180. package/dist/model/secret-patterns.js.map +1 -0
  181. package/dist/operation-journal/authority.d.ts +3 -0
  182. package/dist/operation-journal/authority.d.ts.map +1 -0
  183. package/dist/operation-journal/authority.js +147 -0
  184. package/dist/operation-journal/authority.js.map +1 -0
  185. package/dist/operation-journal/aws/deadline.d.ts +12 -0
  186. package/dist/operation-journal/aws/deadline.d.ts.map +1 -0
  187. package/dist/operation-journal/aws/deadline.js +38 -0
  188. package/dist/operation-journal/aws/deadline.js.map +1 -0
  189. package/dist/operation-journal/aws/oidc.d.ts +27 -0
  190. package/dist/operation-journal/aws/oidc.d.ts.map +1 -0
  191. package/dist/operation-journal/aws/oidc.js +361 -0
  192. package/dist/operation-journal/aws/oidc.js.map +1 -0
  193. package/dist/operation-journal/aws/policy.d.ts +42 -0
  194. package/dist/operation-journal/aws/policy.d.ts.map +1 -0
  195. package/dist/operation-journal/aws/policy.js +404 -0
  196. package/dist/operation-journal/aws/policy.js.map +1 -0
  197. package/dist/operation-journal/aws/s3-boundary.d.ts +17 -0
  198. package/dist/operation-journal/aws/s3-boundary.d.ts.map +1 -0
  199. package/dist/operation-journal/aws/s3-boundary.js +417 -0
  200. package/dist/operation-journal/aws/s3-boundary.js.map +1 -0
  201. package/dist/operation-journal/aws.d.ts +11 -0
  202. package/dist/operation-journal/aws.d.ts.map +1 -0
  203. package/dist/operation-journal/aws.js +50 -0
  204. package/dist/operation-journal/aws.js.map +1 -0
  205. package/dist/operation-journal/canonical.d.ts +52 -0
  206. package/dist/operation-journal/canonical.d.ts.map +1 -0
  207. package/dist/operation-journal/canonical.js +282 -0
  208. package/dist/operation-journal/canonical.js.map +1 -0
  209. package/dist/operation-journal/model.d.ts +251 -0
  210. package/dist/operation-journal/model.d.ts.map +1 -0
  211. package/dist/operation-journal/model.js +56 -0
  212. package/dist/operation-journal/model.js.map +1 -0
  213. package/dist/operation-journal/reducer.d.ts +28 -0
  214. package/dist/operation-journal/reducer.d.ts.map +1 -0
  215. package/dist/operation-journal/reducer.js +73 -0
  216. package/dist/operation-journal/reducer.js.map +1 -0
  217. package/dist/operation-journal/s3.d.ts +7 -0
  218. package/dist/operation-journal/s3.d.ts.map +1 -0
  219. package/dist/operation-journal/s3.js +667 -0
  220. package/dist/operation-journal/s3.js.map +1 -0
  221. package/dist/operation-journal.d.ts +14 -0
  222. package/dist/operation-journal.d.ts.map +1 -0
  223. package/dist/operation-journal.js +12 -0
  224. package/dist/operation-journal.js.map +1 -0
  225. package/dist/platform/bun.d.ts +7 -0
  226. package/dist/platform/bun.d.ts.map +1 -0
  227. package/dist/platform/bun.js +15 -0
  228. package/dist/platform/bun.js.map +1 -0
  229. package/dist/platform/credentials.d.ts +32 -0
  230. package/dist/platform/credentials.d.ts.map +1 -0
  231. package/dist/platform/credentials.js +834 -0
  232. package/dist/platform/credentials.js.map +1 -0
  233. package/dist/platform/host-support.d.ts +2 -0
  234. package/dist/platform/host-support.d.ts.map +1 -0
  235. package/dist/platform/host-support.js +4 -0
  236. package/dist/platform/host-support.js.map +1 -0
  237. package/dist/platform/node.d.ts +7 -0
  238. package/dist/platform/node.d.ts.map +1 -0
  239. package/dist/platform/node.js +15 -0
  240. package/dist/platform/node.js.map +1 -0
  241. package/dist/platform/release-runtime.d.ts +11 -0
  242. package/dist/platform/release-runtime.d.ts.map +1 -0
  243. package/dist/platform/release-runtime.js +34 -0
  244. package/dist/platform/release-runtime.js.map +1 -0
  245. package/dist/platform/services.d.ts +15 -0
  246. package/dist/platform/services.d.ts.map +1 -0
  247. package/dist/platform/services.js +30 -0
  248. package/dist/platform/services.js.map +1 -0
  249. package/dist/platform/source-observer.d.ts +5 -0
  250. package/dist/platform/source-observer.d.ts.map +1 -0
  251. package/dist/platform/source-observer.js +159 -0
  252. package/dist/platform/source-observer.js.map +1 -0
  253. package/dist/provider-sdk.d.ts +14 -0
  254. package/dist/provider-sdk.d.ts.map +1 -0
  255. package/dist/provider-sdk.js +10 -0
  256. package/dist/provider-sdk.js.map +1 -0
  257. package/dist/publication/adapter.d.ts +212 -0
  258. package/dist/publication/adapter.d.ts.map +1 -0
  259. package/dist/publication/adapter.js +83 -0
  260. package/dist/publication/adapter.js.map +1 -0
  261. package/dist/publication/authority.d.ts +137 -0
  262. package/dist/publication/authority.d.ts.map +1 -0
  263. package/dist/publication/authority.js +233 -0
  264. package/dist/publication/authority.js.map +1 -0
  265. package/dist/publication/catalog-git.d.ts +37 -0
  266. package/dist/publication/catalog-git.d.ts.map +1 -0
  267. package/dist/publication/catalog-git.js +451 -0
  268. package/dist/publication/catalog-git.js.map +1 -0
  269. package/dist/publication/claim.d.ts +39 -0
  270. package/dist/publication/claim.d.ts.map +1 -0
  271. package/dist/publication/claim.js +31 -0
  272. package/dist/publication/claim.js.map +1 -0
  273. package/dist/publication/coordinator.d.ts +246 -0
  274. package/dist/publication/coordinator.d.ts.map +1 -0
  275. package/dist/publication/coordinator.js +370 -0
  276. package/dist/publication/coordinator.js.map +1 -0
  277. package/dist/publication/github.d.ts +36 -0
  278. package/dist/publication/github.d.ts.map +1 -0
  279. package/dist/publication/github.js +695 -0
  280. package/dist/publication/github.js.map +1 -0
  281. package/dist/publication/http.d.ts +69 -0
  282. package/dist/publication/http.d.ts.map +1 -0
  283. package/dist/publication/http.js +29 -0
  284. package/dist/publication/http.js.map +1 -0
  285. package/dist/publication/index.d.ts +5 -0
  286. package/dist/publication/index.d.ts.map +1 -0
  287. package/dist/publication/index.js +5 -0
  288. package/dist/publication/index.js.map +1 -0
  289. package/dist/publication/npm.d.ts +40 -0
  290. package/dist/publication/npm.d.ts.map +1 -0
  291. package/dist/publication/npm.js +359 -0
  292. package/dist/publication/npm.js.map +1 -0
  293. package/dist/publication/profiles.d.ts +136 -0
  294. package/dist/publication/profiles.d.ts.map +1 -0
  295. package/dist/publication/profiles.js +90 -0
  296. package/dist/publication/profiles.js.map +1 -0
  297. package/dist/publication/publisher.d.ts +82 -0
  298. package/dist/publication/publisher.d.ts.map +1 -0
  299. package/dist/publication/publisher.js +53 -0
  300. package/dist/publication/publisher.js.map +1 -0
  301. package/dist/publication/pypi.d.ts +45 -0
  302. package/dist/publication/pypi.d.ts.map +1 -0
  303. package/dist/publication/pypi.js +369 -0
  304. package/dist/publication/pypi.js.map +1 -0
  305. package/dist/publication/recovery.d.ts +663 -0
  306. package/dist/publication/recovery.d.ts.map +1 -0
  307. package/dist/publication/recovery.js +252 -0
  308. package/dist/publication/recovery.js.map +1 -0
  309. package/dist/publication/report.d.ts +1061 -0
  310. package/dist/publication/report.d.ts.map +1 -0
  311. package/dist/publication/report.js +441 -0
  312. package/dist/publication/report.js.map +1 -0
  313. package/dist/recipes/config.d.ts +296 -0
  314. package/dist/recipes/config.d.ts.map +1 -0
  315. package/dist/recipes/config.js +325 -0
  316. package/dist/recipes/config.js.map +1 -0
  317. package/dist/release/capabilities.d.ts +12 -0
  318. package/dist/release/capabilities.d.ts.map +1 -0
  319. package/dist/release/capabilities.js +395 -0
  320. package/dist/release/capabilities.js.map +1 -0
  321. package/dist/release/compiler.d.ts +5 -0
  322. package/dist/release/compiler.d.ts.map +1 -0
  323. package/dist/release/compiler.js +19 -0
  324. package/dist/release/compiler.js.map +1 -0
  325. package/dist/release/config.d.ts +5 -0
  326. package/dist/release/config.d.ts.map +1 -0
  327. package/dist/release/config.js +10 -0
  328. package/dist/release/config.js.map +1 -0
  329. package/dist/release/context.d.ts +96 -0
  330. package/dist/release/context.d.ts.map +1 -0
  331. package/dist/release/context.js +168 -0
  332. package/dist/release/context.js.map +1 -0
  333. package/dist/release/graph.d.ts +271 -0
  334. package/dist/release/graph.d.ts.map +1 -0
  335. package/dist/release/graph.js +766 -0
  336. package/dist/release/graph.js.map +1 -0
  337. package/dist/release/inspect.d.ts +88 -0
  338. package/dist/release/inspect.d.ts.map +1 -0
  339. package/dist/release/inspect.js +75 -0
  340. package/dist/release/inspect.js.map +1 -0
  341. package/dist/release/prepare.d.ts +24 -0
  342. package/dist/release/prepare.d.ts.map +1 -0
  343. package/dist/release/prepare.js +1115 -0
  344. package/dist/release/prepare.js.map +1 -0
  345. package/dist/release/prepared-ref.d.ts +60 -0
  346. package/dist/release/prepared-ref.d.ts.map +1 -0
  347. package/dist/release/prepared-ref.js +131 -0
  348. package/dist/release/prepared-ref.js.map +1 -0
  349. package/dist/release/prepared-store.d.ts +90 -0
  350. package/dist/release/prepared-store.d.ts.map +1 -0
  351. package/dist/release/prepared-store.js +339 -0
  352. package/dist/release/prepared-store.js.map +1 -0
  353. package/dist/release/prepared.d.ts +172 -0
  354. package/dist/release/prepared.d.ts.map +1 -0
  355. package/dist/release/prepared.js +268 -0
  356. package/dist/release/prepared.js.map +1 -0
  357. package/dist/release/staging.d.ts +31 -0
  358. package/dist/release/staging.d.ts.map +1 -0
  359. package/dist/release/staging.js +218 -0
  360. package/dist/release/staging.js.map +1 -0
  361. package/dist/resolve/authored.d.ts +53 -0
  362. package/dist/resolve/authored.d.ts.map +1 -0
  363. package/dist/resolve/authored.js +55 -0
  364. package/dist/resolve/authored.js.map +1 -0
  365. package/dist/resolve/encode.d.ts +3 -0
  366. package/dist/resolve/encode.d.ts.map +1 -0
  367. package/dist/resolve/encode.js +21 -0
  368. package/dist/resolve/encode.js.map +1 -0
  369. package/dist/resolve/errors.d.ts +7 -0
  370. package/dist/resolve/errors.d.ts.map +1 -0
  371. package/dist/resolve/errors.js +17 -0
  372. package/dist/resolve/errors.js.map +1 -0
  373. package/dist/resolve/facts.d.ts +12 -0
  374. package/dist/resolve/facts.d.ts.map +1 -0
  375. package/dist/resolve/facts.js +18 -0
  376. package/dist/resolve/facts.js.map +1 -0
  377. package/dist/resolve/resolve.d.ts +10 -0
  378. package/dist/resolve/resolve.d.ts.map +1 -0
  379. package/dist/resolve/resolve.js +242 -0
  380. package/dist/resolve/resolve.js.map +1 -0
  381. package/dist/store.d.ts +11 -0
  382. package/dist/store.d.ts.map +1 -0
  383. package/dist/store.js +9 -0
  384. package/dist/store.js.map +1 -0
  385. package/examples/README.md +20 -30
  386. package/examples/github-release/release.config.json +11 -18
  387. package/examples/homebrew-tap/README.md +11 -0
  388. package/examples/homebrew-tap/release.config.json +40 -28
  389. package/examples/multi-target/release.config.json +28 -53
  390. package/examples/npm-first-publish/release.config.json +23 -25
  391. package/examples/npm-only/release.config.json +22 -30
  392. package/examples/portable-cli/README.md +17 -0
  393. package/examples/portable-cli/artifacts/portable-example-0.1.0-darwin-arm64 +1 -0
  394. package/examples/portable-cli/artifacts/portable-example-0.1.0-darwin-x64 +1 -0
  395. package/examples/portable-cli/artifacts/portable-example-0.1.0-linux-arm64 +1 -0
  396. package/examples/portable-cli/artifacts/portable-example-0.1.0-linux-x64 +1 -0
  397. package/examples/portable-cli/artifacts/portable-example-0.1.0-windows-x64.exe +1 -0
  398. package/examples/portable-cli/artifacts/portable_example-0.1.0-py3-none-macosx_10_15_x86_64.whl +1 -0
  399. package/examples/portable-cli/artifacts/portable_example-0.1.0-py3-none-macosx_11_0_arm64.whl +1 -0
  400. package/examples/portable-cli/artifacts/portable_example-0.1.0-py3-none-manylinux2014_aarch64.whl +1 -0
  401. package/examples/portable-cli/artifacts/portable_example-0.1.0-py3-none-manylinux2014_x86_64.whl +1 -0
  402. package/examples/portable-cli/artifacts/portable_example-0.1.0-py3-none-win_amd64.whl +1 -0
  403. package/examples/portable-cli/package.json +9 -0
  404. package/examples/portable-cli/release.config.json +53 -0
  405. package/examples/portable-cli/src/cli.ts +3 -0
  406. package/examples/pypi-registry/README.md +8 -0
  407. package/examples/scoop-bucket/README.md +10 -0
  408. package/examples/scoop-bucket/release.config.json +40 -30
  409. package/package.json +73 -154
  410. package/src/api/api.ts +365 -0
  411. package/src/api/errors.ts +36 -0
  412. package/src/api/input.ts +92 -0
  413. package/src/api/runtime.ts +14 -0
  414. package/src/api/types.ts +103 -0
  415. package/src/capabilities/bun-targets.ts +2 -0
  416. package/src/capabilities/field-ownership.ts +60 -0
  417. package/src/capabilities/module.ts +158 -0
  418. package/src/capabilities/registry.ts +333 -0
  419. package/src/config/config.ts +48 -0
  420. package/src/correction/coordinator.ts +336 -0
  421. package/src/correction/index.ts +2 -0
  422. package/src/correction/intent.ts +172 -0
  423. package/src/drivers/archive.ts +111 -0
  424. package/src/drivers/contain.ts +20 -0
  425. package/src/drivers/environment.ts +28 -0
  426. package/src/drivers/errors.ts +6 -0
  427. package/src/drivers/glob.ts +137 -0
  428. package/src/drivers/process.ts +339 -0
  429. package/src/drivers/python-wheel.ts +145 -0
  430. package/src/drivers/redact.ts +31 -0
  431. package/src/drivers/seccomp-helper-source.ts +121 -0
  432. package/src/drivers/utils.ts +10 -0
  433. package/src/drivers/workspace.ts +46 -0
  434. package/src/extensions/provider-adapter.ts +99 -0
  435. package/src/host.ts +140 -0
  436. package/src/index.ts +64 -0
  437. package/src/model/artifact-collection.ts +237 -0
  438. package/src/model/authority.ts +116 -0
  439. package/src/model/bun-targets.ts +34 -0
  440. package/src/model/canonical.ts +205 -0
  441. package/src/model/catalog.ts +220 -0
  442. package/src/model/digest.ts +172 -0
  443. package/src/model/errors.ts +6 -0
  444. package/src/model/primitives.ts +37 -0
  445. package/src/model/pypi.ts +30 -0
  446. package/src/model/python-distribution.ts +360 -0
  447. package/src/model/secret-patterns.ts +11 -0
  448. package/src/operation-journal/authority.ts +162 -0
  449. package/src/operation-journal/aws/deadline.ts +41 -0
  450. package/src/operation-journal/aws/oidc.ts +439 -0
  451. package/src/operation-journal/aws/policy.ts +491 -0
  452. package/src/operation-journal/aws/s3-boundary.ts +562 -0
  453. package/src/operation-journal/aws.ts +60 -0
  454. package/src/operation-journal/canonical.ts +373 -0
  455. package/src/operation-journal/model.ts +301 -0
  456. package/src/operation-journal/reducer.ts +112 -0
  457. package/src/operation-journal/s3.ts +884 -0
  458. package/src/operation-journal.ts +51 -0
  459. package/src/platform/bun.ts +38 -0
  460. package/src/platform/credentials.ts +1151 -0
  461. package/src/platform/host-support.ts +4 -0
  462. package/src/platform/node.ts +38 -0
  463. package/src/platform/release-runtime.ts +47 -0
  464. package/src/platform/services.ts +57 -0
  465. package/src/platform/source-observer.ts +193 -0
  466. package/src/provider-sdk.ts +48 -0
  467. package/src/publication/adapter.ts +132 -0
  468. package/src/publication/authority.ts +397 -0
  469. package/src/publication/catalog-git.ts +603 -0
  470. package/src/publication/claim.ts +51 -0
  471. package/src/publication/coordinator.ts +578 -0
  472. package/src/publication/github.ts +1022 -0
  473. package/src/publication/http.ts +101 -0
  474. package/src/publication/index.ts +4 -0
  475. package/src/publication/npm.ts +525 -0
  476. package/src/publication/profiles.ts +94 -0
  477. package/src/publication/publisher.ts +130 -0
  478. package/src/publication/pypi.ts +463 -0
  479. package/src/publication/recovery.ts +349 -0
  480. package/src/publication/report.ts +628 -0
  481. package/src/recipes/config.ts +395 -0
  482. package/src/release/capabilities.ts +460 -0
  483. package/src/release/compiler.ts +28 -0
  484. package/src/release/config.ts +12 -0
  485. package/src/release/context.ts +223 -0
  486. package/src/release/graph.ts +866 -0
  487. package/src/release/inspect.ts +83 -0
  488. package/src/release/prepare.ts +1329 -0
  489. package/src/release/prepared-ref.ts +193 -0
  490. package/src/release/prepared-store.ts +378 -0
  491. package/src/release/prepared.ts +299 -0
  492. package/src/release/staging.ts +257 -0
  493. package/src/resolve/authored.ts +64 -0
  494. package/src/resolve/encode.ts +22 -0
  495. package/src/resolve/errors.ts +15 -0
  496. package/src/resolve/facts.ts +18 -0
  497. package/src/resolve/resolve.ts +328 -0
  498. package/src/store.ts +28 -0
  499. package/templates/README.md +55 -72
  500. package/templates/bun-cli-github/README.md +7 -9
  501. package/templates/bun-cli-github/release.config.json +35 -73
  502. package/templates/github-actions/release.yml +74 -0
  503. package/templates/github-actions/reviewed-release.yml +115 -0
  504. package/templates/multi-target-homebrew/README.md +10 -0
  505. package/templates/multi-target-homebrew/release.config.json +43 -60
  506. package/templates/multi-target-scoop/README.md +10 -0
  507. package/templates/multi-target-scoop/release.config.json +43 -62
  508. package/templates/npm-github/release.config.json +25 -38
  509. package/templates/npm-github/reviewed-release.config.json +37 -0
  510. package/templates/npm-only/README.md +10 -0
  511. package/templates/npm-only/release.config.json +15 -30
  512. package/templates/portable-cli/README.md +13 -0
  513. package/templates/portable-cli/release.config.json +53 -0
  514. package/dist/artifacts/adapter.d.ts +0 -39
  515. package/dist/artifacts/adapter.d.ts.map +0 -1
  516. package/dist/artifacts/adapter.js +0 -23
  517. package/dist/artifacts/adapter.js.map +0 -1
  518. package/dist/artifacts/registry.d.ts +0 -21
  519. package/dist/artifacts/registry.d.ts.map +0 -1
  520. package/dist/artifacts/registry.js +0 -21
  521. package/dist/artifacts/registry.js.map +0 -1
  522. package/dist/config/errors.d.ts +0 -24
  523. package/dist/config/errors.d.ts.map +0 -1
  524. package/dist/config/errors.js +0 -19
  525. package/dist/config/errors.js.map +0 -1
  526. package/dist/config/load.d.ts +0 -9
  527. package/dist/config/load.d.ts.map +0 -1
  528. package/dist/config/load.js +0 -29
  529. package/dist/config/load.js.map +0 -1
  530. package/dist/config/schema.d.ts +0 -12
  531. package/dist/config/schema.d.ts.map +0 -1
  532. package/dist/config/schema.js +0 -19
  533. package/dist/config/schema.js.map +0 -1
  534. package/dist/domain/artifact.d.ts +0 -118
  535. package/dist/domain/artifact.d.ts.map +0 -1
  536. package/dist/domain/artifact.js +0 -197
  537. package/dist/domain/artifact.js.map +0 -1
  538. package/dist/domain/evidence.d.ts +0 -59
  539. package/dist/domain/evidence.d.ts.map +0 -1
  540. package/dist/domain/evidence.js +0 -51
  541. package/dist/domain/evidence.js.map +0 -1
  542. package/dist/domain/operation.d.ts +0 -143
  543. package/dist/domain/operation.d.ts.map +0 -1
  544. package/dist/domain/operation.js +0 -198
  545. package/dist/domain/operation.js.map +0 -1
  546. package/dist/domain/release.d.ts +0 -93
  547. package/dist/domain/release.d.ts.map +0 -1
  548. package/dist/domain/release.js +0 -82
  549. package/dist/domain/release.js.map +0 -1
  550. package/dist/domain/remote-state.d.ts +0 -58
  551. package/dist/domain/remote-state.d.ts.map +0 -1
  552. package/dist/domain/remote-state.js +0 -55
  553. package/dist/domain/remote-state.js.map +0 -1
  554. package/dist/domain/target.d.ts +0 -148
  555. package/dist/domain/target.d.ts.map +0 -1
  556. package/dist/domain/target.js +0 -151
  557. package/dist/domain/target.js.map +0 -1
  558. package/dist/host/host.d.ts +0 -32
  559. package/dist/host/host.d.ts.map +0 -1
  560. package/dist/host/host.js +0 -24
  561. package/dist/host/host.js.map +0 -1
  562. package/dist/host/http-live.d.ts +0 -6
  563. package/dist/host/http-live.d.ts.map +0 -1
  564. package/dist/host/http-live.js +0 -86
  565. package/dist/host/http-live.js.map +0 -1
  566. package/dist/host/http.d.ts +0 -43
  567. package/dist/host/http.d.ts.map +0 -1
  568. package/dist/host/http.js +0 -59
  569. package/dist/host/http.js.map +0 -1
  570. package/dist/host/platform.d.ts +0 -10
  571. package/dist/host/platform.d.ts.map +0 -1
  572. package/dist/host/platform.js +0 -114
  573. package/dist/host/platform.js.map +0 -1
  574. package/dist/host/test.d.ts +0 -23
  575. package/dist/host/test.d.ts.map +0 -1
  576. package/dist/host/test.js +0 -222
  577. package/dist/host/test.js.map +0 -1
  578. package/dist/internal/workspace-path.d.ts +0 -15
  579. package/dist/internal/workspace-path.d.ts.map +0 -1
  580. package/dist/internal/workspace-path.js +0 -36
  581. package/dist/internal/workspace-path.js.map +0 -1
  582. package/dist/planner/artifact-inventory.d.ts +0 -9
  583. package/dist/planner/artifact-inventory.d.ts.map +0 -1
  584. package/dist/planner/artifact-inventory.js +0 -86
  585. package/dist/planner/artifact-inventory.js.map +0 -1
  586. package/dist/planner/create-release-plan.d.ts +0 -5
  587. package/dist/planner/create-release-plan.d.ts.map +0 -1
  588. package/dist/planner/create-release-plan.js +0 -25
  589. package/dist/planner/create-release-plan.js.map +0 -1
  590. package/dist/planner/errors.d.ts +0 -67
  591. package/dist/planner/errors.d.ts.map +0 -1
  592. package/dist/planner/errors.js +0 -58
  593. package/dist/planner/errors.js.map +0 -1
  594. package/dist/planner/evidence-recorder.d.ts +0 -28
  595. package/dist/planner/evidence-recorder.d.ts.map +0 -1
  596. package/dist/planner/evidence-recorder.js +0 -362
  597. package/dist/planner/evidence-recorder.js.map +0 -1
  598. package/dist/planner/executor.d.ts +0 -58
  599. package/dist/planner/executor.d.ts.map +0 -1
  600. package/dist/planner/executor.js +0 -147
  601. package/dist/planner/executor.js.map +0 -1
  602. package/dist/planner/normalize-release.d.ts +0 -19
  603. package/dist/planner/normalize-release.d.ts.map +0 -1
  604. package/dist/planner/normalize-release.js +0 -342
  605. package/dist/planner/normalize-release.js.map +0 -1
  606. package/dist/planner/reconcile.d.ts +0 -17
  607. package/dist/planner/reconcile.d.ts.map +0 -1
  608. package/dist/planner/reconcile.js +0 -259
  609. package/dist/planner/reconcile.js.map +0 -1
  610. package/dist/planner/render-plan.d.ts +0 -15
  611. package/dist/planner/render-plan.d.ts.map +0 -1
  612. package/dist/planner/render-plan.js +0 -231
  613. package/dist/planner/render-plan.js.map +0 -1
  614. package/dist/targets/adapter-helpers.d.ts +0 -79
  615. package/dist/targets/adapter-helpers.d.ts.map +0 -1
  616. package/dist/targets/adapter-helpers.js +0 -194
  617. package/dist/targets/adapter-helpers.js.map +0 -1
  618. package/dist/targets/adapter.d.ts +0 -17
  619. package/dist/targets/adapter.d.ts.map +0 -1
  620. package/dist/targets/adapter.js +0 -2
  621. package/dist/targets/adapter.js.map +0 -1
  622. package/dist/targets/github-release.d.ts +0 -25
  623. package/dist/targets/github-release.d.ts.map +0 -1
  624. package/dist/targets/github-release.js +0 -82
  625. package/dist/targets/github-release.js.map +0 -1
  626. package/dist/targets/github.d.ts +0 -11
  627. package/dist/targets/github.d.ts.map +0 -1
  628. package/dist/targets/github.js +0 -117
  629. package/dist/targets/github.js.map +0 -1
  630. package/dist/targets/homebrew.d.ts +0 -11
  631. package/dist/targets/homebrew.d.ts.map +0 -1
  632. package/dist/targets/homebrew.js +0 -210
  633. package/dist/targets/homebrew.js.map +0 -1
  634. package/dist/targets/live.d.ts +0 -5
  635. package/dist/targets/live.d.ts.map +0 -1
  636. package/dist/targets/live.js +0 -50
  637. package/dist/targets/live.js.map +0 -1
  638. package/dist/targets/npm.d.ts +0 -10
  639. package/dist/targets/npm.d.ts.map +0 -1
  640. package/dist/targets/npm.js +0 -112
  641. package/dist/targets/npm.js.map +0 -1
  642. package/dist/targets/pypi.d.ts +0 -11
  643. package/dist/targets/pypi.d.ts.map +0 -1
  644. package/dist/targets/pypi.js +0 -141
  645. package/dist/targets/pypi.js.map +0 -1
  646. package/dist/targets/registry.d.ts +0 -25
  647. package/dist/targets/registry.d.ts.map +0 -1
  648. package/dist/targets/registry.js +0 -34
  649. package/dist/targets/registry.js.map +0 -1
  650. package/dist/targets/scoop.d.ts +0 -11
  651. package/dist/targets/scoop.d.ts.map +0 -1
  652. package/dist/targets/scoop.js +0 -88
  653. package/dist/targets/scoop.js.map +0 -1
  654. package/dist/types/effect-internal.d.ts +0 -5
  655. package/dist/types/effect-internal.d.ts.map +0 -1
  656. package/dist/types/effect-internal.js +0 -2
  657. package/dist/types/effect-internal.js.map +0 -1
  658. package/dist/workflows/config.d.ts +0 -202
  659. package/dist/workflows/config.d.ts.map +0 -1
  660. package/dist/workflows/config.js +0 -420
  661. package/dist/workflows/config.js.map +0 -1
  662. package/dist/workflows/diagnostics.d.ts +0 -62
  663. package/dist/workflows/diagnostics.d.ts.map +0 -1
  664. package/dist/workflows/diagnostics.js +0 -530
  665. package/dist/workflows/diagnostics.js.map +0 -1
  666. package/dist/workflows/distribution.d.ts +0 -25
  667. package/dist/workflows/distribution.d.ts.map +0 -1
  668. package/dist/workflows/distribution.js +0 -24
  669. package/dist/workflows/distribution.js.map +0 -1
  670. package/dist/workflows/evidence.d.ts +0 -17
  671. package/dist/workflows/evidence.d.ts.map +0 -1
  672. package/dist/workflows/evidence.js +0 -32
  673. package/dist/workflows/evidence.js.map +0 -1
  674. package/dist/workflows/index.d.ts +0 -8
  675. package/dist/workflows/index.d.ts.map +0 -1
  676. package/dist/workflows/index.js +0 -7
  677. package/dist/workflows/index.js.map +0 -1
  678. package/dist/workflows/init.d.ts +0 -84
  679. package/dist/workflows/init.d.ts.map +0 -1
  680. package/dist/workflows/init.js +0 -487
  681. package/dist/workflows/init.js.map +0 -1
  682. package/dist/workflows/live.d.ts +0 -9
  683. package/dist/workflows/live.d.ts.map +0 -1
  684. package/dist/workflows/live.js +0 -7
  685. package/dist/workflows/live.js.map +0 -1
  686. package/dist/workflows/options.d.ts +0 -30
  687. package/dist/workflows/options.d.ts.map +0 -1
  688. package/dist/workflows/options.js +0 -16
  689. package/dist/workflows/options.js.map +0 -1
  690. package/examples/non-strict-skips/artifacts/release-example-skips-0.1.0.tgz +0 -1
  691. package/examples/non-strict-skips/release.config.json +0 -31
  692. package/examples/pypi-registry/release.config.json +0 -31
  693. package/templates/github-actions/plan-and-approved-execute.yml +0 -58
  694. package/templates/github-actions/plan-only.yml +0 -33
  695. package/templates/github-actions/trusted-publishing.yml +0 -61
@@ -0,0 +1,1329 @@
1
+ import * as Effect from "effect/Effect"
2
+ import * as Schema from "effect/Schema"
3
+ import { createHash } from "node:crypto"
4
+ import {
5
+ existsSync,
6
+ lstatSync,
7
+ mkdirSync,
8
+ readFileSync,
9
+ readdirSync,
10
+ realpathSync,
11
+ rmSync
12
+ } from "node:fs"
13
+ import { basename, isAbsolute, join, relative, resolve, sep } from "node:path"
14
+ import { tarGz, zip, type ArchiveEntry } from "../drivers/archive.js"
15
+ import { makeCanonicalTemporaryDirectory, secureRead, secureWrite } from "../drivers/workspace.js"
16
+ import type {
17
+ BunCompileRuntimeIdentity,
18
+ CommandOutcome,
19
+ ExecutableIdentity,
20
+ NetworkIsolationIdentity,
21
+ RunCommand
22
+ } from "../drivers/process.js"
23
+ import { encodeCanonicalJson } from "../model/canonical.js"
24
+ import {
25
+ ArtifactCollectionMember,
26
+ cardinalityAccepts,
27
+ collectionMemberBytesIssue,
28
+ collectionMemberId,
29
+ collectionMemberPath,
30
+ normalizeCollectionMemberKey,
31
+ type ArtifactCollectionContract
32
+ } from "../model/artifact-collection.js"
33
+ import { sha256Digest } from "../model/digest.js"
34
+ import {
35
+ CatalogManagedState,
36
+ PreparedCatalogDownload,
37
+ PreparedCatalogRenderer,
38
+ encodeCatalogManagedState,
39
+ renderCatalog
40
+ } from "../model/catalog.js"
41
+ import { inspectPythonDistribution } from "../model/python-distribution.js"
42
+ import { NonEmptyName, OutputId, SafeRelativePath, Version, WorkspaceRoot } from "../model/primitives.js"
43
+ import {
44
+ ExplicitInputSnapshot,
45
+ type StagingSnapshot,
46
+ VerifiedReleaseContext
47
+ } from "./context.js"
48
+ import {
49
+ GraphArchive,
50
+ GraphCatalogRender,
51
+ GraphChecksum,
52
+ GraphCommandArtifact,
53
+ GraphCommandCheck,
54
+ GraphCommandCollection,
55
+ GraphGitHubPublication,
56
+ GraphCatalogPublication,
57
+ GraphNpmPackageBuild,
58
+ GraphNpmPublication,
59
+ OutputDeclaration,
60
+ type GraphPreparation,
61
+ ReleaseGraph
62
+ } from "./graph.js"
63
+ import {
64
+ PreparedArtifact,
65
+ PreparedArtifactCollection,
66
+ PreparedCatalogPublication,
67
+ PreparedExecutionInputs,
68
+ PreparedGitHubPublication,
69
+ PreparedNpmPublication,
70
+ PreparedPyPiFile,
71
+ PreparedPyPiPublication,
72
+ PreparedProject,
73
+ PreparedProvenance,
74
+ PreparedReleaseV2,
75
+ PreparedSource
76
+ } from "./prepared.js"
77
+ import { CompletePreparedReleaseRef } from "./prepared-ref.js"
78
+ import {
79
+ PreparedCommitHandoffError,
80
+ PreparedStoreError,
81
+ type PreparedReleaseStoreShape
82
+ } from "./prepared-store.js"
83
+ import {
84
+ assertSnapshotPresent,
85
+ materializeExplicitInput,
86
+ preparationBasisDigest,
87
+ selectStagingSnapshot,
88
+ snapshotDifference,
89
+ snapshotStaging,
90
+ snapshotsEqual
91
+ } from "./staging.js"
92
+
93
+ export class PreparationError
94
+ extends Schema.TaggedErrorClass<PreparationError>()("PreparationError", {
95
+ reason: Schema.String,
96
+ prepared: Schema.optionalKey(CompletePreparedReleaseRef)
97
+ }) {}
98
+
99
+ export interface PreparationRequest {
100
+ readonly context: VerifiedReleaseContext
101
+ readonly graph: ReleaseGraph
102
+ readonly store: PreparedReleaseStoreShape
103
+ readonly run: RunCommand
104
+ /** Exact Git-object materializer from the source-repository boundary. */
105
+ readonly materializeSource: (
106
+ context: VerifiedReleaseContext,
107
+ destination: WorkspaceRoot
108
+ ) => Effect.Effect<StagingSnapshot, unknown>
109
+ }
110
+
111
+ type Bytes = Map<string, Uint8Array>
112
+ type Producers = Map<string, string>
113
+ type Declarations = Map<string, ReleaseGraph["artifacts"][number]>
114
+
115
+ interface DependencyMaterialization {
116
+ readonly input: ExplicitInputSnapshot
117
+ readonly bunVersion: string
118
+ }
119
+
120
+ const failure = (cause: unknown): PreparationError => PreparationError.make({
121
+ reason: cause instanceof PreparedCommitHandoffError
122
+ ? cause.reason
123
+ : cause instanceof Error
124
+ ? cause.message
125
+ : String(cause),
126
+ ...(cause instanceof PreparedCommitHandoffError ? { prepared: cause.prepared } : {})
127
+ })
128
+
129
+ const attempt = <A>(body: () => A): Effect.Effect<A, PreparationError> => Effect.try({
130
+ try: body,
131
+ catch: failure
132
+ })
133
+
134
+ const bunCompileTargets = new Set<BunCompileRuntimeIdentity["target"]>([
135
+ "bun-linux-x64",
136
+ "bun-linux-arm64",
137
+ "bun-darwin-x64",
138
+ "bun-darwin-arm64"
139
+ ])
140
+
141
+ const expectedBunCompileTarget = (argv: ReadonlyArray<string>): BunCompileRuntimeIdentity["target"] | undefined =>
142
+ (argv.length === 8 || argv.length === 9) && argv[0] === "bun" && argv[1] === "build" &&
143
+ argv[2]?.startsWith("-") === false && argv[3] === "--compile" && argv[4] === "--target" &&
144
+ argv[6] === "--outfile" && argv[7]?.startsWith("-") === false &&
145
+ (argv.length === 8 || argv[8] === "--minify") && bunCompileTargets.has(argv[5] as never)
146
+ ? argv[5] as BunCompileRuntimeIdentity["target"]
147
+ : undefined
148
+
149
+ const assertBunCompileRuntime = (argv: ReadonlyArray<string>, outcome: CommandOutcome): void => {
150
+ const expected = expectedBunCompileTarget(argv)
151
+ const runtime = outcome.bunCompileRuntime
152
+ if (expected === undefined) {
153
+ if (runtime !== undefined) throw new Error("A non-certified command reported a Bun compile runtime identity.")
154
+ return
155
+ }
156
+ if (runtime === undefined || runtime.target !== expected || !/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/u.test(runtime.bunVersion) ||
157
+ !/^[a-f0-9]{64}$/u.test(runtime.sha256)) {
158
+ throw new Error(`Certified Bun compile target ${expected} did not report its exact runtime identity.`)
159
+ }
160
+ if (expected === "bun-linux-x64") {
161
+ if (runtime.source !== "executing-bun" || runtime.cacheFile !== "executing-bun" ||
162
+ outcome.tool?.protocol !== "ts-release-executable/v1" || outcome.tool.sha256 !== runtime.sha256) {
163
+ throw new Error("Certified Linux x64 compilation did not bind the executing Bun bytes.")
164
+ }
165
+ return
166
+ }
167
+ const platform = expected.slice(4).replace("arm64", "aarch64")
168
+ if (runtime.source !== "host-cache-private-copy" || runtime.cacheFile !== `bun-${platform}-v${runtime.bunVersion}`) {
169
+ throw new Error(`Certified Bun compile target ${expected} did not bind its canonical private cache file.`)
170
+ }
171
+ }
172
+
173
+ const materializeBunDependencies = Effect.fn("materializeBunDependencies")(function*(input: {
174
+ readonly request: PreparationRequest
175
+ readonly sourceWorkspace: string
176
+ readonly sourceSnapshot: StagingSnapshot
177
+ }) {
178
+ const requiresBun = input.request.graph.preparations.some((preparation) =>
179
+ (preparation._tag === "GraphCommandCheck" || preparation._tag === "GraphCommandArtifact" ||
180
+ preparation._tag === "GraphCommandCollection" || preparation._tag === "GraphNpmPackageBuild") &&
181
+ preparation.argv[0] === "bun")
182
+ if (!requiresBun) return undefined
183
+
184
+ const lockfiles = input.sourceSnapshot.entries.filter((entry) =>
185
+ (entry.path.toString() === "bun.lock" || entry.path.toString() === "bun.lockb") &&
186
+ (entry.kind === "file" || entry.kind === "executable"))
187
+ if (lockfiles.length !== 1) {
188
+ return yield* new PreparationError({
189
+ reason: "Bun preparation requires exactly one tracked root bun.lock or bun.lockb for isolated dependency materialization."
190
+ })
191
+ }
192
+ if (input.sourceSnapshot.entries.some((entry) => entry.path.toString().split("/").includes("node_modules"))) {
193
+ return yield* new PreparationError({ reason: "Verified Git source must not contain a tracked node_modules tree." })
194
+ }
195
+
196
+ const stageRoot = input.request.context.workspace.toString()
197
+ const dependencyPath = SafeRelativePath.make("node_modules")
198
+ const stageDependencies = join(stageRoot, dependencyPath)
199
+ if (existsSync(stageDependencies)) {
200
+ return yield* new PreparationError({ reason: "Private dependency destination already exists before isolated install." })
201
+ }
202
+ const before = yield* attempt(() => snapshotStaging(stageRoot, { exclude: [dependencyPath] }))
203
+ const versionOutcome = yield* input.request.run({
204
+ argv: ["bun", "--version"],
205
+ cwd: stageRoot,
206
+ environmentNames: [],
207
+ network: "offline-cli"
208
+ }).pipe(Effect.mapError(failure))
209
+ const bunVersion = versionOutcome.stdout.trim()
210
+ if (versionOutcome.exitCode !== 0 || !/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/u.test(bunVersion)) {
211
+ return yield* new PreparationError({
212
+ reason: `Unable to establish the exact Bun dependency materializer version: ${versionOutcome.stderr.trim()}`
213
+ })
214
+ }
215
+
216
+ const outcome = yield* input.request.run({
217
+ argv: [
218
+ "bun",
219
+ "install",
220
+ "--offline",
221
+ "--frozen-lockfile",
222
+ "--ignore-scripts",
223
+ "--no-save",
224
+ "--linker=hoisted"
225
+ ],
226
+ cwd: stageRoot,
227
+ environmentNames: [],
228
+ network: "offline-cli"
229
+ }).pipe(Effect.mapError(failure))
230
+ if (outcome.exitCode !== 0) {
231
+ return yield* new PreparationError({
232
+ reason: `Isolated Bun dependency install exited ${outcome.exitCode}: ${outcome.stderr.trim()}`
233
+ })
234
+ }
235
+ if (!existsSync(stageDependencies)) yield* attempt(() => mkdirSync(stageDependencies, { mode: 0o700 }))
236
+ yield* attempt(() => {
237
+ if (lstatSync(stageDependencies).isSymbolicLink() || !lstatSync(stageDependencies).isDirectory() ||
238
+ !contained(stageRoot, realpathSync(stageDependencies))) {
239
+ throw new Error("Isolated Bun install did not produce a real dependency directory inside private staging.")
240
+ }
241
+ assertNoWorkspaceInodeAliases(stageDependencies, join(input.sourceWorkspace, "node_modules"))
242
+ })
243
+ const wholeStage = yield* attempt(() => snapshotStaging(stageRoot))
244
+ const dependencySnapshot = selectStagingSnapshot(wholeStage, [dependencyPath])
245
+ const after = yield* attempt(() => snapshotStaging(stageRoot, { exclude: [dependencyPath] }))
246
+ if (!snapshotsEqual(before, after)) {
247
+ return yield* new PreparationError({
248
+ reason: `Isolated Bun install changed source or wrote an undeclared cache: ${snapshotDifference(before, after)}.`
249
+ })
250
+ }
251
+ yield* attempt(() => assertSnapshotPresent(stageRoot, input.sourceSnapshot, "Verified source"))
252
+ const lockfile = lockfiles[0]!
253
+ const toolDigest = sha256Digest(new TextEncoder().encode(`bun@${bunVersion}`))
254
+ return {
255
+ bunVersion,
256
+ input: ExplicitInputSnapshot.make({
257
+ id: `isolated-bun-dependencies@${bunVersion}`,
258
+ path: dependencyPath,
259
+ kind: "directory",
260
+ size: dependencySnapshot.entries
261
+ .filter((entry) => entry.kind !== "directory")
262
+ .reduce((sum, entry) => sum + entry.size, 0),
263
+ digest: dependencySnapshot.digest,
264
+ materializer: `bun@${bunVersion}:install--offline--frozen-lockfile--ignore-scripts--no-save--linker=hoisted`,
265
+ materializationBasis: [lockfile.digest, toolDigest]
266
+ })
267
+ } satisfies DependencyMaterialization
268
+ })
269
+
270
+ const byCodepoint = (left: { readonly id: { toString(): string } }, right: { readonly id: { toString(): string } }): number => {
271
+ const a = left.id.toString(); const b = right.id.toString()
272
+ return a < b ? -1 : a > b ? 1 : 0
273
+ }
274
+
275
+ const pathOf = (context: VerifiedReleaseContext, path: SafeRelativePath): string =>
276
+ join(context.workspace, path)
277
+
278
+ const contained = (root: string, path: string): boolean => {
279
+ const value = relative(root, path)
280
+ return value === "" || (value !== ".." && !value.startsWith(`..${sep}`) && !isAbsolute(value))
281
+ }
282
+
283
+ const portablePathsOverlap = (left: string, right: string): boolean => {
284
+ const a = left.toLocaleLowerCase("en-US")
285
+ const b = right.toLocaleLowerCase("en-US")
286
+ return a === b || a.startsWith(`${b}/`) || b.startsWith(`${a}/`)
287
+ }
288
+
289
+ const inodeKey = (path: string): string | undefined => {
290
+ const stat = lstatSync(path, { bigint: true })
291
+ return stat.isFile() ? `${stat.dev}:${stat.ino}` : undefined
292
+ }
293
+
294
+ const fileInodes = (root: string): ReadonlySet<string> => {
295
+ const result = new Set<string>()
296
+ if (!existsSync(root)) return result
297
+ const walk = (path: string): void => {
298
+ const stat = lstatSync(path)
299
+ if (stat.isSymbolicLink()) return
300
+ const identity = inodeKey(path)
301
+ if (identity !== undefined) result.add(identity)
302
+ if (stat.isDirectory()) for (const entry of readdirSync(path)) walk(join(path, entry))
303
+ }
304
+ walk(root)
305
+ return result
306
+ }
307
+
308
+ const assertNoWorkspaceInodeAliases = (stageDependencies: string, workspaceDependencies: string): void => {
309
+ const workspaceInodes = fileInodes(workspaceDependencies)
310
+ if (workspaceInodes.size === 0) return
311
+ const walk = (path: string): void => {
312
+ const stat = lstatSync(path)
313
+ if (stat.isSymbolicLink()) return
314
+ const identity = inodeKey(path)
315
+ if (identity !== undefined && workspaceInodes.has(identity)) {
316
+ throw new Error(`Isolated dependency path ${relative(stageDependencies, path) || "."} aliases a workspace inode.`)
317
+ }
318
+ if (stat.isDirectory()) for (const entry of readdirSync(path)) walk(join(path, entry))
319
+ }
320
+ walk(stageDependencies)
321
+ }
322
+
323
+ const hashBytes = (algorithm: "sha256" | "sha512", bytes: Uint8Array): string =>
324
+ createHash(algorithm).update(bytes).digest("hex")
325
+
326
+ const stagedContext = (context: VerifiedReleaseContext, root: string): VerifiedReleaseContext =>
327
+ VerifiedReleaseContext.make({
328
+ workspace: WorkspaceRoot.make(root),
329
+ source: context.source,
330
+ package: context.package
331
+ })
332
+
333
+ const capture = (
334
+ context: VerifiedReleaseContext,
335
+ declaration: Pick<ReleaseGraph["artifacts"][number], "id" | "path" | "kind">
336
+ ): Effect.Effect<Uint8Array, PreparationError> => attempt(() => {
337
+ if (declaration.kind === "package") {
338
+ throw new Error(`Directory output ${declaration.id} cannot enter a blob store.`)
339
+ }
340
+ return new Uint8Array(secureRead(context.workspace, declaration.path).bytes)
341
+ })
342
+
343
+ const replaceReferences = (
344
+ value: string,
345
+ inputs: ReadonlyArray<ReleaseGraph["artifacts"][number]>,
346
+ outputs: ReadonlyArray<ReleaseGraph["artifacts"][number]>,
347
+ collection?: ArtifactCollectionContract
348
+ ): string => value.replace(/\{(input|output|collection):([^}]+)\}/gu, (_match, direction: string, id: string) => {
349
+ if (direction === "collection") {
350
+ if (collection === undefined || collection.id.toString() !== id) {
351
+ throw new Error(`Unresolved command collection reference collection:${id}.`)
352
+ }
353
+ return collection.root.toString()
354
+ }
355
+ const declarations = direction === "input" ? inputs : outputs
356
+ const declaration = declarations.find((candidate) => candidate.id.toString() === id)
357
+ if (declaration === undefined) throw new Error(`Unresolved command path reference ${direction}:${id}.`)
358
+ return declaration.path.toString()
359
+ })
360
+
361
+ const assertCommandContract = (
362
+ stageRoot: string,
363
+ preparation: GraphCommandCheck | GraphCommandArtifact | GraphCommandCollection,
364
+ inputs: ReadonlyArray<ReleaseGraph["artifacts"][number]>,
365
+ outputs: ReadonlyArray<ReleaseGraph["artifacts"][number]>,
366
+ sourceSnapshot: StagingSnapshot,
367
+ explicitInputs: ReadonlyArray<ExplicitInputSnapshot>
368
+ ): void => {
369
+ const cwd = resolve(stageRoot, preparation.cwd.toString())
370
+ if (!contained(stageRoot, cwd) || !existsSync(cwd) || lstatSync(cwd).isSymbolicLink() || !lstatSync(cwd).isDirectory()) {
371
+ throw new Error(`Command ${preparation.id} cwd escapes private staging or is not a real directory.`)
372
+ }
373
+ const collectionRoot = preparation._tag === "GraphCommandCollection"
374
+ ? preparation.collection.root.toString()
375
+ : undefined
376
+ const roots = [...inputs.map((item) => ["input", item.path.toString()] as const),
377
+ ...outputs.map((item) => ["output", item.path.toString()] as const),
378
+ ...(collectionRoot === undefined ? [] : [["collection", collectionRoot] as const])]
379
+ for (const [kind, path] of roots) {
380
+ const resolved = resolve(stageRoot, path)
381
+ if (!contained(stageRoot, resolved)) throw new Error(`Command ${preparation.id} ${kind} root ${path} escapes private staging.`)
382
+ }
383
+ const writablePaths = [...outputs.map((output) => output.path.toString()),
384
+ ...(collectionRoot === undefined ? [] : [collectionRoot])]
385
+ for (const outputPath of writablePaths) {
386
+ const sourceOverlap = sourceSnapshot.entries.find((entry) =>
387
+ portablePathsOverlap(entry.path.toString(), outputPath))
388
+ if (sourceOverlap !== undefined) {
389
+ throw new Error(`Command ${preparation.id} writable root ${outputPath} overlaps verified source ${sourceOverlap.path}.`)
390
+ }
391
+ const explicitOverlap = explicitInputs.find((input) =>
392
+ portablePathsOverlap(input.path.toString(), outputPath))
393
+ if (explicitOverlap !== undefined) {
394
+ throw new Error(`Command ${preparation.id} writable root ${outputPath} overlaps explicit input ${explicitOverlap.id}.`)
395
+ }
396
+ for (const input of inputs) {
397
+ const a = outputPath; const b = input.path.toString()
398
+ if (portablePathsOverlap(a, b)) {
399
+ throw new Error(`Command ${preparation.id} writable root ${a} overlaps read-only input ${b}.`)
400
+ }
401
+ }
402
+ }
403
+ for (let index = 0; index < writablePaths.length; index += 1) {
404
+ for (const other of writablePaths.slice(index + 1)) {
405
+ const a = writablePaths[index]!; const b = other
406
+ if (portablePathsOverlap(a, b)) {
407
+ throw new Error(`Command ${preparation.id} has ambiguous overlapping outputs ${a} and ${b}.`)
408
+ }
409
+ }
410
+ }
411
+ if (collectionRoot !== undefined && existsSync(resolve(stageRoot, collectionRoot))) {
412
+ throw new Error(`Command ${preparation.id} collection root ${collectionRoot} must not exist before its producer runs.`)
413
+ }
414
+ }
415
+
416
+ interface CommandExecutionResult {
417
+ readonly outputs: ReadonlyArray<readonly [string, Uint8Array]>
418
+ readonly declarations: ReadonlyArray<OutputDeclaration>
419
+ readonly collection?: PreparedArtifactCollection
420
+ readonly networkIsolation?: NetworkIsolationIdentity
421
+ readonly bunCompileRuntime?: BunCompileRuntimeIdentity
422
+ }
423
+
424
+ const assertRealDirectoryTree = (stageRoot: string, root: SafeRelativePath): void => {
425
+ const location = resolve(stageRoot, root.toString())
426
+ if (!contained(stageRoot, location) || !existsSync(location) || lstatSync(location).isSymbolicLink() ||
427
+ !lstatSync(location).isDirectory() || !contained(stageRoot, realpathSync(location))) {
428
+ throw new Error(`npm package build output root ${root} is missing, linked, or outside private staging.`)
429
+ }
430
+ const walk = (directory: string): void => {
431
+ for (const name of readdirSync(directory)) {
432
+ const path = join(directory, name)
433
+ const stat = lstatSync(path)
434
+ if (stat.isSymbolicLink()) throw new Error(`npm package build output ${relative(stageRoot, path)} is a forbidden symlink.`)
435
+ if (!contained(stageRoot, realpathSync(path))) throw new Error(`npm package build output ${relative(stageRoot, path)} escapes private staging.`)
436
+ if (stat.isDirectory()) walk(path)
437
+ else if (!stat.isFile()) throw new Error(`npm package build output ${relative(stageRoot, path)} has an unsupported filesystem kind.`)
438
+ }
439
+ }
440
+ walk(location)
441
+ }
442
+
443
+ const runNpmPackageBuild = (
444
+ request: PreparationRequest,
445
+ preparation: GraphNpmPackageBuild,
446
+ sourceSnapshot: StagingSnapshot,
447
+ explicitInputs: ReadonlyArray<ExplicitInputSnapshot>
448
+ ): Effect.Effect<CommandExecutionResult, PreparationError> => {
449
+ const stageRoot = request.context.workspace.toString()
450
+ const writable = preparation.outputRoots
451
+ const cleanup = (): void => {
452
+ for (const root of writable) rmSync(resolve(stageRoot, root.toString()), { recursive: true, force: true })
453
+ }
454
+ return Effect.gen(function*() {
455
+ const cwd = resolve(stageRoot, preparation.cwd.toString())
456
+ if (!contained(stageRoot, cwd) || !existsSync(cwd) || lstatSync(cwd).isSymbolicLink() || !lstatSync(cwd).isDirectory()) {
457
+ return yield* new PreparationError({ reason: "npm package build cwd is not a real directory inside private staging." })
458
+ }
459
+ yield* attempt(() => {
460
+ for (const root of writable) {
461
+ if (sourceSnapshot.entries.some((entry) => portablePathsOverlap(entry.path.toString(), root.toString()))) {
462
+ throw new Error(`npm package build output root ${root} overlaps verified source.`)
463
+ }
464
+ if (explicitInputs.some((input) => portablePathsOverlap(input.path.toString(), root.toString()))) {
465
+ throw new Error(`npm package build output root ${root} overlaps explicit input.`)
466
+ }
467
+ if (existsSync(resolve(stageRoot, root.toString()))) {
468
+ throw new Error(`npm package build output root ${root} must be initially absent.`)
469
+ }
470
+ }
471
+ assertSnapshotPresent(stageRoot, sourceSnapshot, "Verified source", { exclude: writable })
472
+ })
473
+ const before = yield* attempt(() => snapshotStaging(stageRoot, { exclude: writable }))
474
+ const outcome = yield* request.run({
475
+ argv: preparation.argv,
476
+ cwd,
477
+ environmentNames: [],
478
+ network: "deny"
479
+ }).pipe(Effect.mapError(failure))
480
+ if (outcome.exitCode !== 0) {
481
+ return yield* new PreparationError({
482
+ reason: `npm package build exited ${outcome.exitCode}: ${outcome.stderr.trim()}`
483
+ })
484
+ }
485
+ yield* attempt(() => assertBunCompileRuntime(preparation.argv, outcome))
486
+ const after = yield* attempt(() => snapshotStaging(stageRoot, { exclude: writable }))
487
+ if (!snapshotsEqual(before, after)) {
488
+ return yield* new PreparationError({
489
+ reason: `npm package build changed undeclared staged paths: ${snapshotDifference(before, after)}.`
490
+ })
491
+ }
492
+ yield* attempt(() => {
493
+ for (const root of writable) assertRealDirectoryTree(stageRoot, root)
494
+ assertSnapshotPresent(stageRoot, sourceSnapshot, "Verified source", { exclude: writable })
495
+ })
496
+ return {
497
+ outputs: [], declarations: [],
498
+ ...(outcome.networkIsolation === undefined ? {} : { networkIsolation: outcome.networkIsolation }),
499
+ ...(outcome.bunCompileRuntime === undefined ? {} : { bunCompileRuntime: outcome.bunCompileRuntime })
500
+ }
501
+ }).pipe(
502
+ Effect.onError(() => Effect.sync(cleanup))
503
+ )
504
+ }
505
+
506
+ const discoverCollection = (
507
+ context: VerifiedReleaseContext,
508
+ contract: ArtifactCollectionContract
509
+ ): CommandExecutionResult => {
510
+ const stageRoot = context.workspace.toString()
511
+ const root = resolve(stageRoot, contract.root.toString())
512
+ if (!contained(stageRoot, root) || !existsSync(root) || lstatSync(root).isSymbolicLink() ||
513
+ !lstatSync(root).isDirectory() || !contained(stageRoot, realpathSync(root))) {
514
+ throw new Error(`Artifact collection ${contract.id} root ${contract.root} is missing, linked, or outside private staging.`)
515
+ }
516
+ const rows: Array<{
517
+ readonly key: SafeRelativePath
518
+ readonly declaration: OutputDeclaration
519
+ readonly bytes: Uint8Array
520
+ }> = []
521
+ const foldedKeys = new Map<string, string>()
522
+ const ids = new Set<string>()
523
+ const walk = (directory: string, prefix: string): void => {
524
+ for (const entry of readdirSync(directory, { withFileTypes: true }).sort((left, right) =>
525
+ left.name < right.name ? -1 : left.name > right.name ? 1 : 0)) {
526
+ const rawKey = prefix.length === 0 ? entry.name : `${prefix}/${entry.name}`
527
+ const key = normalizeCollectionMemberKey(rawKey)
528
+ const folded = key.toString().toLocaleLowerCase("en-US")
529
+ const previous = foldedKeys.get(folded)
530
+ if (previous !== undefined) {
531
+ throw new Error(`Artifact collection ${contract.id} paths ${previous} and ${key} collide under portable case folding.`)
532
+ }
533
+ foldedKeys.set(folded, key.toString())
534
+ const location = join(directory, entry.name)
535
+ const stat = lstatSync(location)
536
+ if (stat.isSymbolicLink()) {
537
+ throw new Error(`Artifact collection ${contract.id} member ${key} is a forbidden symlink.`)
538
+ }
539
+ if (!contained(stageRoot, realpathSync(location))) {
540
+ throw new Error(`Artifact collection ${contract.id} member ${key} escapes private staging.`)
541
+ }
542
+ if (stat.isDirectory()) {
543
+ walk(location, key.toString())
544
+ continue
545
+ }
546
+ if (!stat.isFile()) {
547
+ throw new Error(`Artifact collection ${contract.id} member ${key} has an unsupported filesystem kind.`)
548
+ }
549
+ if (!key.toString().endsWith(contract.pathSuffix.toString())) {
550
+ throw new Error(
551
+ `Artifact collection ${contract.id} member ${key} does not match declared pathSuffix ${contract.pathSuffix}.`
552
+ )
553
+ }
554
+ const executable = (stat.mode & 0o111) !== 0
555
+ if ((contract.artifactKind === "executable") !== executable) {
556
+ throw new Error(
557
+ `Artifact collection ${contract.id} member ${key} does not match declared kind ${contract.artifactKind}.`
558
+ )
559
+ }
560
+ const artifactId = collectionMemberId(contract, key)
561
+ if (ids.has(artifactId.toString())) {
562
+ throw new Error(`Artifact collection ${contract.id} repeats stable artifact id ${artifactId}.`)
563
+ }
564
+ ids.add(artifactId.toString())
565
+ const declaration = OutputDeclaration.make({
566
+ id: artifactId,
567
+ path: collectionMemberPath(contract, key),
568
+ kind: contract.artifactKind,
569
+ mediaType: contract.mediaType
570
+ })
571
+ const bytes = new Uint8Array(secureRead(stageRoot, declaration.path).bytes)
572
+ const bytesIssue = collectionMemberBytesIssue(contract, bytes)
573
+ if (bytesIssue !== undefined) {
574
+ throw new Error(`Artifact collection ${contract.id} member ${key}: ${bytesIssue}.`)
575
+ }
576
+ rows.push({ key, declaration, bytes })
577
+ }
578
+ }
579
+ walk(root, "")
580
+ rows.sort((left, right) => left.key < right.key ? -1 : left.key > right.key ? 1 : 0)
581
+ if (!cardinalityAccepts(contract.cardinality, rows.length)) {
582
+ throw new Error(
583
+ `Artifact collection ${contract.id} discovered ${rows.length} members outside its declared cardinality.`
584
+ )
585
+ }
586
+ return {
587
+ outputs: rows.map((row) => [row.declaration.id.toString(), row.bytes] as const),
588
+ declarations: rows.map((row) => row.declaration),
589
+ collection: PreparedArtifactCollection.make({
590
+ contract,
591
+ members: rows.map((row) => ArtifactCollectionMember.make({
592
+ key: row.key,
593
+ artifactId: row.declaration.id
594
+ }))
595
+ })
596
+ }
597
+ }
598
+
599
+ const runCommand = (
600
+ request: PreparationRequest,
601
+ preparation: GraphCommandCheck | GraphCommandArtifact | GraphCommandCollection,
602
+ declarations: Declarations,
603
+ sourceSnapshot: StagingSnapshot,
604
+ explicitInputs: ReadonlyArray<ExplicitInputSnapshot>
605
+ ): Effect.Effect<CommandExecutionResult, PreparationError> => Effect.gen(function*() {
606
+ const inputs = preparation.inputs.map((id) => declarations.get(id.toString()) ?? (() => {
607
+ throw new Error(`Missing input ${id}.`)
608
+ })())
609
+ const outputs = preparation._tag === "GraphCommandArtifact" ? preparation.outputs : []
610
+ const collection = preparation._tag === "GraphCommandCollection" ? preparation.collection : undefined
611
+ yield* attempt(() => assertCommandContract(
612
+ request.context.workspace,
613
+ preparation,
614
+ inputs,
615
+ outputs,
616
+ sourceSnapshot,
617
+ explicitInputs
618
+ ))
619
+ const writable = [...outputs.map((output) => output.path),
620
+ ...(collection === undefined ? [] : [collection.root])]
621
+ // Every verified source and explicit-input byte remains immutable unless it
622
+ // is beneath a declared output root. Checks have no persistent writable root.
623
+ yield* attempt(() => {
624
+ assertSnapshotPresent(request.context.workspace, sourceSnapshot, "Verified source", { exclude: writable })
625
+ for (const input of explicitInputs) {
626
+ const location = join(request.context.workspace, input.path.toString())
627
+ if (!existsSync(location) || lstatSync(location).isSymbolicLink()) {
628
+ throw new Error(`Explicit input ${input.id} is missing or no longer private.`)
629
+ }
630
+ }
631
+ })
632
+ const before = yield* attempt(() => snapshotStaging(request.context.workspace, { exclude: writable }))
633
+ const argv = preparation.argv.map((part) => replaceReferences(part, inputs, outputs, collection))
634
+ const outcome = yield* request.run({
635
+ argv,
636
+ cwd: pathOf(request.context, preparation.cwd),
637
+ environmentNames: [],
638
+ network: "deny"
639
+ }).pipe(Effect.mapError(failure))
640
+ if (outcome.exitCode !== 0) {
641
+ return yield* new PreparationError({
642
+ reason: `Command ${preparation.id} exited ${outcome.exitCode}: ${outcome.stderr.trim()}`
643
+ })
644
+ }
645
+ yield* attempt(() => assertBunCompileRuntime(argv, outcome))
646
+ const after = yield* attempt(() => snapshotStaging(request.context.workspace, { exclude: writable }))
647
+ if (!snapshotsEqual(before, after)) {
648
+ return yield* new PreparationError({
649
+ reason: `Command ${preparation.id} changed undeclared staged paths: ${snapshotDifference(before, after)}. ` +
650
+ "Declare persistent bytes as command outputs; caches and scratch must not live in the staged source tree."
651
+ })
652
+ }
653
+ yield* attempt(() => assertSnapshotPresent(request.context.workspace, sourceSnapshot, "Verified source", { exclude: writable }))
654
+ if (preparation._tag === "GraphCommandCheck") return {
655
+ outputs: [], declarations: [],
656
+ ...(outcome.networkIsolation === undefined ? {} : { networkIsolation: outcome.networkIsolation }),
657
+ ...(outcome.bunCompileRuntime === undefined ? {} : { bunCompileRuntime: outcome.bunCompileRuntime })
658
+ }
659
+ if (preparation._tag === "GraphCommandCollection") {
660
+ const discovered = yield* attempt(() => discoverCollection(request.context, preparation.collection))
661
+ return {
662
+ ...discovered,
663
+ ...(outcome.networkIsolation === undefined ? {} : { networkIsolation: outcome.networkIsolation }),
664
+ ...(outcome.bunCompileRuntime === undefined ? {} : { bunCompileRuntime: outcome.bunCompileRuntime })
665
+ }
666
+ }
667
+ const produced: Array<[string, Uint8Array]> = []
668
+ for (const output of outputs) {
669
+ produced.push([output.id.toString(), yield* capture(request.context, output)])
670
+ }
671
+ return {
672
+ outputs: produced, declarations: [],
673
+ ...(outcome.networkIsolation === undefined ? {} : { networkIsolation: outcome.networkIsolation }),
674
+ ...(outcome.bunCompileRuntime === undefined ? {} : { bunCompileRuntime: outcome.bunCompileRuntime })
675
+ }
676
+ })
677
+
678
+ const structured = (
679
+ request: PreparationRequest,
680
+ preparation: GraphPreparation,
681
+ declarations: Declarations,
682
+ bytes: Bytes,
683
+ sourceSnapshot: StagingSnapshot,
684
+ explicitInputs: ReadonlyArray<ExplicitInputSnapshot>
685
+ ): Effect.Effect<CommandExecutionResult, PreparationError> => {
686
+ switch (preparation._tag) {
687
+ case "GraphCommandCheck":
688
+ case "GraphCommandArtifact":
689
+ case "GraphCommandCollection":
690
+ return runCommand(request, preparation, declarations, sourceSnapshot, explicitInputs)
691
+ case "GraphNpmPackageBuild":
692
+ return runNpmPackageBuild(request, preparation, sourceSnapshot, explicitInputs)
693
+ case "GraphArchive":
694
+ return attempt(() => {
695
+ assertSnapshotPresent(request.context.workspace, sourceSnapshot, "Verified source")
696
+ const entries: ArchiveEntry[] = preparation.inputs.map((id) => {
697
+ const declaration = declarations.get(id.toString())
698
+ const value = bytes.get(id.toString())
699
+ if (declaration === undefined || value === undefined) {
700
+ throw new Error(`Archive ${preparation.id} references unavailable artifact ${id}.`)
701
+ }
702
+ return {
703
+ path: basename(declaration.path),
704
+ data: value,
705
+ mode: declaration.kind === "executable" ? 0o100755 : 0o100644
706
+ }
707
+ }).sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0)
708
+ if (entries.length === 0) throw new Error(`Archive ${preparation.id} has no inputs.`)
709
+ if (new Set(entries.map((entry) => entry.path)).size !== entries.length) {
710
+ throw new Error(`Archive ${preparation.id} contains duplicate normalized member names.`)
711
+ }
712
+ const value = preparation.format === "zip" ? zip(entries) : tarGz(entries)
713
+ secureWrite(request.context.workspace, preparation.output.path, value)
714
+ assertSnapshotPresent(request.context.workspace, sourceSnapshot, "Verified source")
715
+ return { outputs: [[preparation.output.id.toString(), value]], declarations: [] }
716
+ })
717
+ case "GraphChecksum":
718
+ return attempt(() => {
719
+ assertSnapshotPresent(request.context.workspace, sourceSnapshot, "Verified source")
720
+ const lines = preparation.inputs.map((id) => {
721
+ const value = bytes.get(id.toString())
722
+ const declaration = declarations.get(id.toString())
723
+ if (value === undefined || declaration === undefined) {
724
+ throw new Error(`Checksum ${preparation.id} references unavailable artifact ${id}.`)
725
+ }
726
+ return `${hashBytes(preparation.algorithm, value)} ${basename(declaration.path)}`
727
+ })
728
+ const value = new TextEncoder().encode(`${lines.join("\n")}\n`)
729
+ secureWrite(request.context.workspace, preparation.output.path, value)
730
+ assertSnapshotPresent(request.context.workspace, sourceSnapshot, "Verified source")
731
+ return { outputs: [[preparation.output.id.toString(), value]], declarations: [] }
732
+ })
733
+ case "GraphCatalogRender":
734
+ return attempt(() => {
735
+ assertSnapshotPresent(request.context.workspace, sourceSnapshot, "Verified source")
736
+ const downloads = preparation.sources.map((source) => {
737
+ const value = bytes.get(source.artifactId.toString())
738
+ const declaration = declarations.get(source.artifactId.toString())
739
+ if (value === undefined || declaration === undefined || basename(declaration.path) !== source.filename.toString()) {
740
+ throw new Error(`Catalog renderer ${preparation.id} references unavailable or renamed artifact ${source.artifactId}.`)
741
+ }
742
+ return PreparedCatalogDownload.make({
743
+ architecture: source.architecture,
744
+ url: source.url,
745
+ filename: source.filename,
746
+ sha256: sha256Digest(value)
747
+ })
748
+ }) as [PreparedCatalogDownload, ...Array<PreparedCatalogDownload>]
749
+ const value = renderCatalog(preparation.version, preparation.renderer, downloads)
750
+ secureWrite(request.context.workspace, preparation.output.path, value)
751
+ assertSnapshotPresent(request.context.workspace, sourceSnapshot, "Verified source")
752
+ return { outputs: [[preparation.output.id.toString(), value]], declarations: [] }
753
+ })
754
+ }
755
+ }
756
+
757
+ interface NpmPackFile {
758
+ readonly path: string
759
+ readonly size: number
760
+ readonly mode: number
761
+ }
762
+
763
+ const assertNpmLiteralSelectionsPresent = (packageRoot: string): void => {
764
+ const manifestPath = join(packageRoot, "package.json")
765
+ const parsed: unknown = JSON.parse(readFileSync(manifestPath, "utf8"))
766
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
767
+ throw new Error("npm package manifest root must be an object.")
768
+ }
769
+ const files = (parsed as { readonly files?: unknown }).files
770
+ if (files === undefined) return
771
+ if (!Array.isArray(files) || files.some((entry) => typeof entry !== "string")) {
772
+ throw new Error("npm package files must be an array of strings.")
773
+ }
774
+ for (const entry of files as ReadonlyArray<string>) {
775
+ const normalized = entry.replace(/^\.\//u, "").replace(/\/$/u, "")
776
+ // Glob and exclusion semantics are certified by npm's reported file list;
777
+ // exact selections must already exist in private staging.
778
+ if (normalized.startsWith("!") || /[*?\[\]{}]/u.test(normalized)) continue
779
+ const safe = SafeRelativePath.make(normalized)
780
+ const selected = resolve(packageRoot, safe.toString())
781
+ if (!contained(packageRoot, selected) || !existsSync(selected) || lstatSync(selected).isSymbolicLink()) {
782
+ throw new Error(
783
+ `npm literal package selection ${entry} is absent from verified staging; declare non-Git bytes as an explicit input.`
784
+ )
785
+ }
786
+ }
787
+ }
788
+
789
+ const decodeNpmPackFiles = (stdout: string): ReadonlyArray<NpmPackFile> => {
790
+ let value: unknown
791
+ try { value = JSON.parse(stdout) } catch { throw new Error("npm pack --json did not return valid JSON.") }
792
+ if (!Array.isArray(value) || value.length !== 1 || typeof value[0] !== "object" || value[0] === null) {
793
+ throw new Error("npm pack --json must report exactly one package.")
794
+ }
795
+ const files = (value[0] as { readonly files?: unknown }).files
796
+ if (!Array.isArray(files) || files.length === 0) throw new Error("npm pack reported no selected package files.")
797
+ const decoded = files.map((file): NpmPackFile => {
798
+ if (typeof file !== "object" || file === null) throw new Error("npm pack reported a malformed file entry.")
799
+ const item = file as Record<string, unknown>
800
+ if (typeof item.path !== "string" || !SafeRelativePath.make(item.path) ||
801
+ !Number.isSafeInteger(item.size) || (item.size as number) < 0 || !Number.isSafeInteger(item.mode)) {
802
+ throw new Error("npm pack reported a malformed file path, size, or mode.")
803
+ }
804
+ return { path: item.path, size: item.size as number, mode: item.mode as number }
805
+ }).sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0)
806
+ if (new Set(decoded.map((file) => file.path.toLocaleLowerCase("en-US"))).size !== decoded.length) {
807
+ throw new Error("npm package file list contains a duplicate or case-colliding normalized path.")
808
+ }
809
+ return decoded
810
+ }
811
+
812
+ interface NpmPackIdentity {
813
+ readonly protocol: "ts-release-npm-pack/v1"
814
+ readonly version: string
815
+ readonly executable: ExecutableIdentity
816
+ readonly flags: ReadonlyArray<string>
817
+ readonly stdout: "complete-redacted-protocol"
818
+ }
819
+
820
+ const npmPackFlags = ["--json", "--offline", "--ignore-scripts", "--pack-destination", "{destination}", "--cache", "{cache}"] as const
821
+
822
+ const sameExecutable = (left: ExecutableIdentity | undefined, right: ExecutableIdentity): boolean =>
823
+ left?.protocol === right.protocol && left.command === right.command && left.sha256 === right.sha256
824
+
825
+ const establishNpmPackIdentity = (
826
+ request: PreparationRequest
827
+ ): Effect.Effect<NpmPackIdentity, PreparationError> => Effect.gen(function*() {
828
+ const outcome = yield* request.run({
829
+ argv: ["npm", "--version"],
830
+ cwd: request.context.workspace.toString(),
831
+ environmentNames: [],
832
+ network: "offline-cli"
833
+ }).pipe(Effect.mapError(failure))
834
+ const version = outcome.stdout.trim()
835
+ if (outcome.exitCode !== 0 || !/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/u.test(version)) {
836
+ return yield* new PreparationError({
837
+ reason: `Unable to establish the exact npm pack version: ${outcome.stderr.trim()}`
838
+ })
839
+ }
840
+ if (outcome.tool?.protocol !== "ts-release-executable/v1" || outcome.tool.command !== "npm" ||
841
+ !/^[a-f0-9]{64}$/u.test(outcome.tool.sha256)) {
842
+ return yield* new PreparationError({ reason: "npm --version did not report an exact executable identity." })
843
+ }
844
+ return {
845
+ protocol: "ts-release-npm-pack/v1",
846
+ version,
847
+ executable: outcome.tool,
848
+ flags: npmPackFlags,
849
+ stdout: "complete-redacted-protocol"
850
+ }
851
+ })
852
+
853
+ const npmTarball = (
854
+ request: PreparationRequest,
855
+ publication: GraphNpmPublication,
856
+ declarations: Declarations,
857
+ sourceSnapshot: StagingSnapshot,
858
+ basis: ReturnType<typeof preparationBasisDigest>,
859
+ npmPackIdentity: NpmPackIdentity
860
+ ): Effect.Effect<PreparedArtifact, PreparationError> => Effect.gen(function*() {
861
+ const packageId = publication.packageArtifact
862
+ const packageDeclaration = declarations.get(packageId.toString())
863
+ if (packageDeclaration?.kind !== "package") {
864
+ return yield* new PreparationError({
865
+ reason: `npm publication ${publication.id} does not reference its declared package artifact.`
866
+ })
867
+ }
868
+ yield* attempt(() => assertNpmLiteralSelectionsPresent(resolve(
869
+ request.context.workspace,
870
+ packageDeclaration.path.toString()
871
+ )))
872
+ assertSnapshotPresent(request.context.workspace, sourceSnapshot, "Verified source")
873
+ const destination = SafeRelativePath.make(`.release/ts-release/npm/${publication.id}`)
874
+ const cache = SafeRelativePath.make(`.release/ts-release/npm-cache/${publication.id}`)
875
+ mkdirSync(join(request.context.workspace, destination), { recursive: true })
876
+ mkdirSync(join(request.context.workspace, cache), { recursive: true })
877
+ if (readdirSync(join(request.context.workspace, destination)).length > 0) {
878
+ return yield* new PreparationError({ reason: `npm publication ${publication.id} has a non-empty output directory.` })
879
+ }
880
+ const allowed = [destination, cache]
881
+ const before = yield* attempt(() => snapshotStaging(request.context.workspace, { exclude: allowed }))
882
+ const outcome = yield* request.run({
883
+ argv: ["npm", "pack", packageDeclaration.path.toString(), "--json", "--offline", "--ignore-scripts", "--pack-destination", destination, "--cache", cache],
884
+ cwd: request.context.workspace,
885
+ environmentNames: [],
886
+ stdout: "complete-protocol",
887
+ network: "offline-cli"
888
+ }).pipe(Effect.mapError(failure))
889
+ if (!sameExecutable(outcome.tool, npmPackIdentity.executable)) {
890
+ return yield* new PreparationError({
891
+ reason: "npm pack executable identity disagrees with the preflighted npm executable."
892
+ })
893
+ }
894
+ if (outcome.exitCode !== 0) {
895
+ return yield* new PreparationError({ reason: `npm pack exited ${outcome.exitCode}: ${outcome.stderr.trim()}` })
896
+ }
897
+ const after = yield* attempt(() => snapshotStaging(request.context.workspace, { exclude: allowed }))
898
+ if (!snapshotsEqual(before, after)) {
899
+ return yield* new PreparationError({
900
+ reason: `npm pack changed package source outside its private output/cache roots: ${snapshotDifference(before, after)}.`
901
+ })
902
+ }
903
+ assertSnapshotPresent(request.context.workspace, sourceSnapshot, "Verified source", { exclude: allowed })
904
+ const packageFiles = yield* attempt(() => decodeNpmPackFiles(outcome.stdout))
905
+ for (const file of packageFiles) {
906
+ const selected = resolve(request.context.workspace, packageDeclaration.path.toString(), file.path)
907
+ if (!contained(request.context.workspace, selected) || !existsSync(selected) || lstatSync(selected).isSymbolicLink() ||
908
+ !lstatSync(selected).isFile() || lstatSync(selected).size !== file.size) {
909
+ return yield* new PreparationError({
910
+ reason: `npm selected undeclared, escaped, linked, or size-mismatched package file ${file.path}.`
911
+ })
912
+ }
913
+ }
914
+ const outputEntries = readdirSync(join(request.context.workspace, destination), { withFileTypes: true })
915
+ if (outputEntries.length !== 1 || !outputEntries[0]!.isFile() || !outputEntries[0]!.name.endsWith(".tgz")) {
916
+ return yield* new PreparationError({ reason: "npm pack produced an invalid output directory." })
917
+ }
918
+ const path = SafeRelativePath.make(`${destination}/${outputEntries[0]!.name}`)
919
+ const artifactBytes = yield* capture(request.context, { id: packageId, path, kind: "archive" })
920
+ const digest = sha256Digest(artifactBytes)
921
+ return PreparedArtifact.make({
922
+ id: packageId,
923
+ path,
924
+ kind: "archive",
925
+ size: artifactBytes.length,
926
+ digest,
927
+ blob: digest,
928
+ mediaType: "application/gzip",
929
+ producer: NonEmptyName.make(`npm-pack:${publication.id}`),
930
+ inputBasis: basis
931
+ })
932
+ })
933
+
934
+ const runtimeIdentity = (
935
+ bunVersion: string | undefined,
936
+ isolation: NetworkIsolationIdentity | undefined,
937
+ bunCompileRuntimes: ReadonlyArray<BunCompileRuntimeIdentity>,
938
+ npmPack: NpmPackIdentity | undefined,
939
+ releaseGraph: ReturnType<typeof sha256Digest>
940
+ ): PreparedExecutionInputs => PreparedExecutionInputs.make({
941
+ environment: "closed",
942
+ network: "prohibited",
943
+ timezone: "UTC",
944
+ locale: "C",
945
+ clock: "source-date-epoch=0;host-clock-not-isolated",
946
+ randomness: "host-randomness-not-isolated",
947
+ platform: `${process.platform}-${process.arch}`,
948
+ runtime: bunVersion === undefined ? `bun@${process.versions.bun}` : `bun@${bunVersion}`,
949
+ networkIsolation: isolation === undefined
950
+ ? "host-provided-network-deny/no-helper-identity"
951
+ : encodeCanonicalJson(isolation),
952
+ bunCompileRuntimes: bunCompileRuntimes.length === 0
953
+ ? "not-used"
954
+ : encodeCanonicalJson(bunCompileRuntimes),
955
+ npmPack: npmPack === undefined ? "not-used" : encodeCanonicalJson(npmPack),
956
+ releaseGraph,
957
+ preparer: "@mannyc1/ts-release@0.3.0"
958
+ })
959
+
960
+ export const prepareRelease = Effect.fn("prepareRelease")(function*(input: PreparationRequest) {
961
+ const sourceRoot = input.context.workspace.toString()
962
+ const root = yield* attempt(() => makeCanonicalTemporaryDirectory("ts-release-prepare-"))
963
+ try {
964
+ const sourceSnapshot = yield* input.materializeSource(input.context, WorkspaceRoot.make(root)).pipe(Effect.mapError(failure))
965
+ const context = stagedContext(input.context, root)
966
+ const declarations: Declarations = new Map(input.graph.artifacts.map((artifact) => [artifact.id.toString(), artifact]))
967
+ const produced = new Set(input.graph.preparations.flatMap((preparation) =>
968
+ preparation._tag === "GraphCommandArtifact"
969
+ ? preparation.outputs.map((output) => output.id.toString())
970
+ : preparation._tag === "GraphArchive" || preparation._tag === "GraphChecksum" || preparation._tag === "GraphCatalogRender"
971
+ ? [preparation.output.id.toString()]
972
+ : []))
973
+ const externalInputs: ExplicitInputSnapshot[] = []
974
+ for (const artifact of input.graph.artifacts) {
975
+ if (produced.has(artifact.id.toString()) || artifact.kind === "package") continue
976
+ const path = artifact.path.toString()
977
+ const tracked = sourceSnapshot.entries.some((entry) =>
978
+ entry.path.toString() === path || entry.path.toString().startsWith(`${path}/`))
979
+ if (tracked) continue
980
+ externalInputs.push(yield* attempt(() => materializeExplicitInput({
981
+ id: artifact.id.toString(),
982
+ sourceWorkspace: sourceRoot,
983
+ stageRoot: root,
984
+ path: artifact.path
985
+ })))
986
+ }
987
+ const request: PreparationRequest = { ...input, context }
988
+ const dependencies = yield* materializeBunDependencies({
989
+ request,
990
+ sourceWorkspace: sourceRoot,
991
+ sourceSnapshot
992
+ })
993
+ if (dependencies !== undefined) externalInputs.push(dependencies.input)
994
+ const bytes: Bytes = new Map()
995
+ const producers: Producers = new Map()
996
+ const preparedCollections = new Map<string, PreparedArtifactCollection>()
997
+ const isolationIdentities: NetworkIsolationIdentity[] = []
998
+ const bunCompileRuntimes = new Map<string, BunCompileRuntimeIdentity>()
999
+ for (const artifact of input.graph.artifacts) {
1000
+ if (produced.has(artifact.id.toString()) || artifact.kind === "package") continue
1001
+ bytes.set(artifact.id.toString(), yield* capture(context, artifact))
1002
+ producers.set(artifact.id.toString(), externalInputs.some((item) => item.id === artifact.id.toString())
1003
+ ? `explicit-input:${artifact.id}`
1004
+ : "verified-source")
1005
+ }
1006
+ for (const preparation of input.graph.preparations) {
1007
+ const result = yield* structured(request, preparation, declarations, bytes, sourceSnapshot, externalInputs)
1008
+ if (result.networkIsolation !== undefined) isolationIdentities.push(result.networkIsolation)
1009
+ if (result.bunCompileRuntime !== undefined) {
1010
+ const prior = bunCompileRuntimes.get(result.bunCompileRuntime.target)
1011
+ if (prior !== undefined && encodeCanonicalJson(prior) !== encodeCanonicalJson(result.bunCompileRuntime)) {
1012
+ return yield* new PreparationError({
1013
+ reason: `Bun compile target ${result.bunCompileRuntime.target} reported inconsistent private runtime identities.`
1014
+ })
1015
+ }
1016
+ bunCompileRuntimes.set(result.bunCompileRuntime.target, result.bunCompileRuntime)
1017
+ }
1018
+ for (const declaration of result.declarations) {
1019
+ const id = declaration.id.toString()
1020
+ const idCollision = [...declarations.keys()].find((candidate) =>
1021
+ candidate.toLocaleLowerCase("en-US") === id.toLocaleLowerCase("en-US"))
1022
+ if (idCollision !== undefined) {
1023
+ return yield* new PreparationError({
1024
+ reason: `Runtime artifact ${id} collides with existing artifact id ${idCollision}.`
1025
+ })
1026
+ }
1027
+ const path = declaration.path.toString()
1028
+ const pathCollision = [...declarations.values()].find((candidate) =>
1029
+ candidate.path.toString().toLocaleLowerCase("en-US") === path.toLocaleLowerCase("en-US"))
1030
+ if (pathCollision !== undefined) {
1031
+ return yield* new PreparationError({
1032
+ reason: `Runtime artifact ${id} path ${path} collides with ${pathCollision.id} at ${pathCollision.path}.`
1033
+ })
1034
+ }
1035
+ declarations.set(id, declaration)
1036
+ }
1037
+ if (result.collection !== undefined) {
1038
+ const id = result.collection.contract.id.toString()
1039
+ if (preparedCollections.has(id)) {
1040
+ return yield* new PreparationError({ reason: `Runtime artifact collection ${id} was produced more than once.` })
1041
+ }
1042
+ preparedCollections.set(id, result.collection)
1043
+ }
1044
+ for (const [id, value] of result.outputs) {
1045
+ bytes.set(id, value)
1046
+ producers.set(id, preparation.id.toString())
1047
+ }
1048
+ }
1049
+ const isolation = isolationIdentities[0]
1050
+ if (isolation !== undefined && isolationIdentities.some((value) =>
1051
+ encodeCanonicalJson(value) !== encodeCanonicalJson(isolation))) {
1052
+ return yield* new PreparationError({
1053
+ reason: "Network-isolated commands reported inconsistent helper, libseccomp, kernel, architecture, or syscall identities."
1054
+ })
1055
+ }
1056
+ const hasNpmPublication = input.graph.publications.some((publication) => publication._tag === "GraphNpmPublication")
1057
+ if (hasNpmPublication) yield* attempt(() => {
1058
+ for (const publication of input.graph.publications) {
1059
+ if (publication._tag !== "GraphNpmPublication") continue
1060
+ const declaration = declarations.get(publication.packageArtifact.toString())
1061
+ if (declaration?.kind !== "package") throw new Error(
1062
+ `npm publication ${publication.id} does not reference its declared package artifact.`
1063
+ )
1064
+ assertNpmLiteralSelectionsPresent(resolve(root, declaration.path.toString()))
1065
+ }
1066
+ })
1067
+ const npmPackIdentity = hasNpmPublication ? yield* establishNpmPackIdentity(request) : undefined
1068
+ const releaseGraph = sha256Digest(new TextEncoder().encode(encodeCanonicalJson(
1069
+ Schema.encodeSync(ReleaseGraph)(input.graph)
1070
+ )))
1071
+ const execution = runtimeIdentity(
1072
+ dependencies?.bunVersion,
1073
+ isolation,
1074
+ [...bunCompileRuntimes.values()].sort((left, right) => left.target < right.target ? -1 : left.target > right.target ? 1 : 0),
1075
+ npmPackIdentity,
1076
+ releaseGraph
1077
+ )
1078
+ const basis = preparationBasisDigest(sourceSnapshot, externalInputs, {
1079
+ environment: execution.environment,
1080
+ network: execution.network,
1081
+ timezone: execution.timezone,
1082
+ locale: execution.locale,
1083
+ clock: execution.clock,
1084
+ randomness: execution.randomness,
1085
+ platform: execution.platform,
1086
+ runtime: execution.runtime,
1087
+ networkIsolation: execution.networkIsolation,
1088
+ bunCompileRuntimes: execution.bunCompileRuntimes,
1089
+ npmPack: execution.npmPack,
1090
+ releaseGraph: execution.releaseGraph.hex,
1091
+ preparer: execution.preparer
1092
+ })
1093
+ const preparedArtifacts = new Map<string, PreparedArtifact>()
1094
+ for (const artifact of [...declarations.values()].sort(byCodepoint)) {
1095
+ const value = bytes.get(artifact.id.toString())
1096
+ if (value === undefined || artifact.kind === "package") continue
1097
+ const digest = sha256Digest(value)
1098
+ preparedArtifacts.set(artifact.id.toString(), PreparedArtifact.make({
1099
+ id: artifact.id,
1100
+ path: artifact.path,
1101
+ kind: artifact.kind,
1102
+ size: value.length,
1103
+ digest,
1104
+ blob: digest,
1105
+ ...(artifact.mediaType === undefined ? {} : { mediaType: artifact.mediaType }),
1106
+ producer: NonEmptyName.make(producers.get(artifact.id.toString()) ?? "verified-source"),
1107
+ inputBasis: basis
1108
+ }))
1109
+ }
1110
+ const publications: Array<PreparedNpmPublication | PreparedPyPiPublication | PreparedGitHubPublication | PreparedCatalogPublication> = []
1111
+ for (const publication of input.graph.publications) {
1112
+ if (publication._tag === "GraphNpmPublication") {
1113
+ const artifact = yield* npmTarball(request, publication, declarations, sourceSnapshot, basis, npmPackIdentity!)
1114
+ const artifactBytes = yield* capture(context, { id: artifact.id, path: artifact.path, kind: artifact.kind })
1115
+ bytes.set(artifact.id.toString(), artifactBytes)
1116
+ preparedArtifacts.set(artifact.id.toString(), artifact)
1117
+ publications.push(PreparedNpmPublication.make({
1118
+ id: NonEmptyName.make(publication.id),
1119
+ packageName: publication.packageName,
1120
+ version: Version.make(publication.version.toString()),
1121
+ registryUrl: publication.registryUrl,
1122
+ artifactId: artifact.id,
1123
+ distTag: publication.distTag,
1124
+ access: publication.access,
1125
+ authentication: publication.authentication,
1126
+ provenance: publication.provenance,
1127
+ authority: publication.authority
1128
+ }))
1129
+ } else if (publication._tag === "GraphPyPiPublication") {
1130
+ const files = publication.files.map((file) => {
1131
+ const artifact = preparedArtifacts.get(file.artifactId.toString())
1132
+ const value = bytes.get(file.artifactId.toString())
1133
+ if (artifact === undefined || value === undefined) {
1134
+ throw new Error(`PyPI publication ${publication.id} references unavailable artifact ${file.artifactId}.`)
1135
+ }
1136
+ if (basename(artifact.path.toString()) !== file.filename.toString()) {
1137
+ throw new Error(`PyPI artifact ${file.artifactId} filename changed after graph linking.`)
1138
+ }
1139
+ const distribution = inspectPythonDistribution(
1140
+ file.filename.toString(), value, publication.project.toString(), publication.version.toString()
1141
+ )
1142
+ if (artifact.mediaType !== undefined && artifact.mediaType !== distribution.mediaType) {
1143
+ throw new Error(`PyPI artifact ${file.artifactId} declared media type disagrees with its verified distribution type.`)
1144
+ }
1145
+ const typedArtifact = artifact.mediaType === distribution.mediaType
1146
+ ? artifact
1147
+ : PreparedArtifact.make({ ...artifact, mediaType: distribution.mediaType })
1148
+ preparedArtifacts.set(file.artifactId.toString(), typedArtifact)
1149
+ const digest = sha256Digest(value)
1150
+ return PreparedPyPiFile.make({
1151
+ artifactId: typedArtifact.id,
1152
+ filename: file.filename,
1153
+ size: value.length,
1154
+ sha256: digest,
1155
+ mediaType: distribution.mediaType,
1156
+ distribution,
1157
+ authority: file.authority
1158
+ })
1159
+ }) as [PreparedPyPiFile, ...Array<PreparedPyPiFile>]
1160
+ publications.push(PreparedPyPiPublication.make({
1161
+ id: NonEmptyName.make(publication.id),
1162
+ project: publication.project,
1163
+ version: publication.version,
1164
+ repository: publication.repository,
1165
+ simpleBaseUrl: publication.simpleBaseUrl,
1166
+ projectUrl: publication.projectUrl,
1167
+ uploadUrl: publication.uploadUrl,
1168
+ authentication: publication.authentication,
1169
+ files
1170
+ }))
1171
+ } else if (publication._tag === "GraphGitHubPublication") {
1172
+ const selectedIds = publication.assetCollections.flatMap((selector) => {
1173
+ const collection = preparedCollections.get(selector.collection.toString())
1174
+ if (collection === undefined) {
1175
+ throw new Error(`GitHub publication ${publication.id} collection ${selector.collection} is unavailable.`)
1176
+ }
1177
+ if (!cardinalityAccepts(selector.cardinality, collection.members.length)) {
1178
+ throw new Error(
1179
+ `GitHub publication ${publication.id} collection ${selector.collection} selected ${collection.members.length} members outside its cardinality.`
1180
+ )
1181
+ }
1182
+ return collection.members.map((member) => member.artifactId)
1183
+ })
1184
+ const assets = [...publication.assetIds, ...selectedIds].map((id) => {
1185
+ const artifact = preparedArtifacts.get(id.toString())
1186
+ if (artifact === undefined) throw new Error(`GitHub publication ${publication.id} references unavailable artifact ${id}.`)
1187
+ return {
1188
+ artifactId: artifact.id,
1189
+ name: NonEmptyName.make(basename(artifact.path)),
1190
+ mediaType: artifact.mediaType ?? "application/octet-stream"
1191
+ }
1192
+ })
1193
+ const assetNames = new Map<string, string>()
1194
+ for (const asset of assets) {
1195
+ const folded = asset.name.toLocaleLowerCase("en-US")
1196
+ const previous = assetNames.get(folded)
1197
+ if (previous !== undefined) {
1198
+ throw new Error(`GitHub publication ${publication.id} assets ${previous} and ${asset.name} have duplicate portable names.`)
1199
+ }
1200
+ assetNames.set(folded, asset.name)
1201
+ }
1202
+ const body = publication.bodyArtifact === undefined
1203
+ ? publication.body
1204
+ : new TextDecoder().decode(bytes.get(publication.bodyArtifact.toString()) ?? (() => {
1205
+ throw new Error(`GitHub body artifact ${publication.bodyArtifact} is unavailable.`)
1206
+ })())
1207
+ publications.push(PreparedGitHubPublication.make({
1208
+ id: NonEmptyName.make(publication.id),
1209
+ repository: publication.repository,
1210
+ tag: publication.tag,
1211
+ title: publication.title,
1212
+ draft: publication.draft,
1213
+ prerelease: publication.prerelease,
1214
+ targetCommit: context.source.commit,
1215
+ ...(body === undefined ? {} : { body }),
1216
+ assets,
1217
+ authority: publication.authority
1218
+ }))
1219
+ } else {
1220
+ const target = preparedArtifacts.get(publication.targetArtifact.toString())
1221
+ const targetBytes = bytes.get(publication.targetArtifact.toString())
1222
+ const render = input.graph.preparations.find((candidate): candidate is GraphCatalogRender =>
1223
+ candidate._tag === "GraphCatalogRender" && candidate.output.id.toString() === publication.targetArtifact.toString())
1224
+ if (target === undefined || targetBytes === undefined || render === undefined) {
1225
+ throw new Error(`Catalog publication ${publication.id} references no verified renderer output.`)
1226
+ }
1227
+ const downloads = publication.sources.map((source) => {
1228
+ const value = bytes.get(source.artifactId.toString())
1229
+ const declaration = declarations.get(source.artifactId.toString())
1230
+ if (value === undefined || declaration === undefined || basename(declaration.path) !== source.filename.toString()) {
1231
+ throw new Error(`Catalog publication ${publication.id} references unavailable source ${source.artifactId}.`)
1232
+ }
1233
+ return PreparedCatalogDownload.make({
1234
+ architecture: source.architecture,
1235
+ url: source.url,
1236
+ filename: source.filename,
1237
+ sha256: sha256Digest(value)
1238
+ })
1239
+ }) as [PreparedCatalogDownload, ...Array<PreparedCatalogDownload>]
1240
+ const targetDigest = sha256Digest(targetBytes)
1241
+ const stateBytes = encodeCatalogManagedState(CatalogManagedState.make({
1242
+ schemaVersion: "ts-release/catalog-state/v2",
1243
+ catalogId: publication.catalogId,
1244
+ renderer: publication.renderer._tag,
1245
+ generation: publication.version,
1246
+ status: "active",
1247
+ targetDigest,
1248
+ sourceRepository: publication.sourceRepository,
1249
+ sourceTag: publication.sourceTag
1250
+ }))
1251
+ const stateDigest = sha256Digest(stateBytes)
1252
+ const stateArtifactId = OutputId.make(`catalog-state-${publication.catalogId}`)
1253
+ if (preparedArtifacts.has(stateArtifactId.toString()) || bytes.has(stateArtifactId.toString())) {
1254
+ throw new Error(`Catalog state artifact ${stateArtifactId} collides with another prepared artifact.`)
1255
+ }
1256
+ const stateArtifact = PreparedArtifact.make({
1257
+ id: stateArtifactId,
1258
+ path: SafeRelativePath.make(`.release/catalog-state/${publication.catalogId}.json`),
1259
+ kind: "file",
1260
+ size: stateBytes.length,
1261
+ digest: stateDigest,
1262
+ blob: stateDigest,
1263
+ mediaType: "application/json",
1264
+ producer: NonEmptyName.make(publication.id.toString()),
1265
+ inputBasis: basis
1266
+ })
1267
+ bytes.set(stateArtifactId.toString(), stateBytes)
1268
+ preparedArtifacts.set(stateArtifactId.toString(), stateArtifact)
1269
+ publications.push(PreparedCatalogPublication.make({
1270
+ id: NonEmptyName.make(publication.id.toString()),
1271
+ catalogId: publication.catalogId,
1272
+ targetArtifactId: target.id,
1273
+ stateArtifactId,
1274
+ targetDigest,
1275
+ stateDigest,
1276
+ repository: publication.repository,
1277
+ branch: publication.branch,
1278
+ targetPath: publication.targetPath,
1279
+ statePath: publication.statePath,
1280
+ version: publication.version,
1281
+ sourceRepository: publication.sourceRepository,
1282
+ sourceTag: publication.sourceTag,
1283
+ renderer: PreparedCatalogRenderer.make({ renderer: publication.renderer, downloads }),
1284
+ authority: publication.authority
1285
+ }))
1286
+ }
1287
+ }
1288
+ assertSnapshotPresent(root, sourceSnapshot, "Verified source")
1289
+ const githubPublication = input.graph.publications.find((publication): publication is GraphGitHubPublication =>
1290
+ publication._tag === "GraphGitHubPublication")
1291
+ const npmPublication = input.graph.publications.find((publication): publication is GraphNpmPublication =>
1292
+ publication._tag === "GraphNpmPublication")
1293
+ const manifest = PreparedReleaseV2.make({
1294
+ kind: "complete",
1295
+ schemaVersion: "prepared-release/v2",
1296
+ source: PreparedSource.make({
1297
+ commit: context.source.commit,
1298
+ tree: context.source.tree,
1299
+ clean: true,
1300
+ packageManifestPath: context.source.packageManifestPath,
1301
+ packageManifestDigest: context.source.packageManifestDigest,
1302
+ materialized: sourceSnapshot
1303
+ }),
1304
+ project: PreparedProject.make({
1305
+ name: context.package.name,
1306
+ version: context.package.version,
1307
+ tag: githubPublication?.tag ?? NonEmptyName.make(`v${context.package.version}`),
1308
+ ...(npmPublication === undefined ? {} : { packageName: npmPublication.packageName }),
1309
+ ...(context.package.repository === undefined ? {} : { repository: context.package.repository })
1310
+ }),
1311
+ provenance: PreparedProvenance.make({
1312
+ source: sourceSnapshot,
1313
+ externalInputs,
1314
+ execution,
1315
+ inputBasis: basis,
1316
+ reproducibility: "not-asserted"
1317
+ }),
1318
+ artifacts: [...preparedArtifacts.values()].sort(byCodepoint),
1319
+ collections: [...preparedCollections.values()].sort((left, right) =>
1320
+ left.contract.id < right.contract.id ? -1 : left.contract.id > right.contract.id ? 1 : 0),
1321
+ publications
1322
+ })
1323
+ const blobMap = new Map([...bytes.entries()].filter(([id]) => preparedArtifacts.has(id)))
1324
+ return yield* input.store.commit(manifest, blobMap).pipe(Effect.mapError((cause) =>
1325
+ cause instanceof PreparedStoreError ? PreparationError.make({ reason: cause.reason }) : failure(cause)))
1326
+ } finally {
1327
+ rmSync(root, { recursive: true, force: true })
1328
+ }
1329
+ })