@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,1151 @@
1
+ import { Buffer } from "node:buffer"
2
+ import { chmod, copyFile, mkdir, mkdtemp, readFile, realpath, rm, stat, writeFile } from "node:fs/promises"
3
+ import { tmpdir } from "node:os"
4
+ import { basename, isAbsolute, join, resolve } from "node:path"
5
+ import * as Config from "effect/Config"
6
+ import * as Effect from "effect/Effect"
7
+ import * as Exit from "effect/Exit"
8
+ import * as Layer from "effect/Layer"
9
+ import * as Option from "effect/Option"
10
+ import * as Redacted from "effect/Redacted"
11
+ import * as Schema from "effect/Schema"
12
+ import * as Stream from "effect/Stream"
13
+ import * as ChildProcess from "effect/unstable/process/ChildProcess"
14
+ import type { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner"
15
+ import * as Semver from "semver"
16
+ import {
17
+ EnvironmentName,
18
+ type CanonicalAudience,
19
+ type CredentialRef,
20
+ type CredentialRequest,
21
+ type SubjectId,
22
+ type ResolvedTrustedPublishingAuthStrategy
23
+ } from "../model/authority.js"
24
+ import { redactOutput } from "../drivers/redact.js"
25
+ import {
26
+ CredentialAudienceMismatch,
27
+ CredentialProvider,
28
+ CredentialPurposeMismatch,
29
+ CredentialStrategyUnsupported,
30
+ CredentialSubjectMismatch,
31
+ CredentialUnavailable,
32
+ type AnonymousAccess,
33
+ type CredentialGrantAcquirer,
34
+ type CredentialProviderShape,
35
+ type MutationCredentialGrant,
36
+ type ScopedSecret,
37
+ type WorkloadIdentity,
38
+ makeCredentialProvider,
39
+ validateGrantForOperation
40
+ } from "../publication/authority.js"
41
+ import {
42
+ AuthorizedMutationHttp,
43
+ HttpAuthorizer,
44
+ type AuthorizedMutationHttpShape,
45
+ type HttpAuthorizerShape,
46
+ type HttpObservationRequest,
47
+ type HttpResponse,
48
+ type MutationHttpRequest,
49
+ type PublicationHttp
50
+ } from "../publication/http.js"
51
+ import {
52
+ CertifiedPublisherSpawn,
53
+ NpmUserConfigResource,
54
+ PublisherExited,
55
+ PublisherOutcomeUnknown,
56
+ RejectedBeforeStart,
57
+ makeNpmUserConfigHandle,
58
+ npmPublishArgv,
59
+ type CertifiedPublisherResult,
60
+ type CertifiedPublisherSpawnShape,
61
+ type CertifiedPublisherSpec,
62
+ type NpmPublishOperation,
63
+ type NpmPublisherSpec,
64
+ type NpmUserConfig,
65
+ type NpmUserConfigInput,
66
+ type NpmUserConfigResourceShape,
67
+ type WorkloadPublisherSpec
68
+ } from "../publication/publisher.js"
69
+ import { canonicalizeRegistryUrl } from "../release/graph.js"
70
+
71
+ export {
72
+ AuthorizedMutationHttp,
73
+ CertifiedPublisherSpawn,
74
+ HttpAuthorizer,
75
+ NpmUserConfigResource,
76
+ PublisherExited,
77
+ PublisherOutcomeUnknown,
78
+ RejectedBeforeStart
79
+ }
80
+ export type {
81
+ AuthorizedMutationHttpShape,
82
+ CertifiedPublisherResult,
83
+ CertifiedPublisherSpawnShape,
84
+ CertifiedPublisherSpec,
85
+ HttpAuthorizerShape,
86
+ HttpObservationRequest,
87
+ MutationHttpRequest,
88
+ NpmPublishOperation,
89
+ NpmPublisherSpec,
90
+ NpmUserConfig,
91
+ NpmUserConfigInput,
92
+ NpmUserConfigResourceShape,
93
+ WorkloadPublisherSpec
94
+ }
95
+
96
+ export class CredentialPlatformError
97
+ extends Schema.TaggedErrorClass<CredentialPlatformError>()("CredentialPlatformError", {
98
+ phase: Schema.Literals(["observe", "mutate", "resource", "spawn"]),
99
+ commitment: Schema.Literals(["before-dispatch", "unknown"]),
100
+ reason: Schema.NonEmptyString
101
+ }) {}
102
+
103
+ export type CredentialPlatformServices =
104
+ | CredentialProvider
105
+ | HttpAuthorizer
106
+ | AuthorizedMutationHttp
107
+ | NpmUserConfigResource
108
+ | CertifiedPublisherSpawn
109
+
110
+ // These are deliberately the only literal spellings in production code. The
111
+ // import-rules AST gate keeps OIDC request material in this host module.
112
+ const oidcRequestUrlName = EnvironmentName.make("ACTIONS_ID_TOKEN_REQUEST_URL")
113
+ const oidcRequestTokenName = EnvironmentName.make("ACTIONS_ID_TOKEN_REQUEST_TOKEN")
114
+ const oidcNames = [oidcRequestUrlName, oidcRequestTokenName] as const
115
+ const githubActionsName = "GITHUB_ACTIONS"
116
+ const githubRepositoryName = "GITHUB_REPOSITORY"
117
+ const githubWorkflowRefName = "GITHUB_WORKFLOW_REF"
118
+ const githubServerUrlName = "GITHUB_SERVER_URL"
119
+ const githubEventName = "GITHUB_EVENT_NAME"
120
+ const githubRepositoryIdName = "GITHUB_REPOSITORY_ID"
121
+ const githubRepositoryOwnerIdName = "GITHUB_REPOSITORY_OWNER_ID"
122
+ const githubRefName = "GITHUB_REF"
123
+ const githubShaName = "GITHUB_SHA"
124
+ const runnerEnvironmentName = "RUNNER_ENVIRONMENT"
125
+ const githubRunIdName = "GITHUB_RUN_ID"
126
+ const githubRunAttemptName = "GITHUB_RUN_ATTEMPT"
127
+
128
+ const portableEnvironmentName = /^[A-Za-z_][A-Za-z0-9_]*$/u
129
+ const certifiedWorkflow = /^\.github\/workflows\/[A-Za-z0-9_.-]+\.ya?ml$/u
130
+ const certifiedRepository = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/u
131
+ const certifiedWorkflowRef = /^(?:refs\/(?:heads|tags)\/[A-Za-z0-9][A-Za-z0-9._/-]*|[a-f0-9]{40}(?:[a-f0-9]{24})?)$/u
132
+ const certifiedSourceCommit = /^[a-f0-9]{40}(?:[a-f0-9]{24})?$/u
133
+ const certifiedEvent = /^[a-z][a-z0-9_]*$/u
134
+ const certifiedDecimalId = /^[1-9][0-9]*$/u
135
+ const certifiedSourceRef = /^refs\/(?:heads|tags)\/[A-Za-z0-9][A-Za-z0-9._/-]*$/u
136
+
137
+ type SecretVault = Map<string, Redacted.Redacted<string>>
138
+
139
+ interface TrustedHostSnapshot {
140
+ readonly GITHUB_ACTIONS: "true"
141
+ readonly GITHUB_REPOSITORY: string
142
+ readonly GITHUB_WORKFLOW_REF: string
143
+ readonly GITHUB_SERVER_URL: "https://github.com"
144
+ readonly GITHUB_EVENT_NAME: string
145
+ readonly GITHUB_REPOSITORY_ID: string
146
+ readonly GITHUB_REPOSITORY_OWNER_ID: string
147
+ readonly GITHUB_REF: string
148
+ readonly GITHUB_SHA: string
149
+ readonly RUNNER_ENVIRONMENT: "github-hosted"
150
+ readonly GITHUB_RUN_ID: string
151
+ readonly GITHUB_RUN_ATTEMPT: string
152
+ }
153
+
154
+ interface TrustedWorkloadMaterial {
155
+ readonly snapshot: TrustedHostSnapshot
156
+ readonly oidc: ReadonlyMap<EnvironmentName, Redacted.Redacted<string>>
157
+ }
158
+
159
+ type TrustedWorkloadVault = WeakMap<WorkloadIdentity, TrustedWorkloadMaterial>
160
+
161
+ type ObservationGrant = AnonymousAccess | ScopedSecret
162
+
163
+ type ObservationGrantMetadata =
164
+ | {
165
+ readonly _tag: "AnonymousAccess"
166
+ readonly subject: SubjectId
167
+ readonly audience: CanonicalAudience
168
+ readonly purposes: ReadonlySet<"observe" | "publish" | "correct">
169
+ }
170
+ | {
171
+ readonly _tag: "ScopedSecret"
172
+ readonly subject: SubjectId
173
+ readonly audience: CanonicalAudience
174
+ readonly purposes: ReadonlySet<"observe" | "publish" | "correct">
175
+ readonly ref: CredentialRef
176
+ }
177
+
178
+ type ObservationGrantRegistry = WeakMap<ObservationGrant, ObservationGrantMetadata>
179
+
180
+ const rememberObservationGrant = (
181
+ observationGrants: ObservationGrantRegistry,
182
+ grant: ObservationGrant
183
+ ): void => {
184
+ observationGrants.set(grant, grant._tag === "AnonymousAccess"
185
+ ? {
186
+ _tag: grant._tag,
187
+ subject: grant.subject,
188
+ audience: grant.audience,
189
+ purposes: new Set(grant.purposes)
190
+ }
191
+ : {
192
+ _tag: grant._tag,
193
+ subject: grant.subject,
194
+ audience: grant.audience,
195
+ purposes: new Set(grant.purposes),
196
+ ref: grant.ref
197
+ })
198
+ }
199
+
200
+ const vaultKey = (subject: SubjectId, name: string): string => `${subject}\u0000${name}`
201
+
202
+ const platformError = (
203
+ phase: CredentialPlatformError["phase"],
204
+ commitment: CredentialPlatformError["commitment"],
205
+ reason: string
206
+ ): CredentialPlatformError => new CredentialPlatformError({ phase, commitment, reason })
207
+
208
+ const unavailable = (request: CredentialRequest, reason: string): CredentialUnavailable =>
209
+ new CredentialUnavailable({
210
+ subject: request.subject,
211
+ provider: request.provider,
212
+ purpose: request.purpose,
213
+ reason
214
+ })
215
+
216
+ const unsupported = (request: CredentialRequest, reason: string): CredentialStrategyUnsupported =>
217
+ new CredentialStrategyUnsupported({
218
+ subject: request.subject,
219
+ provider: request.provider,
220
+ strategy: request.strategy.kind,
221
+ reason
222
+ })
223
+
224
+ const readSecret = (
225
+ request: CredentialRequest,
226
+ name: string
227
+ ): Effect.Effect<Redacted.Redacted<string>, CredentialUnavailable> =>
228
+ Config.redacted(name).pipe(
229
+ Effect.mapError(() => unavailable(request, `Credential reference ${name} is unavailable.`))
230
+ )
231
+
232
+ const trustedStrategyIssue = (request: CredentialRequest): string | undefined => {
233
+ const strategy = request.strategy
234
+ if (strategy.kind !== "trusted-publishing") return "The request does not carry trusted-publishing intent."
235
+ if (request.provider !== "npm" || request.audience !== "https://registry.npmjs.org/" ||
236
+ request.purpose !== "publish") {
237
+ return "Trusted publishing is certified only for npm publish at the canonical npm registry."
238
+ }
239
+ if (strategy.identityProvider !== "github-actions" || strategy.runnerClass !== "github-hosted" ||
240
+ !certifiedRepository.test(strategy.repository) || !certifiedWorkflow.test(strategy.workflow) ||
241
+ !certifiedWorkflowRef.test(strategy.workflowRef) ||
242
+ !certifiedSourceCommit.test(strategy.sourceCommit) ||
243
+ strategy.provenanceEnvironmentContract !== "github-actions-npm-provenance-v1" ||
244
+ strategy.allowedAction !== "npm-publish-direct" || strategy.publisherSink !== "certified-npm-cli") {
245
+ return "Trusted publishing requires one exact GitHub-hosted workflow/ref, source commit, provenance environment contract, and certified direct npm sink."
246
+ }
247
+ return undefined
248
+ }
249
+
250
+ /** Validate non-secret GitHub host facts before either OIDC request value is read. */
251
+ const verifyTrustedHostIdentity = Effect.fn("EnvironmentCredentialProvider.verifyTrustedHostIdentity")(
252
+ function*(request: CredentialRequest) {
253
+ const issue = trustedStrategyIssue(request)
254
+ if (issue !== undefined) return yield* unsupported(request, issue)
255
+ if (request.strategy.kind !== "trusted-publishing") return yield* unsupported(request, issue ?? "Invalid strategy.")
256
+ const expected: ReadonlyArray<readonly [string, string]> = [
257
+ [githubActionsName, "true"],
258
+ [githubRepositoryName, request.strategy.repository],
259
+ [githubWorkflowRefName,
260
+ `${request.strategy.repository}/${request.strategy.workflow}@${request.strategy.workflowRef}`],
261
+ [githubServerUrlName, "https://github.com"],
262
+ [githubShaName, request.strategy.sourceCommit],
263
+ [runnerEnvironmentName, "github-hosted"]
264
+ ]
265
+ const values: Record<string, string> = {}
266
+ for (const [name, value] of expected) {
267
+ const observed = yield* Config.string(name).pipe(
268
+ Effect.mapError(() => unsupported(request, `Trusted publishing host identity ${name} is unavailable.`))
269
+ )
270
+ if (observed !== value) {
271
+ return yield* unsupported(request, `Trusted publishing host identity ${name} does not match prepared intent.`)
272
+ }
273
+ values[name] = observed
274
+ }
275
+ const shaped: ReadonlyArray<readonly [string, RegExp, string]> = [
276
+ [githubEventName, certifiedEvent, "a canonical GitHub event name"],
277
+ [githubRepositoryIdName, certifiedDecimalId, "a canonical positive decimal repository ID"],
278
+ [githubRepositoryOwnerIdName, certifiedDecimalId, "a canonical positive decimal repository-owner ID"],
279
+ [githubRefName, certifiedSourceRef, "a canonical heads/tags source ref"],
280
+ [githubRunIdName, certifiedDecimalId, "a canonical positive decimal run ID"],
281
+ [githubRunAttemptName, certifiedDecimalId, "a canonical positive decimal run attempt"]
282
+ ]
283
+ for (const [name, pattern, description] of shaped) {
284
+ const observed = yield* Config.string(name).pipe(
285
+ Effect.mapError(() => unsupported(request, `Trusted publishing provenance fact ${name} is unavailable.`))
286
+ )
287
+ if (!pattern.test(observed)) {
288
+ return yield* unsupported(request, `Trusted publishing provenance fact ${name} is not ${description}.`)
289
+ }
290
+ values[name] = observed
291
+ }
292
+ return Object.freeze({
293
+ GITHUB_ACTIONS: values[githubActionsName] as "true",
294
+ GITHUB_REPOSITORY: values[githubRepositoryName]!,
295
+ GITHUB_WORKFLOW_REF: values[githubWorkflowRefName]!,
296
+ GITHUB_SERVER_URL: values[githubServerUrlName] as "https://github.com",
297
+ GITHUB_EVENT_NAME: values[githubEventName]!,
298
+ GITHUB_REPOSITORY_ID: values[githubRepositoryIdName]!,
299
+ GITHUB_REPOSITORY_OWNER_ID: values[githubRepositoryOwnerIdName]!,
300
+ GITHUB_REF: values[githubRefName]!,
301
+ GITHUB_SHA: values[githubShaName]!,
302
+ RUNNER_ENVIRONMENT: values[runnerEnvironmentName] as "github-hosted",
303
+ GITHUB_RUN_ID: values[githubRunIdName]!,
304
+ GITHUB_RUN_ATTEMPT: values[githubRunAttemptName]!
305
+ }) satisfies TrustedHostSnapshot
306
+ }
307
+ )
308
+
309
+ /**
310
+ * A raw environment token has no host-verifiable downscope metadata. Treat it
311
+ * conservatively as bundled provider authority instead of relabeling the same
312
+ * bearer as a purpose-specific secret merely because one method requested it.
313
+ */
314
+ const environmentTokenPurposes = (
315
+ purpose: CredentialRequest["purpose"]
316
+ ): readonly ["observe", "publish", ...Array<"correct">] => purpose === "correct"
317
+ ? ["observe", "publish", "correct"]
318
+ : ["observe", "publish"]
319
+
320
+ const makeEnvironmentAcquirer = (
321
+ vault: SecretVault,
322
+ onTrustedWorkload?: (material: TrustedWorkloadMaterial) => void
323
+ ): CredentialGrantAcquirer => ({
324
+ acquire: Effect.fn("EnvironmentCredentialProvider.acquire")(function*(request) {
325
+ switch (request.strategy.kind) {
326
+ case "anonymous":
327
+ return { _tag: "AnonymousAccess", purposes: ["observe"] } as const
328
+ case "token": {
329
+ if (request.provider === "npm" && request.purpose === "observe") {
330
+ return yield* unsupported(
331
+ request,
332
+ "Authenticated npm observation is unsupported without a distinct typed read credential."
333
+ )
334
+ }
335
+ const name = request.strategy.credential.toString()
336
+ if (!portableEnvironmentName.test(name)) {
337
+ return yield* unsupported(request, "Token credential references must be portable environment names.")
338
+ }
339
+ const value = yield* readSecret(request, name)
340
+ vault.set(vaultKey(request.subject, name), value)
341
+ return {
342
+ _tag: "ScopedSecret",
343
+ purposes: environmentTokenPurposes(request.purpose),
344
+ ref: request.strategy.credential
345
+ } as const
346
+ }
347
+ case "trusted-publishing": {
348
+ if (onTrustedWorkload === undefined) {
349
+ return yield* unsupported(request, "Trusted workload acquisition requires a private host-material binding.")
350
+ }
351
+ const snapshot = yield* verifyTrustedHostIdentity(request)
352
+ const oidcValues = new Map<EnvironmentName, Redacted.Redacted<string>>()
353
+ for (const name of oidcNames) {
354
+ const value = yield* readSecret(request, name)
355
+ oidcValues.set(name, value)
356
+ }
357
+ onTrustedWorkload(Object.freeze({ snapshot, oidc: oidcValues }))
358
+ return {
359
+ _tag: "WorkloadIdentity",
360
+ purposes: [request.purpose],
361
+ names: oidcNames
362
+ } as const
363
+ }
364
+ }
365
+ })
366
+ })
367
+
368
+ const makeProvider = (
369
+ vault: SecretVault,
370
+ trustedWorkloads: TrustedWorkloadVault,
371
+ observationGrants: ObservationGrantRegistry
372
+ ): CredentialProviderShape => {
373
+ const provider = makeCredentialProvider(makeEnvironmentAcquirer(vault))
374
+ return {
375
+ acquireForObservation: Effect.fn("EnvironmentCredentialProvider.acquireForObservation")(function*(request) {
376
+ const grant = yield* provider.acquireForObservation(request)
377
+ if (grant._tag !== "WorkloadIdentity") {
378
+ rememberObservationGrant(observationGrants, grant)
379
+ }
380
+ return grant
381
+ }),
382
+ acquireForMutation: Effect.fn("EnvironmentCredentialProvider.acquireForMutation")(function*(request, decision) {
383
+ let grant: MutationCredentialGrant
384
+ if (request.strategy.kind === "trusted-publishing") {
385
+ let material: TrustedWorkloadMaterial | undefined
386
+ const trustedProvider = makeCredentialProvider(makeEnvironmentAcquirer(
387
+ vault,
388
+ (acquired) => { material = acquired }
389
+ ))
390
+ grant = yield* trustedProvider.acquireForMutation(request, decision)
391
+ if (grant._tag !== "WorkloadIdentity" || material === undefined) {
392
+ return yield* unavailable(request, "Trusted publishing host evidence was not bound to its workload grant.")
393
+ }
394
+ trustedWorkloads.set(grant, material)
395
+ } else {
396
+ grant = yield* provider.acquireForMutation(request, decision)
397
+ }
398
+ // npm publication tokens never become observation capabilities. GitHub
399
+ // retains its truthfully bundled installation-token read/write model.
400
+ if (grant._tag === "ScopedSecret" && request.provider !== "npm") {
401
+ rememberObservationGrant(observationGrants, grant)
402
+ }
403
+ return grant
404
+ })
405
+ }
406
+ }
407
+
408
+ /** Environment-backed provider projection for hosts that need no sinks. */
409
+ export const makeEnvironmentCredentialProvider = (): CredentialProviderShape =>
410
+ makeProvider(new Map(), new WeakMap(), new WeakMap())
411
+
412
+ const hasAuthorizationHeader = (headers: Readonly<Record<string, string>> | undefined): boolean =>
413
+ headers !== undefined && Object.keys(headers).some((name) => name.toLowerCase() === "authorization")
414
+
415
+ const audienceAllows = (audience: CanonicalAudience, requested: string): boolean => {
416
+ try {
417
+ const expected = new URL(audience)
418
+ const observed = new URL(requested)
419
+ if (expected.protocol !== "https:" || observed.protocol !== "https:") return false
420
+ if (expected.username !== "" || expected.password !== "" || expected.search !== "" || expected.hash !== "") return false
421
+ if (expected.origin !== observed.origin) return false
422
+ const base = expected.pathname.endsWith("/") ? expected.pathname : `${expected.pathname}/`
423
+ return observed.pathname === expected.pathname || observed.pathname.startsWith(base)
424
+ } catch {
425
+ return false
426
+ }
427
+ }
428
+
429
+ const githubUploadAudienceAllows = (audience: CanonicalAudience, requested: string): boolean => {
430
+ try {
431
+ const expected = new URL(audience)
432
+ const observed = new URL(requested)
433
+ if (expected.protocol !== "https:" || expected.hostname !== "api.github.com" ||
434
+ expected.port !== "" || expected.username !== "" || expected.password !== "" ||
435
+ expected.search !== "" || expected.hash !== "") return false
436
+ if (observed.protocol !== "https:" || observed.hostname !== "uploads.github.com" ||
437
+ observed.port !== "" || observed.username !== "" || observed.password !== "" ||
438
+ observed.hash !== "") return false
439
+ const repository = /^\/repos\/[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/u.exec(expected.pathname)?.[0]
440
+ if (repository === undefined ||
441
+ !new RegExp(`^${repository.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}\/releases\/[1-9][0-9]*\/assets$`, "u")
442
+ .test(observed.pathname)) return false
443
+ const keys = [...observed.searchParams.keys()]
444
+ return /^\?name=[^&]+(?:&label=[^&]*)?$/u.test(observed.search) &&
445
+ new Set(keys).size === keys.length && observed.searchParams.get("name")!.length > 0
446
+ } catch {
447
+ return false
448
+ }
449
+ }
450
+
451
+ const checkAudience = (
452
+ subject: SubjectId,
453
+ audience: CanonicalAudience,
454
+ requested: string,
455
+ allowGithubUpload = false
456
+ ): Effect.Effect<void, CredentialAudienceMismatch> => audienceAllows(audience, requested) ||
457
+ (allowGithubUpload && githubUploadAudienceAllows(audience, requested))
458
+ ? Effect.void
459
+ : Effect.fail(new CredentialAudienceMismatch({
460
+ subject,
461
+ expected: audience,
462
+ observed: requested
463
+ }))
464
+
465
+ const lookupToken = (
466
+ vault: SecretVault,
467
+ subject: SubjectId,
468
+ ref: CredentialRef
469
+ ): Effect.Effect<Redacted.Redacted<string>, CredentialUnavailable> => {
470
+ const value = vault.get(vaultKey(subject, ref))
471
+ return value === undefined
472
+ ? Effect.fail(new CredentialUnavailable({
473
+ subject,
474
+ provider: "unknown",
475
+ purpose: "unknown",
476
+ reason: "The credential grant has no host-owned secret in this platform boundary."
477
+ }))
478
+ : Effect.succeed(value)
479
+ }
480
+
481
+ const validateObservationGrant = Effect.fn("HttpAuthorizer.validateGrant")(function*(
482
+ input: HttpObservationRequest,
483
+ grant: ObservationGrant,
484
+ observationGrants: ObservationGrantRegistry
485
+ ) {
486
+ const issued = observationGrants.get(grant)
487
+ if (issued === undefined) {
488
+ return yield* new CredentialUnavailable({
489
+ subject: input.subject,
490
+ provider: "unknown",
491
+ purpose: "observe",
492
+ reason: "The supplied observation grant was not issued by this platform boundary."
493
+ })
494
+ }
495
+ if (issued.subject !== input.subject) {
496
+ return yield* new CredentialSubjectMismatch({
497
+ expected: input.subject,
498
+ observed: issued.subject
499
+ })
500
+ }
501
+ if (!issued.purposes.has("observe")) {
502
+ return yield* new CredentialPurposeMismatch({
503
+ subject: input.subject,
504
+ required: "observe",
505
+ granted: [...issued.purposes]
506
+ })
507
+ }
508
+ yield* checkAudience(input.subject, issued.audience, input.url)
509
+ return issued
510
+ })
511
+
512
+ const sendAnonymous = (
513
+ http: PublicationHttp,
514
+ request: HttpObservationRequest
515
+ ): Effect.Effect<HttpResponse, CredentialPlatformError> => {
516
+ if (hasAuthorizationHeader(request.headers)) {
517
+ return Effect.fail(platformError("observe", "before-dispatch", "Callers may not supply an Authorization header."))
518
+ }
519
+ return http.request({
520
+ method: request.method,
521
+ url: request.url,
522
+ ...(request.headers === undefined ? {} : { headers: request.headers }),
523
+ ...(request.body === undefined ? {} : { body: request.body })
524
+ }).pipe(
525
+ Effect.mapError(() => platformError("observe", "unknown", "Observation HTTP transport failed after dispatch."))
526
+ )
527
+ }
528
+
529
+ const sendAuthorized = (
530
+ http: PublicationHttp,
531
+ request: HttpObservationRequest | MutationHttpRequest,
532
+ token: Redacted.Redacted<string>,
533
+ phase: "observe" | "mutate",
534
+ credentialScheme: "bearer" | "pypi-token-basic" = "bearer"
535
+ ): Effect.Effect<HttpResponse, CredentialPlatformError> => {
536
+ if (hasAuthorizationHeader(request.headers)) {
537
+ return Effect.fail(platformError(phase, "before-dispatch", "Callers may not supply an Authorization header."))
538
+ }
539
+ const headers = {
540
+ ...(request.headers ?? {}),
541
+ authorization: credentialScheme === "pypi-token-basic"
542
+ ? `Basic ${Buffer.from(`__token__:${Redacted.value(token)}`, "utf8").toString("base64")}`
543
+ : `Bearer ${Redacted.value(token)}`
544
+ }
545
+ return http.request({
546
+ method: request.method,
547
+ url: request.url,
548
+ headers,
549
+ ...(request.body === undefined ? {} : { body: request.body })
550
+ }).pipe(
551
+ Effect.mapError(() => platformError(phase, "unknown", "Authorized HTTP transport failed after dispatch."))
552
+ )
553
+ }
554
+
555
+ const makeHttpAuthorizer = (
556
+ http: PublicationHttp,
557
+ vault: SecretVault,
558
+ observationGrants: ObservationGrantRegistry
559
+ ): HttpAuthorizerShape => ({
560
+ execute: Effect.fn("HttpAuthorizer.execute")(function*(input, grant) {
561
+ const issued = yield* validateObservationGrant(input, grant, observationGrants)
562
+ if (issued._tag === "AnonymousAccess") {
563
+ return yield* sendAnonymous(http, input)
564
+ }
565
+ const token = yield* lookupToken(vault, issued.subject, issued.ref)
566
+ return yield* sendAuthorized(http, input, token, "observe")
567
+ })
568
+ })
569
+
570
+ const makeAuthorizedMutationHttp = (
571
+ http: PublicationHttp,
572
+ vault: SecretVault
573
+ ): AuthorizedMutationHttpShape => ({
574
+ execute: Effect.fn("AuthorizedMutationHttp.execute")(function*(operation, request, grant) {
575
+ yield* validateGrantForOperation(operation, grant)
576
+ if ((operation.provider === "pypi") !== (request.credentialScheme === "pypi-token-basic")) {
577
+ return yield* platformError(
578
+ "mutate", "before-dispatch",
579
+ "The PyPI Basic credential projection is accepted only for typed PyPI mutations and is mandatory for them."
580
+ )
581
+ }
582
+ yield* checkAudience(operation.subject, operation.audience, request.url, operation.provider === "github")
583
+ const token = yield* lookupToken(vault, grant.subject, grant.ref)
584
+ return yield* sendAuthorized(http, request, token, "mutate", request.credentialScheme)
585
+ })
586
+ })
587
+
588
+ type NpmUserConfigMetadata = {
589
+ readonly directory: string
590
+ readonly path: string
591
+ readonly globalConfigPath: string
592
+ readonly subject: SubjectId
593
+ readonly grant: ScopedSecret
594
+ readonly token: Redacted.Redacted<string>
595
+ }
596
+
597
+ const npmUserConfigs = new WeakMap<NpmUserConfig, NpmUserConfigMetadata>()
598
+
599
+ const npmAuthLine = (registryUrl: string, token: Redacted.Redacted<string>): string => {
600
+ const registry = new URL(registryUrl)
601
+ const pathname = registry.pathname.endsWith("/") ? registry.pathname : `${registry.pathname}/`
602
+ return `//${registry.host}${pathname}:_authToken=${Redacted.value(token)}`
603
+ }
604
+
605
+ const makeNpmUserConfigResource = (
606
+ vault: SecretVault,
607
+ temporaryRoot?: string
608
+ ): NpmUserConfigResourceShape => ({
609
+ acquire: Effect.fn("NpmUserConfigResource.acquire")(function*(input, grant) {
610
+ yield* validateGrantForOperation(input.operation, grant)
611
+ if (input.operation.provider !== "npm") {
612
+ return yield* platformError("resource", "before-dispatch", "npm user config accepts only the npm provider.")
613
+ }
614
+ yield* checkAudience(input.operation.subject, input.operation.audience, input.registryUrl)
615
+ const token = yield* lookupToken(vault, grant.subject, grant.ref)
616
+ const root = temporaryRoot ?? tmpdir()
617
+ const handle = makeNpmUserConfigHandle()
618
+ const acquired = Effect.tryPromise({
619
+ try: async () => {
620
+ await mkdir(root, { recursive: true, mode: 0o700 })
621
+ const directory = await mkdtemp(join(root, "ts-release-npm-"))
622
+ try {
623
+ const path = join(directory, "userconfig")
624
+ const globalConfigPath = join(directory, "globalconfig")
625
+ const contents = `${npmAuthLine(input.registryUrl, token)}\nignore-scripts=true\n`
626
+ await writeFile(path, contents, { mode: 0o600, flag: "wx" })
627
+ await writeFile(globalConfigPath, "", { mode: 0o600, flag: "wx" })
628
+ npmUserConfigs.set(handle, {
629
+ directory,
630
+ path,
631
+ globalConfigPath,
632
+ subject: input.operation.subject,
633
+ grant,
634
+ token
635
+ })
636
+ return handle
637
+ } catch (cause) {
638
+ await rm(directory, { recursive: true, force: true })
639
+ throw cause
640
+ }
641
+ },
642
+ catch: () => platformError("resource", "before-dispatch", "Unable to create the scoped npm user config.")
643
+ })
644
+ return yield* Effect.acquireRelease(acquired, (resource) => {
645
+ const metadata = npmUserConfigs.get(resource)
646
+ npmUserConfigs.delete(resource)
647
+ return metadata === undefined
648
+ ? Effect.void
649
+ : Effect.tryPromise({
650
+ try: () => rm(metadata.directory, { recursive: true, force: true }),
651
+ catch: () => undefined
652
+ }).pipe(Effect.catch(() => Effect.void))
653
+ })
654
+ })
655
+ })
656
+
657
+ const collect = (stream: Stream.Stream<Uint8Array, unknown>): Effect.Effect<string, unknown> =>
658
+ Stream.mkString(Stream.decodeText(stream))
659
+
660
+ const minimumTrustedNode = [22, 14, 0] as const
661
+ const minimumTrustedNpm = [11, 5, 1] as const
662
+
663
+ const parseVersion = (value: string): readonly [number, number, number] | undefined => {
664
+ const match = /^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?\s*$/u.exec(value)
665
+ if (match === null) return undefined
666
+ return [Number(match[1]), Number(match[2]), Number(match[3])]
667
+ }
668
+
669
+ const versionAtLeast = (
670
+ value: readonly [number, number, number],
671
+ minimum: readonly [number, number, number]
672
+ ): boolean => value[0] > minimum[0] ||
673
+ (value[0] === minimum[0] && (value[1] > minimum[1] ||
674
+ (value[1] === minimum[1] && value[2] >= minimum[2])))
675
+
676
+ const optionalPath = Config.option(Config.string("PATH")).pipe(
677
+ Effect.map(Option.getOrUndefined),
678
+ Effect.orElseSucceed(() => undefined)
679
+ )
680
+
681
+ const closedBaseEnvironment = (path: string | undefined): Record<string, string> =>
682
+ path === undefined ? {} : { PATH: path }
683
+
684
+ const optionalReleaseHome = Config.option(Config.string("TS_RELEASE_HOME")).pipe(
685
+ Effect.map(Option.getOrUndefined),
686
+ Effect.orElseSucceed(() => undefined)
687
+ )
688
+
689
+ const privateTrustedNpmEnvironment = Effect.fn("CertifiedPublisherSpawn.privateTrustedNpmEnvironment")(
690
+ function*(grant: WorkloadIdentity) {
691
+ const configured = yield* optionalReleaseHome
692
+ if (configured === undefined || !isAbsolute(configured) || resolve(configured) !== configured) {
693
+ return yield* new CredentialUnavailable({
694
+ subject: grant.subject,
695
+ provider: grant.provider,
696
+ purpose: "publish",
697
+ reason: "Trusted npm publishing requires one canonical absolute TS_RELEASE_HOME."
698
+ })
699
+ }
700
+ return yield* Effect.tryPromise({
701
+ try: async () => {
702
+ const canonicalHome = await realpath(configured)
703
+ if (canonicalHome !== configured) throw new Error("TS_RELEASE_HOME is not canonical.")
704
+ const homeMetadata = await stat(canonicalHome)
705
+ const owner = typeof process.getuid === "function" ? process.getuid() : undefined
706
+ if (!homeMetadata.isDirectory() || (homeMetadata.mode & 0o077) !== 0 ||
707
+ (owner !== undefined && homeMetadata.uid !== owner)) {
708
+ throw new Error("TS_RELEASE_HOME is not one private caller-owned directory.")
709
+ }
710
+ const userConfig = join(canonicalHome, "npm-userconfig")
711
+ const globalConfig = join(canonicalHome, "npm-globalconfig")
712
+ for (const path of [userConfig, globalConfig]) {
713
+ if (await realpath(path) !== path) throw new Error("A trusted npm config path is not canonical.")
714
+ const metadata = await stat(path)
715
+ if (!metadata.isFile() || (metadata.mode & 0o777) !== 0o600 ||
716
+ (owner !== undefined && metadata.uid !== owner) || (await readFile(path)).length !== 0) {
717
+ throw new Error("A trusted npm config is not an empty private caller-owned file.")
718
+ }
719
+ }
720
+ return {
721
+ HOME: canonicalHome,
722
+ NPM_CONFIG_USERCONFIG: userConfig,
723
+ NPM_CONFIG_GLOBALCONFIG: globalConfig
724
+ }
725
+ },
726
+ catch: () => new CredentialUnavailable({
727
+ subject: grant.subject,
728
+ provider: grant.provider,
729
+ purpose: "publish",
730
+ reason: "Trusted npm publishing requires validated empty private npm configuration files."
731
+ })
732
+ })
733
+ }
734
+ )
735
+
736
+ const workloadEnvironment = Effect.fn("CertifiedPublisherSpawn.workloadEnvironment")(function*(
737
+ trustedWorkloads: TrustedWorkloadVault,
738
+ grant: WorkloadIdentity
739
+ ) {
740
+ if (grant.names.size !== oidcNames.length || oidcNames.some((name) => !grant.names.has(name))) {
741
+ return yield* new CredentialStrategyUnsupported({
742
+ subject: grant.subject,
743
+ provider: grant.provider,
744
+ strategy: "trusted-publishing",
745
+ reason: "Workload identity lacks the exact certified GitHub Actions OIDC names."
746
+ })
747
+ }
748
+ const material = trustedWorkloads.get(grant)
749
+ if (material === undefined) {
750
+ return yield* new CredentialUnavailable({
751
+ subject: grant.subject,
752
+ provider: grant.provider,
753
+ purpose: "publish",
754
+ reason: "Certified GitHub Actions provenance facts are unavailable in this platform boundary."
755
+ })
756
+ }
757
+ const path = yield* optionalPath
758
+ const privateEnvironment = yield* privateTrustedNpmEnvironment(grant)
759
+ const env: Record<string, string> = {
760
+ ...closedBaseEnvironment(path),
761
+ ...privateEnvironment
762
+ }
763
+ for (const name of oidcNames) {
764
+ const value = material.oidc.get(name)
765
+ if (value === undefined) {
766
+ return yield* new CredentialUnavailable({
767
+ subject: grant.subject,
768
+ provider: grant.provider,
769
+ purpose: "publish",
770
+ reason: "Certified workload identity material is unavailable in this platform boundary."
771
+ })
772
+ }
773
+ env[name] = Redacted.value(value)
774
+ }
775
+ // npm's OIDC exchange consumes the two host-held request values. Its
776
+ // libnpmpublish provenance builder separately consumes this exact validated
777
+ // non-secret GitHub run snapshot. No ambient environment is inherited.
778
+ Object.assign(env, material.snapshot)
779
+ env.NPM_CONFIG_IGNORE_SCRIPTS = "true"
780
+ return env
781
+ })
782
+
783
+ const validateTrustedNpmOperation = Effect.fn("CertifiedPublisherSpawn.validateTrustedNpmOperation")(
784
+ function*(operation: NpmPublishOperation, grant: WorkloadIdentity) {
785
+ if (operation.provider !== "npm" || operation.purpose !== "publish" ||
786
+ operation.audience !== "https://registry.npmjs.org/" ||
787
+ grant.strategy.identityProvider !== "github-actions" ||
788
+ grant.strategy.runnerClass !== "github-hosted" ||
789
+ grant.strategy.allowedAction !== "npm-publish-direct" ||
790
+ grant.strategy.publisherSink !== "certified-npm-cli") {
791
+ return yield* new CredentialStrategyUnsupported({
792
+ subject: operation.subject,
793
+ provider: operation.provider,
794
+ strategy: "trusted-publishing",
795
+ reason: "The workload grant is not bound to the certified direct npm publisher sink."
796
+ })
797
+ }
798
+ }
799
+ )
800
+
801
+ const publisherSpecKeys = Object.freeze({
802
+ NpmPublisherSpec: [
803
+ "_tag", "access", "cwd", "distTag", "operation", "packageName", "provenance",
804
+ "registryUrl", "tarballPath", "userConfig", "version"
805
+ ],
806
+ WorkloadPublisherSpec: [
807
+ "_tag", "access", "cwd", "distTag", "operation", "packageName", "provenance",
808
+ "registryUrl", "tarballPath", "version"
809
+ ]
810
+ } satisfies Record<CertifiedPublisherSpec["_tag"], ReadonlyArray<string>>)
811
+
812
+ const sameKeys = (value: object, expected: ReadonlyArray<string>): boolean => {
813
+ const observed = Object.keys(value).sort()
814
+ return observed.length === expected.length && observed.every((key, index) => key === expected[index])
815
+ }
816
+
817
+ const certifiedNpmSpecIssue = (spec: CertifiedPublisherSpec): string | undefined => {
818
+ const tagDescriptor = Object.getOwnPropertyDescriptor(spec, "_tag")
819
+ const tag: unknown = tagDescriptor !== undefined && "value" in tagDescriptor
820
+ ? tagDescriptor.value
821
+ : undefined
822
+ if (tag !== "NpmPublisherSpec" && tag !== "WorkloadPublisherSpec") {
823
+ return "The certified npm publisher accepts only its two closed typed spec variants."
824
+ }
825
+ const expectedKeys = publisherSpecKeys[tag]
826
+ const descriptors = Object.getOwnPropertyDescriptors(spec)
827
+ if (!sameKeys(spec, expectedKeys) || expectedKeys.some((key) => {
828
+ const descriptor = descriptors[key]
829
+ return descriptor === undefined || !("value" in descriptor)
830
+ })) {
831
+ return "The certified npm publisher accepts only its exact typed fields; generic argv and override fields are forbidden."
832
+ }
833
+ const operation = spec.operation
834
+ if (operation._tag !== "PublishOperation" || operation.provider !== "npm" || operation.purpose !== "publish") {
835
+ return "The certified npm publisher accepts only an npm publish operation."
836
+ }
837
+ if (typeof spec.packageName !== "string" || spec.packageName.length === 0 ||
838
+ typeof spec.version !== "string" || Semver.valid(spec.version) !== spec.version) {
839
+ return "The certified npm publisher requires one nonempty package name and canonical semantic version."
840
+ }
841
+ if (operation.subject !== `npm:${spec.packageName}@${spec.version}`) {
842
+ return "The certified npm publisher package and version do not match the authorized operation subject."
843
+ }
844
+ try {
845
+ if (canonicalizeRegistryUrl(spec.registryUrl) !== spec.registryUrl ||
846
+ operation.audience.toString() !== spec.registryUrl.toString()) {
847
+ return "The certified npm publisher registry does not match the authorized canonical audience."
848
+ }
849
+ } catch {
850
+ return "The certified npm publisher requires a canonical registry and npm distribution tag."
851
+ }
852
+ if (Semver.validRange(spec.distTag) !== null || spec.distTag.trim() !== spec.distTag ||
853
+ encodeURIComponent(spec.distTag) !== spec.distTag) {
854
+ return "The certified npm publisher requires a canonical registry and npm distribution tag."
855
+ }
856
+ if ((spec.access !== "public" && spec.access !== "restricted") ||
857
+ (spec.provenance !== "required" && spec.provenance !== "automatic" && spec.provenance !== "disabled") ||
858
+ (spec._tag === "NpmPublisherSpec" && spec.provenance === "automatic") ||
859
+ (spec.access === "restricted" && !spec.packageName.startsWith("@")) ||
860
+ (Semver.prerelease(spec.version) !== null && spec.distTag === "latest")) {
861
+ return "The certified npm publisher access, provenance, or dist-tag policy is not an allowed canonical value."
862
+ }
863
+ try {
864
+ const fileName = basename(spec.tarballPath)
865
+ if (!isAbsolute(spec.cwd) || resolve(spec.cwd) !== spec.cwd ||
866
+ !/^[a-f0-9]{64}$/u.test(fileName) ||
867
+ resolve(spec.cwd, "blobs", fileName) !== spec.tarballPath) {
868
+ return "The certified npm publisher tarball must be the canonical prepared-store blob path."
869
+ }
870
+ } catch {
871
+ return "The certified npm publisher tarball must be the canonical prepared-store blob path."
872
+ }
873
+ return undefined
874
+ }
875
+
876
+ const validateCertifiedNpmSpec = Effect.fn("CertifiedPublisherSpawn.validateCertifiedNpmSpec")(
877
+ function*(spec: CertifiedPublisherSpec) {
878
+ const issue = certifiedNpmSpecIssue(spec)
879
+ if (issue !== undefined) {
880
+ return yield* new CredentialStrategyUnsupported({
881
+ subject: spec.operation.subject,
882
+ provider: spec.operation.provider,
883
+ strategy: spec._tag === "WorkloadPublisherSpec" ? "trusted-publishing" : "token",
884
+ reason: issue
885
+ })
886
+ }
887
+ }
888
+ )
889
+
890
+ /** Snapshot caller data once so accessors or concurrent mutation cannot change the admitted command after validation. */
891
+ const snapshotCertifiedNpmSpec = (spec: CertifiedPublisherSpec): CertifiedPublisherSpec => {
892
+ const operation = Object.freeze({
893
+ ...spec.operation,
894
+ decision: Object.freeze({ ...spec.operation.decision })
895
+ }) as NpmPublishOperation
896
+ return Object.freeze({ ...spec, operation }) as CertifiedPublisherSpec
897
+ }
898
+
899
+ const publisherEnvironment = Effect.fn("CertifiedPublisherSpawn.environment")(function*(
900
+ vault: SecretVault,
901
+ trustedWorkloads: TrustedWorkloadVault,
902
+ spec: CertifiedPublisherSpec,
903
+ grant: ScopedSecret | WorkloadIdentity
904
+ ) {
905
+ yield* validateCertifiedNpmSpec(spec)
906
+ if (spec._tag === "NpmPublisherSpec") {
907
+ if (grant._tag !== "ScopedSecret") {
908
+ return yield* new CredentialStrategyUnsupported({
909
+ subject: spec.operation.subject,
910
+ provider: spec.operation.provider,
911
+ strategy: "trusted-publishing",
912
+ reason: "A scoped npm user config requires a scoped-secret grant."
913
+ })
914
+ }
915
+ const metadata = npmUserConfigs.get(spec.userConfig)
916
+ if (metadata === undefined || metadata.subject !== spec.operation.subject || metadata.grant !== grant) {
917
+ return yield* new CredentialUnavailable({
918
+ subject: spec.operation.subject,
919
+ provider: spec.operation.provider,
920
+ purpose: spec.operation.purpose,
921
+ reason: "The npm user config is not active for this exact operation and grant."
922
+ })
923
+ }
924
+ const path = yield* optionalPath
925
+ return {
926
+ ...closedBaseEnvironment(path),
927
+ HOME: metadata.directory,
928
+ NPM_CONFIG_USERCONFIG: metadata.path,
929
+ NPM_CONFIG_GLOBALCONFIG: metadata.globalConfigPath,
930
+ NPM_CONFIG_IGNORE_SCRIPTS: "true"
931
+ }
932
+ }
933
+ if (grant._tag !== "WorkloadIdentity") {
934
+ return yield* new CredentialStrategyUnsupported({
935
+ subject: spec.operation.subject,
936
+ provider: spec.operation.provider,
937
+ strategy: "token",
938
+ reason: "The workload publisher requires a workload-identity grant."
939
+ })
940
+ }
941
+ yield* validateTrustedNpmOperation(spec.operation, grant)
942
+ return yield* workloadEnvironment(trustedWorkloads, grant)
943
+ })
944
+
945
+ const redactPublisherOutput = (
946
+ value: string,
947
+ env: Readonly<Record<string, string>>,
948
+ spec: CertifiedPublisherSpec
949
+ ): string => {
950
+ const metadata = spec._tag === "NpmPublisherSpec" ? npmUserConfigs.get(spec.userConfig) : undefined
951
+ const known = metadata === undefined
952
+ ? env
953
+ : { ...env, PUBLISH_CREDENTIAL: Redacted.value(metadata.token) }
954
+ return redactOutput(value, known)
955
+ }
956
+
957
+ interface NpmPublishTransport {
958
+ readonly directory: string
959
+ readonly path: string
960
+ }
961
+
962
+ /**
963
+ * npm classifies a suffixless local path as a package directory. Prepared
964
+ * artifacts deliberately use digest-only blob names, so expose the already
965
+ * admitted bytes through a private `.tgz` transport name only while npm runs.
966
+ */
967
+ const makeNpmPublishTransport = (
968
+ sourcePath: string,
969
+ temporaryRoot?: string
970
+ ) => {
971
+ const root = temporaryRoot ?? tmpdir()
972
+ const acquired = Effect.tryPromise({
973
+ try: async () => {
974
+ await mkdir(root, { recursive: true, mode: 0o700 })
975
+ const directory = await mkdtemp(join(root, "ts-release-npm-tarball-"))
976
+ try {
977
+ const path = join(directory, "package.tgz")
978
+ await copyFile(sourcePath, path)
979
+ await chmod(path, 0o600)
980
+ return { directory, path }
981
+ } catch (cause) {
982
+ await rm(directory, { recursive: true, force: true })
983
+ throw cause
984
+ }
985
+ },
986
+ catch: () => platformError(
987
+ "resource",
988
+ "before-dispatch",
989
+ "Unable to materialize the verified npm tarball transport path."
990
+ )
991
+ })
992
+ return Effect.acquireRelease(acquired, ({ directory }) => Effect.tryPromise({
993
+ try: () => rm(directory, { recursive: true, force: true }),
994
+ catch: () => undefined
995
+ }).pipe(Effect.catch(() => Effect.void)))
996
+ }
997
+
998
+ const makeCertifiedPublisherSpawn = (
999
+ spawner: ChildProcessSpawner["Service"],
1000
+ vault: SecretVault,
1001
+ trustedWorkloads: TrustedWorkloadVault,
1002
+ temporaryRoot?: string
1003
+ ): CertifiedPublisherSpawnShape => {
1004
+ const runClosed = Effect.fn("CertifiedPublisherSpawn.runClosed")(function*(
1005
+ argv: readonly [string, ...Array<string>]
1006
+ ) {
1007
+ const path = yield* optionalPath
1008
+ const command = ChildProcess.make(argv[0], [...argv.slice(1)], {
1009
+ env: closedBaseEnvironment(path),
1010
+ extendEnv: false,
1011
+ stdin: "ignore",
1012
+ stdout: "pipe",
1013
+ stderr: "pipe"
1014
+ })
1015
+ return yield* Effect.scoped(Effect.gen(function*() {
1016
+ const spawned = yield* Effect.exit(spawner.spawn(command))
1017
+ if (Exit.isFailure(spawned)) {
1018
+ return yield* platformError("spawn", "before-dispatch", "Trusted npm preflight could not start its version probe.")
1019
+ }
1020
+ const output = yield* Effect.exit(Effect.all({
1021
+ stdout: collect(spawned.value.stdout),
1022
+ stderr: collect(spawned.value.stderr),
1023
+ exitCode: spawned.value.exitCode
1024
+ }, { concurrency: "unbounded" }))
1025
+ if (Exit.isFailure(output)) {
1026
+ return yield* platformError("spawn", "before-dispatch", "Trusted npm preflight could not observe its version probe.")
1027
+ }
1028
+ if (Number(output.value.exitCode) !== 0) {
1029
+ return yield* platformError("spawn", "before-dispatch", "Trusted npm preflight version probe was rejected.")
1030
+ }
1031
+ return output.value.stdout
1032
+ }))
1033
+ })
1034
+
1035
+ return {
1036
+ preflightTrustedNpm: Effect.fn("CertifiedPublisherSpawn.preflightTrustedNpm")(function*(operation, grant) {
1037
+ yield* validateGrantForOperation(operation, grant)
1038
+ if (grant._tag !== "WorkloadIdentity") {
1039
+ return yield* new CredentialStrategyUnsupported({
1040
+ subject: operation.subject,
1041
+ provider: operation.provider,
1042
+ strategy: "token",
1043
+ reason: "Trusted npm preflight requires a workload-identity grant."
1044
+ })
1045
+ }
1046
+ yield* validateTrustedNpmOperation(operation, grant)
1047
+ // Validate the exact host-held OIDC names before any provider authority is dispatched.
1048
+ yield* workloadEnvironment(trustedWorkloads, grant)
1049
+ const node = parseVersion(yield* runClosed(["node", "--version"]))
1050
+ const npm = parseVersion(yield* runClosed(["npm", "--version"]))
1051
+ if (node === undefined || !versionAtLeast(node, minimumTrustedNode) ||
1052
+ npm === undefined || !versionAtLeast(npm, minimumTrustedNpm)) {
1053
+ return yield* platformError(
1054
+ "spawn",
1055
+ "before-dispatch",
1056
+ "Trusted npm publishing requires Node >=22.14.0 and npm >=11.5.1."
1057
+ )
1058
+ }
1059
+ }),
1060
+ spawn: Effect.fn("CertifiedPublisherSpawn.spawn")(function*(inputSpec, grant) {
1061
+ yield* validateGrantForOperation(inputSpec.operation, grant)
1062
+ yield* validateCertifiedNpmSpec(inputSpec)
1063
+ const spec = snapshotCertifiedNpmSpec(inputSpec)
1064
+ const env = yield* publisherEnvironment(vault, trustedWorkloads, spec, grant)
1065
+ return yield* Effect.scoped(Effect.gen(function*() {
1066
+ const transport = yield* makeNpmPublishTransport(spec.tarballPath, temporaryRoot)
1067
+ const argv = npmPublishArgv({ ...spec, tarballPath: transport.path })
1068
+ const command = ChildProcess.make(argv[0], [...argv.slice(1)], {
1069
+ cwd: spec.cwd,
1070
+ env,
1071
+ extendEnv: false,
1072
+ stdin: "ignore",
1073
+ stdout: "pipe",
1074
+ stderr: "pipe"
1075
+ })
1076
+ const spawned = yield* Effect.exit(spawner.spawn(command))
1077
+ if (Exit.isFailure(spawned)) {
1078
+ return {
1079
+ _tag: "RejectedBeforeStart",
1080
+ commitment: "before-dispatch",
1081
+ reason: "The certified publisher process did not return a started handle."
1082
+ } satisfies CertifiedPublisherResult
1083
+ }
1084
+ const output = yield* Effect.exit(Effect.all({
1085
+ stdout: collect(spawned.value.stdout),
1086
+ stderr: collect(spawned.value.stderr),
1087
+ exitCode: spawned.value.exitCode
1088
+ }, { concurrency: "unbounded" }))
1089
+ if (Exit.isFailure(output)) {
1090
+ return {
1091
+ _tag: "PublisherOutcomeUnknown",
1092
+ commitment: "unknown",
1093
+ reason: "The certified publisher started, but its final outcome could not be observed."
1094
+ } satisfies CertifiedPublisherResult
1095
+ }
1096
+ return {
1097
+ _tag: "PublisherExited",
1098
+ commitment: "started",
1099
+ exitCode: Number(output.value.exitCode),
1100
+ stdout: redactPublisherOutput(output.value.stdout, env, spec),
1101
+ stderr: redactPublisherOutput(output.value.stderr, env, spec)
1102
+ } satisfies CertifiedPublisherResult
1103
+ }))
1104
+ })
1105
+ }
1106
+ }
1107
+
1108
+ export interface EnvironmentCredentialPlatform {
1109
+ readonly credentialProvider: CredentialProviderShape
1110
+ readonly httpAuthorizer: HttpAuthorizerShape
1111
+ readonly authorizedMutationHttp: AuthorizedMutationHttpShape
1112
+ readonly npmUserConfigResource: NpmUserConfigResourceShape
1113
+ readonly certifiedPublisherSpawn: CertifiedPublisherSpawnShape
1114
+ }
1115
+
1116
+ export interface EnvironmentCredentialPlatformOptions {
1117
+ readonly temporaryRoot?: string
1118
+ }
1119
+
1120
+ /** All host sinks share one private vault; no secret is returned from it. */
1121
+ export const makeEnvironmentCredentialPlatform = (
1122
+ http: PublicationHttp,
1123
+ spawner: ChildProcessSpawner["Service"],
1124
+ options: EnvironmentCredentialPlatformOptions = {}
1125
+ ): EnvironmentCredentialPlatform => {
1126
+ const vault: SecretVault = new Map()
1127
+ const trustedWorkloads: TrustedWorkloadVault = new WeakMap()
1128
+ const observationGrants: ObservationGrantRegistry = new WeakMap()
1129
+ return {
1130
+ credentialProvider: makeProvider(vault, trustedWorkloads, observationGrants),
1131
+ httpAuthorizer: makeHttpAuthorizer(http, vault, observationGrants),
1132
+ authorizedMutationHttp: makeAuthorizedMutationHttp(http, vault),
1133
+ npmUserConfigResource: makeNpmUserConfigResource(vault, options.temporaryRoot),
1134
+ certifiedPublisherSpawn: makeCertifiedPublisherSpawn(spawner, vault, trustedWorkloads, options.temporaryRoot)
1135
+ }
1136
+ }
1137
+
1138
+ export const makeEnvironmentCredentialPlatformLayer = (
1139
+ http: PublicationHttp,
1140
+ spawner: ChildProcessSpawner["Service"],
1141
+ options: EnvironmentCredentialPlatformOptions = {}
1142
+ ): Layer.Layer<CredentialPlatformServices> => {
1143
+ const platform = makeEnvironmentCredentialPlatform(http, spawner, options)
1144
+ return Layer.mergeAll(
1145
+ Layer.succeed(CredentialProvider)(platform.credentialProvider),
1146
+ Layer.succeed(HttpAuthorizer)(platform.httpAuthorizer),
1147
+ Layer.succeed(AuthorizedMutationHttp)(platform.authorizedMutationHttp),
1148
+ Layer.succeed(NpmUserConfigResource)(platform.npmUserConfigResource),
1149
+ Layer.succeed(CertifiedPublisherSpawn)(platform.certifiedPublisherSpawn)
1150
+ )
1151
+ }