@workerstack/repo 0.0.0-stage → 0.1.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 (438) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/LICENSE +21 -0
  3. package/README.md +0 -3
  4. package/agents/global-settings/antigravity.json +3 -0
  5. package/agents/global-settings/claude.json +6 -0
  6. package/agents/global-settings/codex.toml +2 -0
  7. package/agents/global-settings/cursor.json +3 -0
  8. package/agents/global-settings/grok.toml +36 -0
  9. package/agents/global-settings/manifest.json +36 -0
  10. package/agents/grok/hooks/orchestrate-parent.json +60 -0
  11. package/agents/grok/hooks/rtk.json +16 -0
  12. package/agents/grok/hooks/rulesync.json +3 -0
  13. package/agents/grok/roles/scout.toml +5 -0
  14. package/agents/hooks.jsonc +67 -0
  15. package/agents/mcp-policy.jsonc +16 -0
  16. package/agents/rules/overview.md +21 -0
  17. package/agents/settings/claude.json +5 -0
  18. package/agents/skills/ask/SKILL.md +13 -0
  19. package/agents/skills/auto/SKILL.md +13 -0
  20. package/agents/skills/away/SKILL.md +19 -0
  21. package/agents/skills/blast-radius/SKILL.md +54 -0
  22. package/agents/skills/bro/SKILL.md +9 -0
  23. package/agents/skills/browser/SKILL.md +16 -0
  24. package/agents/skills/clasp/SKILL.md +11 -0
  25. package/agents/skills/commit/SKILL.md +35 -0
  26. package/agents/skills/commit-and-push/SKILL.md +9 -0
  27. package/agents/skills/commit-push/SKILL.md +59 -0
  28. package/agents/skills/commit-push-deploy/SKILL.md +74 -0
  29. package/agents/skills/commit-push-release/SKILL.md +75 -0
  30. package/agents/skills/correct/SKILL.md +39 -0
  31. package/agents/skills/do/SKILL.md +21 -0
  32. package/agents/skills/explain/SKILL.md +13 -0
  33. package/agents/skills/google-workspace/SKILL.md +12 -0
  34. package/agents/skills/handoff/SKILL.md +81 -0
  35. package/agents/skills/implement-plan/SKILL.md +29 -0
  36. package/agents/skills/implement-roadmap/SKILL.md +27 -0
  37. package/agents/skills/jev/SKILL.md +17 -0
  38. package/agents/skills/listen/SKILL.md +15 -0
  39. package/agents/skills/pickup/SKILL.md +58 -0
  40. package/agents/skills/principle-attack-the-premise/SKILL.md +24 -0
  41. package/agents/skills/principle-boundary-discipline/SKILL.md +38 -0
  42. package/agents/skills/principle-build-the-lever/SKILL.md +28 -0
  43. package/agents/skills/principle-encode-lessons-in-structure/SKILL.md +15 -0
  44. package/agents/skills/principle-exhaust-the-design-space/SKILL.md +12 -0
  45. package/agents/skills/principle-experience-first/SKILL.md +22 -0
  46. package/agents/skills/principle-explain-the-number/SKILL.md +21 -0
  47. package/agents/skills/principle-fix-root-causes/SKILL.md +24 -0
  48. package/agents/skills/principle-foundational-thinking/SKILL.md +25 -0
  49. package/agents/skills/principle-guard-the-context-window/SKILL.md +16 -0
  50. package/agents/skills/principle-laziness-protocol/SKILL.md +13 -0
  51. package/agents/skills/principle-make-operations-idempotent/SKILL.md +25 -0
  52. package/agents/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md +18 -0
  53. package/agents/skills/principle-minimize-reader-load/SKILL.md +22 -0
  54. package/agents/skills/principle-model-the-domain/SKILL.md +26 -0
  55. package/agents/skills/principle-never-block-on-the-human/SKILL.md +17 -0
  56. package/agents/skills/principle-outcome-oriented-execution/SKILL.md +25 -0
  57. package/agents/skills/principle-prove-it-works/SKILL.md +25 -0
  58. package/agents/skills/principle-redesign-from-first-principles/SKILL.md +18 -0
  59. package/agents/skills/principle-separate-before-serializing-shared-state/SKILL.md +20 -0
  60. package/agents/skills/principle-sequence-verifiable-units/SKILL.md +22 -0
  61. package/agents/skills/principle-subtract-before-you-add/SKILL.md +21 -0
  62. package/agents/skills/principle-test-behavior-not-implementation/SKILL.md +24 -0
  63. package/agents/skills/principle-type-system-discipline/SKILL.md +17 -0
  64. package/agents/skills/review-plan/SKILL.md +28 -0
  65. package/agents/skills/review-roadmap/SKILL.md +28 -0
  66. package/agents/skills/show-me/SKILL.md +21 -0
  67. package/agents/skills/speak/SKILL.md +15 -0
  68. package/agents/skills/su/SKILL.md +11 -0
  69. package/agents/skills/teach/SKILL.md +25 -0
  70. package/agents/skills/tmp/SKILL.md +54 -0
  71. package/agents/skills/typescript-best-practices/SKILL.md +32 -0
  72. package/agents/skills/typescript-best-practices/references/patterns.md +328 -0
  73. package/agents/skills/unslop/SKILL.md +62 -0
  74. package/agents/skills/write-plan/SKILL.md +33 -0
  75. package/agents/skills/write-roadmap/SKILL.md +66 -0
  76. package/agents/subagents/implementer-deep.md +57 -0
  77. package/agents/subagents/implementer-fast.md +57 -0
  78. package/agents/subagents/orchestrator.md +43 -0
  79. package/agents/subagents/researcher.md +47 -0
  80. package/agents/subagents/scout.md +44 -0
  81. package/bin/repo.ts +4 -0
  82. package/cli/actionlint-default.test.ts +38 -0
  83. package/cli/actionlint-download.test.ts +162 -0
  84. package/cli/actionlint-real.test.ts +33 -0
  85. package/cli/actionlint.constants.ts +43 -0
  86. package/cli/actionlint.test.ts +108 -0
  87. package/cli/actionlint.ts +178 -0
  88. package/cli/bin.test.ts +54 -0
  89. package/cli/bin.ts +40 -0
  90. package/cli/check-changelog.test.ts +40 -0
  91. package/cli/check-changelog.ts +12 -0
  92. package/cli/check-comments.test.ts +157 -0
  93. package/cli/check-comments.ts +123 -0
  94. package/cli/check-env-modules.test.ts +85 -0
  95. package/cli/check-env-modules.ts +56 -0
  96. package/cli/check-magic.test.ts +139 -0
  97. package/cli/check-magic.ts +120 -0
  98. package/cli/check-manifests.constants.ts +4 -0
  99. package/cli/check-manifests.test.ts +210 -0
  100. package/cli/check-manifests.ts +71 -0
  101. package/cli/check-nightly.test.ts +103 -0
  102. package/cli/check-nightly.ts +37 -0
  103. package/cli/check-ratchets-missing.test.ts +46 -0
  104. package/cli/check-ratchets-mutation.test.ts +49 -0
  105. package/cli/check-ratchets.constants.ts +20 -0
  106. package/cli/check-ratchets.test.ts +190 -0
  107. package/cli/check-ratchets.ts +193 -0
  108. package/cli/check-rejections.test.ts +220 -0
  109. package/cli/check-rejections.ts +153 -0
  110. package/cli/check-reports.ts +37 -0
  111. package/cli/check-tsconfig.test.ts +507 -0
  112. package/cli/check-tsconfig.ts +205 -0
  113. package/cli/commands.test.ts +114 -0
  114. package/cli/commands.ts +218 -0
  115. package/cli/commit-changelog.ts +27 -0
  116. package/cli/commit-msg-changelog.test.ts +93 -0
  117. package/cli/commit-msg-range.test.ts +126 -0
  118. package/cli/commit-msg-red-proof.test.ts +147 -0
  119. package/cli/commit-msg-repo.test.ts +153 -0
  120. package/cli/commit-msg.test.ts +101 -0
  121. package/cli/commit-msg.ts +194 -0
  122. package/cli/commit-range.constants.ts +17 -0
  123. package/cli/commit-range.ts +57 -0
  124. package/cli/config-knip.test.ts +70 -0
  125. package/cli/config-nightly.test.ts +84 -0
  126. package/cli/config-tdd.test.ts +46 -0
  127. package/cli/config.test.ts +576 -0
  128. package/cli/config.ts +261 -0
  129. package/cli/conventional.ts +9 -0
  130. package/cli/coverage-config.ts +62 -0
  131. package/cli/coverage.constants.ts +6 -0
  132. package/cli/coverage.test.ts +199 -0
  133. package/cli/coverage.ts +127 -0
  134. package/cli/env.constants.ts +10 -0
  135. package/cli/env.test.ts +56 -0
  136. package/cli/env.ts +72 -0
  137. package/cli/fixtures/base-config.ts +13 -0
  138. package/cli/fixtures/gate-world.ts +48 -0
  139. package/cli/fixtures/knip-run.ts +38 -0
  140. package/cli/fixtures/mutate-fakes.ts +137 -0
  141. package/cli/fixtures/planted-repo.ts +50 -0
  142. package/cli/fixtures/publish-world.ts +71 -0
  143. package/cli/fixtures/red-proof-repo.ts +72 -0
  144. package/cli/fixtures/release-context.ts +15 -0
  145. package/cli/fixtures/release-world.ts +111 -0
  146. package/cli/fixtures/stub-bin.ts +40 -0
  147. package/cli/guard-diagnostics.test.ts +157 -0
  148. package/cli/guard-diagnostics.ts +195 -0
  149. package/cli/init.test.ts +487 -0
  150. package/cli/init.ts +275 -0
  151. package/cli/knip-check-ignores.test.ts +106 -0
  152. package/cli/knip-check-real.test.ts +65 -0
  153. package/cli/knip-check.constants.ts +46 -0
  154. package/cli/knip-check.test.ts +114 -0
  155. package/cli/knip-check.ts +169 -0
  156. package/cli/knip-schema.ts +67 -0
  157. package/cli/known-failures.constants.ts +22 -0
  158. package/cli/known-failures.test.ts +138 -0
  159. package/cli/known-failures.ts +131 -0
  160. package/cli/lint/as-needs-reason.ts +41 -0
  161. package/cli/lint/boundaries.ts +73 -0
  162. package/cli/lint/class-file.ts +39 -0
  163. package/cli/lint/config-file.ts +32 -0
  164. package/cli/lint/entry-file.ts +54 -0
  165. package/cli/lint/env-in-config.ts +66 -0
  166. package/cli/lint/filename-matches-export.ts +63 -0
  167. package/cli/lint/index-file.ts +22 -0
  168. package/cli/lint/index.ts +47 -0
  169. package/cli/lint/is-reason.ts +6 -0
  170. package/cli/lint/manifest.ts +46 -0
  171. package/cli/lint/names.ts +59 -0
  172. package/cli/lint/no-commented-out-code.ts +105 -0
  173. package/cli/lint/no-mock-of-subject.ts +64 -0
  174. package/cli/lint/no-module-side-effects.ts +94 -0
  175. package/cli/lint/no-module-state.ts +58 -0
  176. package/cli/lint/no-side-effect-import.ts +17 -0
  177. package/cli/lint/no-v8-ignore.ts +18 -0
  178. package/cli/lint/script-rules.ts +9 -0
  179. package/cli/lint/skip-on-platform.constants.ts +8 -0
  180. package/cli/lint/skip-on-platform.ts +118 -0
  181. package/cli/lint/todo-needs-id.ts +63 -0
  182. package/cli/lint/types-file.ts +28 -0
  183. package/cli/lint-schema.ts +32 -0
  184. package/cli/magic/fixtures/magic-repo.ts +64 -0
  185. package/cli/magic/magic-hit.ts +2 -0
  186. package/cli/magic/magic-nodes.ts +44 -0
  187. package/cli/magic/magic-numbers.test.ts +99 -0
  188. package/cli/magic/magic-numbers.ts +117 -0
  189. package/cli/magic/magic-strings.test.ts +38 -0
  190. package/cli/magic/magic-strings.ts +81 -0
  191. package/cli/main.test.ts +853 -0
  192. package/cli/main.ts +292 -0
  193. package/cli/mutate-args.ts +79 -0
  194. package/cli/mutate-check.test.ts +147 -0
  195. package/cli/mutate-deps.ts +42 -0
  196. package/cli/mutate-edges.test.ts +150 -0
  197. package/cli/mutate-files.test.ts +100 -0
  198. package/cli/mutate-files.ts +31 -0
  199. package/cli/mutate-floors.test.ts +91 -0
  200. package/cli/mutate-floors.ts +104 -0
  201. package/cli/mutate-real-deps.ts +57 -0
  202. package/cli/mutate-real.test.ts +96 -0
  203. package/cli/mutate-report.ts +96 -0
  204. package/cli/mutate-run.ts +128 -0
  205. package/cli/mutate-shards.ts +64 -0
  206. package/cli/mutate.constants.ts +67 -0
  207. package/cli/mutate.test.ts +182 -0
  208. package/cli/mutate.ts +64 -0
  209. package/cli/nested-vitest-flags.ts +8 -0
  210. package/cli/nightly-config.constants.ts +14 -0
  211. package/cli/nightly-config.ts +62 -0
  212. package/cli/prompt.test.ts +202 -0
  213. package/cli/prompt.ts +1 -0
  214. package/cli/red-proof-edges.test.ts +78 -0
  215. package/cli/red-proof-failures.test.ts +82 -0
  216. package/cli/red-proof-tree.test.ts +157 -0
  217. package/cli/red-proof-verdict.test.ts +44 -0
  218. package/cli/red-proof.constants.ts +14 -0
  219. package/cli/red-proof.ts +202 -0
  220. package/cli/release/changelog-parse.ts +83 -0
  221. package/cli/release/changelog.test.ts +141 -0
  222. package/cli/release/changelog.ts +106 -0
  223. package/cli/release/gate-refusals.test.ts +85 -0
  224. package/cli/release/gate.test.ts +63 -0
  225. package/cli/release/gate.ts +70 -0
  226. package/cli/release/git.ts +66 -0
  227. package/cli/release/github.test.ts +128 -0
  228. package/cli/release/github.ts +46 -0
  229. package/cli/release/list-dir.test.ts +11 -0
  230. package/cli/release/list-dir.ts +6 -0
  231. package/cli/release/main.test.ts +48 -0
  232. package/cli/release/main.ts +51 -0
  233. package/cli/release/manifest.test.ts +24 -0
  234. package/cli/release/manifest.ts +34 -0
  235. package/cli/release/pack.test.ts +107 -0
  236. package/cli/release/pack.ts +76 -0
  237. package/cli/release/plan.ts +127 -0
  238. package/cli/release/publish-run.test.ts +98 -0
  239. package/cli/release/publish.test.ts +127 -0
  240. package/cli/release/publish.ts +125 -0
  241. package/cli/release/refuse.ts +5 -0
  242. package/cli/release/release-messages.constants.ts +96 -0
  243. package/cli/release/release-refusals.test.ts +149 -0
  244. package/cli/release/release.constants.ts +128 -0
  245. package/cli/release/release.test.ts +199 -0
  246. package/cli/release/release.ts +145 -0
  247. package/cli/release/version.test.ts +118 -0
  248. package/cli/release/version.ts +69 -0
  249. package/cli/release/wait.test.ts +101 -0
  250. package/cli/release/wait.ts +95 -0
  251. package/cli/script-diagnostics.test.ts +76 -0
  252. package/cli/script-diagnostics.ts +62 -0
  253. package/cli/scripts/board.ts +519 -0
  254. package/cli/scripts/browser.ts +994 -0
  255. package/cli/scripts/change-set.ts +105 -0
  256. package/cli/scripts/check-globals.ts +199 -0
  257. package/cli/scripts/ci-skips.constants.ts +17 -0
  258. package/cli/scripts/ci-skips.ts +118 -0
  259. package/cli/scripts/ci-wait-run.ts +28 -0
  260. package/cli/scripts/ci-wait.constants.ts +44 -0
  261. package/cli/scripts/ci-wait.ts +175 -0
  262. package/cli/scripts/ci.constants.ts +15 -0
  263. package/cli/scripts/ci.ts +208 -0
  264. package/cli/scripts/clean.ts +363 -0
  265. package/cli/scripts/comments-step.ts +15 -0
  266. package/cli/scripts/doctor.ts +828 -0
  267. package/cli/scripts/emit-agent-shell-tools.ts +91 -0
  268. package/cli/scripts/emit-mcp-policy.ts +402 -0
  269. package/cli/scripts/folder-map.ts +353 -0
  270. package/cli/scripts/gate-fat-tools.ts +1095 -0
  271. package/cli/scripts/gitignore-guard.ts +338 -0
  272. package/cli/scripts/google-workspace.ts +779 -0
  273. package/cli/scripts/hook-command.ts +72 -0
  274. package/cli/scripts/hook-input.ts +178 -0
  275. package/cli/scripts/hook-io.constants.ts +5 -0
  276. package/cli/scripts/hook-io.ts +46 -0
  277. package/cli/scripts/hooks-union.ts +469 -0
  278. package/cli/scripts/interactive.ts +30 -0
  279. package/cli/scripts/jev.ts +420 -0
  280. package/cli/scripts/job.ts +934 -0
  281. package/cli/scripts/jsonc.ts +9 -0
  282. package/cli/scripts/lessons.ts +564 -0
  283. package/cli/scripts/lint-baseline.ts +233 -0
  284. package/cli/scripts/lint-main.ts +24 -0
  285. package/cli/scripts/lint-skips.ts +139 -0
  286. package/cli/scripts/lint.constants.ts +6 -0
  287. package/cli/scripts/lint.ts +142 -0
  288. package/cli/scripts/listen.ts +962 -0
  289. package/cli/scripts/managed-text.ts +47 -0
  290. package/cli/scripts/orchestrate-parent.ts +1155 -0
  291. package/cli/scripts/parse-need.ts +81 -0
  292. package/cli/scripts/persist-handoff.ts +228 -0
  293. package/cli/scripts/persist-implementation.ts +276 -0
  294. package/cli/scripts/persist-plan.ts +888 -0
  295. package/cli/scripts/persist-roadmap.ts +478 -0
  296. package/cli/scripts/persist-tmp.ts +177 -0
  297. package/cli/scripts/persist.ts +54 -0
  298. package/cli/scripts/phase-files.ts +121 -0
  299. package/cli/scripts/prompt.ts +38 -0
  300. package/cli/scripts/purge-global-mcps.ts +313 -0
  301. package/cli/scripts/rejections-step.ts +37 -0
  302. package/cli/scripts/rtk-grok.ts +102 -0
  303. package/cli/scripts/scrub-spawn-prompt.ts +103 -0
  304. package/cli/scripts/scrub-text-lib.ts +58 -0
  305. package/cli/scripts/session-map.ts +69 -0
  306. package/cli/scripts/setup.ts +862 -0
  307. package/cli/scripts/skill-conflicts.ts +82 -0
  308. package/cli/scripts/speak.ts +370 -0
  309. package/cli/scripts/strategy.ts +168 -0
  310. package/cli/scripts/sync-global-settings.ts +316 -0
  311. package/cli/scripts/turn-steps.ts +249 -0
  312. package/cli/scripts/turn.ts +157 -0
  313. package/cli/scripts/typecheck-step.ts +43 -0
  314. package/cli/scripts/vscode-tasks.ts +175 -0
  315. package/cli/scripts/workspace-root.ts +160 -0
  316. package/cli/scripts/worktree.ts +806 -0
  317. package/cli/spawn.test.ts +34 -0
  318. package/cli/spawn.ts +27 -0
  319. package/cli/staged-change.constants.ts +11 -0
  320. package/cli/staged-change.ts +79 -0
  321. package/cli/stryker-preload.test.ts +50 -0
  322. package/cli/stryker-preload.ts +9 -0
  323. package/cli/sync/agents/frontmatter.test.ts +197 -0
  324. package/cli/sync/agents/frontmatter.ts +140 -0
  325. package/cli/sync/agents/hooks.test.ts +517 -0
  326. package/cli/sync/agents/hooks.ts +401 -0
  327. package/cli/sync/agents/rules.test.ts +229 -0
  328. package/cli/sync/agents/rules.ts +84 -0
  329. package/cli/sync/agents/sources.test.ts +375 -0
  330. package/cli/sync/agents/sources.ts +244 -0
  331. package/cli/sync/agents/subagents.test.ts +149 -0
  332. package/cli/sync/agents/subagents.ts +158 -0
  333. package/cli/sync/agents-text.test.ts +31 -0
  334. package/cli/sync/agents-text.ts +36 -0
  335. package/cli/sync/fixtures/copied.README.md +35 -0
  336. package/cli/sync/fixtures/copied.tgz +0 -0
  337. package/cli/sync/fixtures/maps/golden/INDEX.md +22 -0
  338. package/cli/sync/fixtures/maps-ws.tgz +0 -0
  339. package/cli/sync/git-files.test.ts +340 -0
  340. package/cli/sync/git-files.ts +197 -0
  341. package/cli/sync/git-hooks.test.ts +94 -0
  342. package/cli/sync/golden.test.ts +15 -0
  343. package/cli/sync/hooks-mode.test.ts +99 -0
  344. package/cli/sync/hosts.ts +28 -0
  345. package/cli/sync/knip-config.constants.ts +4 -0
  346. package/cli/sync/knip-config.test.ts +147 -0
  347. package/cli/sync/knip-config.ts +108 -0
  348. package/cli/sync/manifest.test.ts +78 -0
  349. package/cli/sync/manifest.ts +60 -0
  350. package/cli/sync/maps.test.ts +304 -0
  351. package/cli/sync/maps.ts +297 -0
  352. package/cli/sync/migrate.test.ts +2000 -0
  353. package/cli/sync/migrate.ts +1104 -0
  354. package/cli/sync/nightly-jobs.test.ts +63 -0
  355. package/cli/sync/nightly.constants.ts +48 -0
  356. package/cli/sync/nightly.test.ts +100 -0
  357. package/cli/sync/nightly.ts +75 -0
  358. package/cli/sync/outputs.test.ts +351 -0
  359. package/cli/sync/outputs.ts +168 -0
  360. package/cli/sync/oxlint-config.test.ts +178 -0
  361. package/cli/sync/oxlint-config.ts +168 -0
  362. package/cli/sync/release-jobs.test.ts +29 -0
  363. package/cli/sync/release-reports.test.ts +31 -0
  364. package/cli/sync/release-workflow.constants.ts +61 -0
  365. package/cli/sync/release-workflow.test.ts +192 -0
  366. package/cli/sync/release-workflow.ts +86 -0
  367. package/cli/sync/release.constants.ts +93 -0
  368. package/cli/sync/release.test.ts +187 -0
  369. package/cli/sync/release.ts +112 -0
  370. package/cli/sync/repo-dir.test.ts +371 -0
  371. package/cli/sync/repo-dir.ts +205 -0
  372. package/cli/sync/standards.constants.ts +8 -0
  373. package/cli/sync/standards.test.ts +206 -0
  374. package/cli/sync/standards.ts +179 -0
  375. package/cli/sync/stock-history.json +2796 -0
  376. package/cli/sync/stock-history.test.ts +66 -0
  377. package/cli/sync/stock-history.ts +84 -0
  378. package/cli/sync/stock.test.ts +47 -0
  379. package/cli/sync/stock.ts +24 -0
  380. package/cli/sync/trees.test.ts +621 -0
  381. package/cli/sync/trees.ts +280 -0
  382. package/cli/sync/validate.test.ts +299 -0
  383. package/cli/sync/validate.ts +402 -0
  384. package/cli/sync/workspace.test.ts +180 -0
  385. package/cli/sync/workspace.ts +138 -0
  386. package/cli/sync/write.test.ts +341 -0
  387. package/cli/sync/write.ts +231 -0
  388. package/cli/sync.test.ts +1495 -0
  389. package/cli/sync.ts +360 -0
  390. package/cli/tdd-scope.test.ts +36 -0
  391. package/cli/tdd-scope.ts +41 -0
  392. package/cli/test-files.ts +16 -0
  393. package/cli/ts-program.ts +69 -0
  394. package/cli/tsconfig.constants.ts +8 -0
  395. package/cli/workerstack-config.types.ts +21 -0
  396. package/dist/cli/env.d.ts +10 -0
  397. package/dist/cli/env.js +64 -0
  398. package/dist/presets/temp-root-env.d.ts +10 -0
  399. package/dist/presets/temp-root-env.js +20 -0
  400. package/dist/presets/temp-root.constants.d.ts +6 -0
  401. package/dist/presets/temp-root.constants.js +6 -0
  402. package/dist/presets/temp-root.d.ts +2 -0
  403. package/dist/presets/temp-root.js +60 -0
  404. package/dist/presets/vitest.constants.d.ts +16 -0
  405. package/dist/presets/vitest.constants.js +16 -0
  406. package/dist/presets/vitest.d.ts +11 -0
  407. package/dist/presets/vitest.js +67 -0
  408. package/dist/presets/workers-plugin.d.ts +10 -0
  409. package/dist/presets/workers-plugin.js +54 -0
  410. package/package.json +141 -4
  411. package/references/INDEX.md +100 -0
  412. package/references/browser.md +66 -0
  413. package/references/cascade.md +15 -0
  414. package/references/clasp.md +21 -0
  415. package/references/cloudflare.md +36 -0
  416. package/references/google-workspace.md +79 -0
  417. package/references/hooks.md +103 -0
  418. package/references/implementation-format.md +119 -0
  419. package/references/jev.md +56 -0
  420. package/references/lessons.md +73 -0
  421. package/references/listen.md +80 -0
  422. package/references/map.md +107 -0
  423. package/references/output.md +47 -0
  424. package/references/plan-format.md +264 -0
  425. package/references/review-loop.md +48 -0
  426. package/references/roadmap-format.md +104 -0
  427. package/references/rtk.md +27 -0
  428. package/references/scope.md +76 -0
  429. package/references/setup.md +42 -0
  430. package/references/speak.md +59 -0
  431. package/tsconfig/base.json +28 -0
  432. package/tsconfig/bun.d.ts +1 -0
  433. package/tsconfig/bun.json +8 -0
  434. package/tsconfig/reset.d.ts +1 -0
  435. package/tsconfig/strict.json +12 -0
  436. package/tsconfig/web.json +7 -0
  437. package/tsconfig/worker.d.ts +1 -0
  438. package/tsconfig/worker.json +8 -0
@@ -0,0 +1,76 @@
1
+ import path from "node:path";
2
+ import { z } from "zod";
3
+ import type { Runner } from "../scripts/ci-wait.ts";
4
+ import { BUN_PACK, PACK_DIR_PREFIX, TAR_MANIFEST, TARBALL_EXTENSION, UNPINNED } from "./release.constants.ts";
5
+ import {
6
+ PACKED_UNREADABLE,
7
+ PACK_ARGS,
8
+ PACK_PREFIX,
9
+ packCount,
10
+ packFailed,
11
+ unpinned,
12
+ } from "./release-messages.constants.ts";
13
+ import { refuse } from "./refuse.ts";
14
+
15
+ /** Where `repo release pack` runs and prints, and the bun, tar and temp directory it works through. */
16
+ export type PackIo = {
17
+ cwd: string;
18
+ stdout: (text: string) => void;
19
+ stderr: (text: string) => void;
20
+ bun: Runner;
21
+ tar: Runner;
22
+ /** The directory the tarball's own directory is made in. */
23
+ tmp: string;
24
+ mkdtemp: (prefix: string) => string;
25
+ /** The names in a directory. */
26
+ list: (dir: string) => string[];
27
+ };
28
+ /** A tarball and the name and version its package.json carries. */
29
+ export type Packed = { tarball: string; name: string; version: string };
30
+
31
+ const pins = () => z.record(z.string(), z.string()).default({});
32
+ const packedSchema = () =>
33
+ z.object({
34
+ name: z.string(),
35
+ version: z.string(),
36
+ dependencies: pins(),
37
+ devDependencies: pins(),
38
+ peerDependencies: pins(),
39
+ optionalDependencies: pins(),
40
+ });
41
+
42
+ /** Packs the package in `io.cwd` and checks its package.json; the tarball and its manifest, or the refusal line. */
43
+ export function packTarball(
44
+ io: Pick<PackIo, "cwd" | "stderr" | "bun" | "tar" | "tmp" | "mkdtemp" | "list">,
45
+ ): Packed | string {
46
+ const dir = io.mkdtemp(path.join(io.tmp, PACK_DIR_PREFIX));
47
+ const packed = io.bun(BUN_PACK(dir), io.cwd);
48
+ if (packed.stdout !== "") io.stderr(packed.stdout);
49
+ if (packed.code !== 0) return packFailed(packed.stderr.trim());
50
+ const tarballs = io.list(dir).filter((file) => file.endsWith(TARBALL_EXTENSION));
51
+ const [only] = tarballs;
52
+ if (only === undefined || tarballs.length > 1) return packCount(tarballs.length, dir);
53
+ const tarball = path.join(dir, only);
54
+ let manifest: z.infer<ReturnType<typeof packedSchema>>;
55
+ try {
56
+ // By name from its own dir: GNU tar reads an absolute `C:\\…` as `host:path` and tries to connect to the host `C`.
57
+ manifest = packedSchema().parse(JSON.parse(io.tar(TAR_MANIFEST(only), dir).stdout));
58
+ } catch {
59
+ return PACKED_UNREADABLE;
60
+ }
61
+ const { name, version, ...fields } = manifest;
62
+ for (const entries of Object.values(fields)) {
63
+ const bad = Object.entries(entries).find(([, spec]) => UNPINNED.test(spec));
64
+ if (bad !== undefined) return unpinned(bad[1], bad[0]);
65
+ }
66
+ return { tarball, name, version };
67
+ }
68
+
69
+ /** `repo release pack`: prints the path of the tarball of this checkout's package, after checking its package.json. */
70
+ export function releasePack(argv: readonly string[], io: PackIo): number {
71
+ if (argv.length > 0) return refuse(io, PACK_PREFIX, PACK_ARGS);
72
+ const got = packTarball(io);
73
+ if (typeof got === "string") return refuse(io, PACK_PREFIX, got);
74
+ io.stdout(`${got.tarball}\n`);
75
+ return 0;
76
+ }
@@ -0,0 +1,127 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { loadConfig } from "../config.ts";
4
+ import { GH_AUTH } from "../scripts/ci-wait.constants.ts";
5
+ import { hasUnreleased } from "./changelog.ts";
6
+ import {
7
+ commitsSince,
8
+ commitWithSubject,
9
+ defaultBranch,
10
+ divergence,
11
+ must,
12
+ read,
13
+ releaseTag,
14
+ type Tools,
15
+ } from "./git.ts";
16
+ import { readManifest, type Manifest } from "./manifest.ts";
17
+ import {
18
+ CHANGELOG_FILE,
19
+ GIT_BRANCH,
20
+ GIT_FETCH,
21
+ GIT_HEAD,
22
+ GIT_STATUS,
23
+ PREFIX_SEPARATOR,
24
+ RELEASE_PACK,
25
+ RELEASE_SUBJECT,
26
+ TAG_OF,
27
+ ZERO_VERSION,
28
+ } from "./release.constants.ts";
29
+ import {
30
+ DIRTY,
31
+ EMPTY_UNRELEASED,
32
+ GH_MISSING,
33
+ NO_MANIFEST,
34
+ PACK_OFF,
35
+ differs,
36
+ isPrivate,
37
+ notMain,
38
+ notSemver,
39
+ nothing,
40
+ stableOnly,
41
+ tagExists,
42
+ } from "./release-messages.constants.ts";
43
+ import { nextVersion, type Mode } from "./version.ts";
44
+
45
+ /** What a release will do: which version, and whether it writes a commit (`bump`: new version; `fold`: into a pending one). */
46
+ export type Plan = {
47
+ name: string;
48
+ from: string;
49
+ version: string;
50
+ tag: string;
51
+ branch: string;
52
+ action: "bump" | "fold" | "none";
53
+ /** Commits on the branch origin does not have yet. */
54
+ ahead: number;
55
+ };
56
+
57
+ const unreleased = (t: Tools): boolean => {
58
+ const file = path.join(t.cwd, CHANGELOG_FILE);
59
+ return fs.existsSync(file) && hasUnreleased(fs.readFileSync(file, "utf8"));
60
+ };
61
+
62
+ /** The refusal for the config and manifest, or the manifest. */
63
+ async function readRepo(t: Tools): Promise<Manifest | string> {
64
+ const manifest = readManifest(t.cwd);
65
+ if (manifest === null) return NO_MANIFEST;
66
+ const loaded = await loadConfig(t.cwd);
67
+ if (!loaded.ok) return loaded.errors.join(PREFIX_SEPARATOR);
68
+ if (!loaded.config.packs.includes(RELEASE_PACK)) return PACK_OFF;
69
+ return manifest.private ? isPrivate(manifest.name) : manifest;
70
+ }
71
+
72
+ /** What the pending-release and new-version steps read: the checkout, its manifest, the last tag and the mode. */
73
+ type Release = { t: Tools; manifest: Manifest; lastTag: string | null; mode: Mode };
74
+
75
+ /** The new version, or the refusal; checks the changelog and the tag it would make. */
76
+ function newVersion({ t, manifest: m, lastTag, mode }: Release): { version: string } | string {
77
+ const first = lastTag === null && m.version === ZERO_VERSION;
78
+ const got = nextVersion({ current: m.version, commits: commitsSince(t, lastTag), mode, first });
79
+ if (!got.ok) {
80
+ const lines = {
81
+ nothing: nothing(lastTag),
82
+ stable: stableOnly(m.name, m.version),
83
+ version: notSemver(m.name, m.version),
84
+ };
85
+ return lines[got.reason];
86
+ }
87
+ if (!unreleased(t)) return EMPTY_UNRELEASED;
88
+ const taken = releaseTag(t, m.name, got.version);
89
+ return taken === null ? { version: got.version } : tagExists(taken);
90
+ }
91
+
92
+ /** Where the checkout stands: the branch to release from, commits not pushed, the pending release commit, the last tag. */
93
+ type State = { branch: string; ahead: number; pending: string | null; lastTag: string | null };
94
+
95
+ /** The state, or the refusal for a dirty tree, another branch, an origin that differs or a gh that is not logged in. */
96
+ function readState(t: Tools, { name, version }: Manifest): State | string {
97
+ if (read(t, GIT_STATUS) !== "") return DIRTY;
98
+ const branch = defaultBranch(t);
99
+ const current = read(t, GIT_BRANCH);
100
+ if (current !== branch) return notMain(branch, current);
101
+ must(t, GIT_FETCH);
102
+ const lastTag = releaseTag(t, name, version);
103
+ const pending = lastTag === null ? commitWithSubject(t, RELEASE_SUBJECT(TAG_OF(name, version))) : null;
104
+ const { ahead, behind } = divergence(t, branch);
105
+ if (behind > 0 || (ahead > 0 && pending === null)) return differs(branch, behind, ahead);
106
+ return t.gh(GH_AUTH.slice(), t.cwd).code === 0 ? { branch, ahead, pending, lastTag } : GH_MISSING;
107
+ }
108
+
109
+ /**
110
+ * The plan for the checkout at `t.cwd`, or the one-line reason it refuses; nothing is written. A version whose release
111
+ * commit is in history without a tag is pending: a rerun finishes it instead of computing a new one.
112
+ */
113
+ export async function makePlan(t: Tools, mode: Mode): Promise<Plan | string> {
114
+ const manifest = await readRepo(t);
115
+ if (typeof manifest === "string") return manifest;
116
+ const state = readState(t, manifest);
117
+ if (typeof state === "string") return state;
118
+ const { name, version: from } = manifest;
119
+ const { branch, ahead } = state;
120
+ if (state.pending !== null) {
121
+ const action = state.pending !== read(t, GIT_HEAD) && unreleased(t) ? "fold" : "none";
122
+ return { name, from, version: from, tag: TAG_OF(name, from), branch, ahead, action };
123
+ }
124
+ const next = newVersion({ t, manifest, lastTag: state.lastTag, mode });
125
+ const tag = typeof next === "string" ? "" : TAG_OF(name, next.version);
126
+ return typeof next === "string" ? next : { name, from, version: next.version, tag, branch, ahead, action: "bump" };
127
+ }
@@ -0,0 +1,98 @@
1
+ import path from "node:path";
2
+ import { afterEach, describe, expect, it } from "vitest";
3
+ import { publish, type Registry } from "../fixtures/publish-world.ts";
4
+ import { removePlanted } from "../fixtures/planted-repo.ts";
5
+ import { gitIn, NAME, world } from "../fixtures/release-world.ts";
6
+
7
+ afterEach(removePlanted);
8
+
9
+ const SPEC = `${NAME}@0.1.0`;
10
+ const newVersion = (notServed = 0, offline = 0): Registry => ({ exists: true, listed: false, notServed, offline });
11
+ const PACK_DIR = path.normalize("/tmp/repo-pack-x1");
12
+ const TGZ = path.join(PACK_DIR, "acme-app-0.1.0.tgz");
13
+
14
+ describe("repo release publish publishes", () => {
15
+ it("a first publish from your machine through npm, under latest", async () => {
16
+ const registry: Registry = { exists: false, listed: false, notServed: 0 };
17
+ const got = await publish(world(), [SPEC], registry, { ci: false });
18
+ expect(got).toMatchObject({
19
+ code: 0,
20
+ out: `repo release publish: packed ${SPEC} · published under latest · tarball served\n`,
21
+ err: "",
22
+ npmCalls: [`publish ${TGZ} --access public --ignore-scripts`],
23
+ bunCalls: [`pm pack --destination ${PACK_DIR} --quiet`],
24
+ });
25
+ });
26
+
27
+ it("a new version of a package already on npm, in CI", async () => {
28
+ const got = await publish(world(), [SPEC], newVersion(), { ci: true });
29
+ expect(got).toMatchObject({ code: 0, npmCalls: [`publish ${TGZ} --access public --ignore-scripts`] });
30
+ });
31
+
32
+ it("a prerelease under next", async () => {
33
+ const pre = `${NAME}@0.2.0-rc.1`;
34
+ const w = world({ "package.json": { name: NAME, version: "0.2.0-rc.1" } }, false);
35
+ gitIn(w.work, "tag", "-a", pre, "-m", "t");
36
+ const got = await publish(w, [pre], newVersion(), { ci: true });
37
+ expect(got.out).toBe(`repo release publish: packed ${pre} · published under next · tarball served\n`);
38
+ expect(got.npmCalls).toEqual([
39
+ `publish ${path.join(PACK_DIR, "acme-app-0.2.0-rc.1.tgz")} --access public --ignore-scripts --tag next`,
40
+ ]);
41
+ });
42
+ });
43
+
44
+ describe("repo release publish waits and reruns", () => {
45
+ it("waits for the tarball after publishing", async () => {
46
+ const got = await publish(world(), [SPEC], newVersion(1));
47
+ expect(got).toMatchObject({ code: 0, sleeps: [10_000] });
48
+ });
49
+
50
+ it("keeps waiting through a network error", async () => {
51
+ const got = await publish(world(), [SPEC], newVersion(0, 2));
52
+ expect(got).toMatchObject({ code: 0, sleeps: [10_000, 10_000] });
53
+ });
54
+
55
+ it("counts an npm failure as published when the version is listed by then", async () => {
56
+ const got = await publish(world(), [SPEC], newVersion(), { npm: { code: 1, lists: true } });
57
+ expect(got).toMatchObject({
58
+ code: 0,
59
+ out: `repo release publish: packed ${SPEC} · published under latest · tarball served\n`,
60
+ });
61
+ });
62
+ });
63
+
64
+ describe("repo release publish refuses", () => {
65
+ it("an npm failure that published nothing", async () => {
66
+ const got = await publish(world(), [SPEC], newVersion(), { npm: { code: 1, lists: false } });
67
+ expect(got).toMatchObject({
68
+ code: 1,
69
+ out: "",
70
+ err: "repo release publish: npm publish exited 1 and npm does not list 0.1.0; its output is above\n",
71
+ });
72
+ });
73
+
74
+ it("a tarball that npm never serves after publishing", async () => {
75
+ const got = await publish(world(), [SPEC], newVersion(Number.MAX_SAFE_INTEGER));
76
+ expect(got).toMatchObject({
77
+ code: 1,
78
+ err: `repo release publish: the tarball of ${SPEC} is not served after 15 minutes; rerun this job\n`,
79
+ });
80
+ });
81
+ });
82
+
83
+ describe("repo release publish refuses a bad pack", () => {
84
+ it("a package that bun cannot pack", async () => {
85
+ const bun = () => ({ code: 1, stdout: "", stderr: "boom\n" });
86
+ const got = await publish(world(), [SPEC], newVersion(), { bun });
87
+ expect(got).toMatchObject({ code: 1, err: "repo release publish: bun pm pack failed: boom\n", npmCalls: [] });
88
+ });
89
+
90
+ it("a tarball that holds another name or version than the tag", async () => {
91
+ const got = await publish(world(), [SPEC], newVersion(), { packed: { name: NAME, version: "0.0.9" } });
92
+ expect(got).toMatchObject({
93
+ code: 1,
94
+ err: `repo release publish: the tarball holds ${NAME}@0.0.9 but the tag is ${SPEC}; nothing was published\n`,
95
+ npmCalls: [],
96
+ });
97
+ });
98
+ });
@@ -0,0 +1,127 @@
1
+ import { afterEach, describe, expect, it } from "vitest";
2
+ import { publish, type Registry } from "../fixtures/publish-world.ts";
3
+ import { removePlanted, tempDir } from "../fixtures/planted-repo.ts";
4
+ import { commit, gitIn, NAME, world } from "../fixtures/release-world.ts";
5
+
6
+ afterEach(removePlanted);
7
+
8
+ const SPEC = `${NAME}@0.1.0`;
9
+ const GONE: Registry = { exists: false, listed: false, notServed: 0 };
10
+ const LISTED = (notServed = 0): Registry => ({ exists: true, listed: true, notServed });
11
+ const TARBALL = "https://registry.npmjs.org/@acme/app/-/app-0.1.0.tgz";
12
+ const TAG_FORMAT = "repo release publish: name the tag as <name>@<version>, e.g. @workerstack/repo@1.0.0\n";
13
+
14
+ describe("repo release publish starts from the tag", () => {
15
+ it("refuses when HEAD is past the tag, without packing or publishing", async () => {
16
+ const w = world();
17
+ commit(w, "chore: later", { "later.txt": "x\n" });
18
+ const got = await publish(w, [SPEC], LISTED());
19
+ expect(got).toMatchObject({
20
+ code: 1,
21
+ out: "",
22
+ err: `repo release publish: HEAD is not ${SPEC}; check out the tag first\n`,
23
+ npmCalls: [],
24
+ bunCalls: [],
25
+ });
26
+ });
27
+
28
+ it("refuses a tag that does not exist", async () => {
29
+ expect((await publish(world({}, false), [SPEC], LISTED())).err).toBe(
30
+ `repo release publish: HEAD is not ${SPEC}; check out the tag first\n`,
31
+ );
32
+ });
33
+
34
+ it("refuses outside a git checkout", async () => {
35
+ const got = await publish({ work: tempDir(), remote: "" }, [SPEC], LISTED());
36
+ expect(got.err).toBe(`repo release publish: HEAD is not ${SPEC}; check out the tag first\n`);
37
+ });
38
+
39
+ it("refuses a missing or malformed argument with the tag format", async () => {
40
+ expect((await publish(world(), [], LISTED())).err).toBe(TAG_FORMAT);
41
+ expect((await publish(world(), ["x"], LISTED())).err).toBe(TAG_FORMAT);
42
+ });
43
+ });
44
+
45
+ describe("repo release publish skips a version npm serves", () => {
46
+ it("says so and runs nothing", async () => {
47
+ const got = await publish(world(), [SPEC], LISTED());
48
+ expect(got).toMatchObject({
49
+ code: 0,
50
+ out: `repo release publish: ${SPEC} is already on npm; nothing to publish\n`,
51
+ err: "",
52
+ npmCalls: [],
53
+ bunCalls: [],
54
+ urls: ["https://registry.npmjs.org/@acme%2fapp", TARBALL],
55
+ });
56
+ });
57
+
58
+ it("polls the tarball url every 10 seconds when npm lists the version but does not serve it yet", async () => {
59
+ const got = await publish(world(), [SPEC], LISTED(2));
60
+ expect(got).toMatchObject({ code: 0, sleeps: [10_000, 10_000], npmCalls: [] });
61
+ expect(got.out).toBe(
62
+ "repo release publish: npm lists 0.1.0 but its tarball is not served yet; waiting…\n" +
63
+ `repo release publish: ${SPEC} is already on npm; nothing to publish\n`,
64
+ );
65
+ expect(got.urls.filter((url) => url === TARBALL)).toHaveLength(3);
66
+ });
67
+
68
+ it("gives up after 15 minutes", async () => {
69
+ const got = await publish(world(), [SPEC], LISTED(Number.MAX_SAFE_INTEGER));
70
+ expect(got.code).toBe(1);
71
+ expect(got.err).toBe(
72
+ `repo release publish: the tarball of ${SPEC} is not served after 15 minutes; rerun this job\n`,
73
+ );
74
+ expect(got.sleeps.reduce((sum, ms) => sum + ms, 0)).toBe(900_000);
75
+ });
76
+ });
77
+
78
+ describe("repo release publish for a package that is not on npm", () => {
79
+ const origin = (url: string) => {
80
+ const w = world();
81
+ gitIn(w.work, "remote", "set-url", "origin", url);
82
+ return publish(w, [SPEC], { ...GONE }, { ci: true });
83
+ };
84
+ const bootstrap = (repo: string) =>
85
+ `repo release publish: ${NAME} is not on npm yet, and npm adds a trusted publisher only to a package that exists;\n` +
86
+ ` run \`npm login\` and \`bun repo release publish ${SPEC}\` at that tag on your machine, then add the\n` +
87
+ ` trusted publisher at https://www.npmjs.com/package/${NAME}/access (GitHub Actions, ${repo}, release.yml)\n`;
88
+
89
+ it("prints the bootstrap steps in CI and publishes nothing", async () => {
90
+ const got = await origin("https://github.com/acme/app.git");
91
+ expect(got).toMatchObject({ code: 1, out: "", err: bootstrap("acme/app"), npmCalls: [], bunCalls: [] });
92
+ });
93
+
94
+ it("names the repository from an ssh remote too", async () => {
95
+ expect((await origin("git@github.com:acme/app.git")).err).toBe(bootstrap("acme/app"));
96
+ });
97
+
98
+ it("names the repository from a remote with no .git", async () => {
99
+ expect((await origin("https://github.com/acme/app")).err).toBe(bootstrap("acme/app"));
100
+ });
101
+
102
+ it("leaves a placeholder for a remote that is not on GitHub", async () => {
103
+ expect((await origin("/some/where.git")).err).toBe(bootstrap("<owner>/<repo>"));
104
+ });
105
+ });
106
+
107
+ describe("repo release publish when the registry does not answer", () => {
108
+ it("asks again every 10 seconds and goes on once it answers", async () => {
109
+ const got = await publish(world(), [SPEC], { ...LISTED(), docFails: 2 });
110
+ expect(got).toMatchObject({
111
+ code: 0,
112
+ out: `repo release publish: ${SPEC} is already on npm; nothing to publish\n`,
113
+ sleeps: [10_000, 10_000],
114
+ });
115
+ });
116
+
117
+ it("does not print the bootstrap steps for a registry that is down, and gives up after 15 minutes", async () => {
118
+ const got = await publish(world(), [SPEC], { ...GONE, docFails: Number.MAX_SAFE_INTEGER }, { ci: true });
119
+ expect(got).toMatchObject({
120
+ code: 1,
121
+ out: "",
122
+ err: "repo release publish: registry.npmjs.org did not answer for 15 minutes (HTTP 503); rerun the release job\n",
123
+ npmCalls: [],
124
+ });
125
+ expect(got.sleeps.reduce((sum, ms) => sum + ms, 0)).toBe(900_000);
126
+ });
127
+ });
@@ -0,0 +1,125 @@
1
+ import type { Runner } from "../scripts/ci-wait.ts";
2
+ import { packTarball, type PackIo } from "./pack.ts";
3
+ import { refuse } from "./refuse.ts";
4
+ import {
5
+ ACCESS_URL,
6
+ GITHUB_REPO,
7
+ GIT_HEAD,
8
+ GIT_ORIGIN_URL,
9
+ GIT_TAG_REF,
10
+ NPM_PUBLISH,
11
+ NPM_TAG,
12
+ RELEASE_WORKFLOW,
13
+ REPO_PLACEHOLDER,
14
+ TARBALL_URL,
15
+ WAIT_GIVE_UP_MINUTES,
16
+ } from "./release.constants.ts";
17
+ import {
18
+ PUBLISH_PREFIX,
19
+ WAIT_USAGE,
20
+ alreadyOnNpm,
21
+ bootstrap,
22
+ headNotTag,
23
+ listedNotServed,
24
+ npmFailed,
25
+ publishedLine,
26
+ registryDown,
27
+ tarballDiffers,
28
+ tarballNotServed,
29
+ } from "./release-messages.constants.ts";
30
+ import { listing, poll, registryDoc, splitSpec, type RegistryAnswer, type Spec, type WaitIo } from "./wait.ts";
31
+
32
+ /** What `repo release publish` works through: pack's tools, git, npm, the registry, the clock and whether it runs in CI. */
33
+ export type PublishIo = PackIo & Pick<WaitIo, "fetch" | "now" | "sleep"> & { git: Runner; npm: Runner; ci: boolean };
34
+
35
+ const served = async (fetcher: WaitIo["fetch"], url: string): Promise<true | null> => {
36
+ try {
37
+ return (await fetcher(url)).ok ? true : null;
38
+ } catch {
39
+ return null;
40
+ }
41
+ };
42
+
43
+ /** Polls the tarball url of `parts` until npm serves it; `announce` prints one line at the first miss. */
44
+ async function tarballServed(io: PublishIo, parts: Spec, announce: boolean): Promise<boolean> {
45
+ const url = TARBALL_URL(parts.name, parts.version);
46
+ let told = false;
47
+ const probe = async (): Promise<true | null> => {
48
+ const got = await served(io.fetch, url);
49
+ if (got === null && announce && !told) {
50
+ told = true;
51
+ io.stdout(`${PUBLISH_PREFIX}${listedNotServed(parts.version)}\n`);
52
+ }
53
+ return got;
54
+ };
55
+ return (await poll(io, probe, io.now())) !== null;
56
+ }
57
+
58
+ /** The registry's answer for `name`, asked again every 10 seconds while it does not answer; unreachable after 15 minutes. */
59
+ async function settled(io: PublishIo, name: string): Promise<RegistryAnswer> {
60
+ let why = "";
61
+ const probe = async (): Promise<RegistryAnswer | null> => {
62
+ const answer = await registryDoc(io.fetch, name);
63
+ if (answer.kind === "unreachable") why = answer.why;
64
+ return answer.kind === "unreachable" ? null : answer;
65
+ };
66
+ return (await poll(io, probe, io.now())) ?? { kind: "unreachable", why };
67
+ }
68
+
69
+ /** `owner/repo` of origin when it is on GitHub, else a placeholder. */
70
+ function ownerRepo(io: PublishIo): string {
71
+ return GITHUB_REPO.exec(io.git([...GIT_ORIGIN_URL], io.cwd).stdout.trim())?.[1] ?? REPO_PLACEHOLDER;
72
+ }
73
+
74
+ /** Packs and publishes the tag's package; the exit code. */
75
+ async function publishNew(io: PublishIo, parts: Spec, tag: string): Promise<number> {
76
+ const packed = packTarball(io);
77
+ if (typeof packed === "string") return refuse(io, PUBLISH_PREFIX, packed);
78
+ if (packed.name !== parts.name || packed.version !== parts.version) {
79
+ return refuse(io, PUBLISH_PREFIX, tarballDiffers(`${packed.name}@${packed.version}`, tag));
80
+ }
81
+ const prerelease = parts.version.includes("-");
82
+ const run = io.npm(NPM_PUBLISH(packed.tarball, prerelease), io.cwd);
83
+ if (run.code !== 0 && listing(await registryDoc(io.fetch, parts.name), parts.version) === null) {
84
+ return refuse(io, PUBLISH_PREFIX, npmFailed(run.code, parts.version));
85
+ }
86
+ if (!(await tarballServed(io, parts, false))) {
87
+ return refuse(io, PUBLISH_PREFIX, tarballNotServed(tag, WAIT_GIVE_UP_MINUTES));
88
+ }
89
+ io.stdout(`${PUBLISH_PREFIX}${publishedLine(tag, NPM_TAG(prerelease))}\n`);
90
+ return 0;
91
+ }
92
+
93
+ /** A version npm lists: waits until it serves the tarball, then reports that there is nothing to publish. */
94
+ async function alreadyThere(io: PublishIo, parts: Spec, tag: string): Promise<number> {
95
+ if (!(await tarballServed(io, parts, true))) {
96
+ return refuse(io, PUBLISH_PREFIX, tarballNotServed(tag, WAIT_GIVE_UP_MINUTES));
97
+ }
98
+ io.stdout(`${PUBLISH_PREFIX}${alreadyOnNpm(tag)}\n`);
99
+ return 0;
100
+ }
101
+
102
+ /** `repo release publish <name>@<version>`: packs the checked-out tag and publishes it, unless npm already serves it. */
103
+ export async function releasePublish(argv: readonly string[], io: PublishIo): Promise<number> {
104
+ const tag = argv[0] ?? "";
105
+ const parts = argv.length === 1 ? splitSpec(tag) : null;
106
+ if (parts === null) return refuse(io, PUBLISH_PREFIX, WAIT_USAGE);
107
+ const head = io.git([...GIT_HEAD], io.cwd).stdout.trim();
108
+ if (head === "" || head !== io.git(GIT_TAG_REF(tag), io.cwd).stdout.trim()) {
109
+ return refuse(io, PUBLISH_PREFIX, headNotTag(tag));
110
+ }
111
+ const answer = await settled(io, parts.name);
112
+ if (answer.kind === "unreachable") {
113
+ return refuse(io, PUBLISH_PREFIX, registryDown(answer.why, WAIT_GIVE_UP_MINUTES));
114
+ }
115
+ if (listing(answer, parts.version) !== null) return alreadyThere(io, parts, tag);
116
+ if (answer.kind === "missing" && io.ci) {
117
+ const url = ACCESS_URL(parts.name);
118
+ return refuse(
119
+ io,
120
+ PUBLISH_PREFIX,
121
+ bootstrap({ name: parts.name, tag, repo: ownerRepo(io), url, workflow: RELEASE_WORKFLOW }),
122
+ );
123
+ }
124
+ return publishNew(io, parts, tag);
125
+ }
@@ -0,0 +1,5 @@
1
+ /** Prints `prefix` and `line` to stderr and returns the failing exit code. */
2
+ export function refuse(io: { stderr: (text: string) => void }, prefix: string, line: string): number {
3
+ io.stderr(`${prefix}${line}\n`);
4
+ return 1;
5
+ }
@@ -0,0 +1,96 @@
1
+ /** Every refusal and result line of `repo release`, so the words live in one place. */
2
+ export const PREFIX = "repo release: ";
3
+ export const USAGE =
4
+ "usage: repo release [--stable | --pre <id>] | wait | gate | pack | publish | github <name>@<version>";
5
+ export const DIRTY = "the working tree has changes; commit or stash them first";
6
+ export const GH_MISSING = "gh is not installed or not logged in; run `gh auth login`";
7
+ export const EMPTY_UNRELEASED = "CHANGELOG.md has no entries under ## [Unreleased]; add them first";
8
+ export const PACK_OFF = 'the release pack is off; add "release" to packs in workerstack.config.ts';
9
+ export const NO_MANIFEST = "package.json needs a name and a version";
10
+ export const NOT_CHECKOUT = "run it inside a git checkout";
11
+ export const notMain = (main: string, branch: string): string => `release from ${main}; this checkout is on ${branch}`;
12
+ export const differs = (main: string, behind: number, ahead: number): string =>
13
+ `${main} and origin/${main} differ (${behind} behind, ${ahead} ahead); pull or push first`;
14
+ export const nothing = (since: string | null): string =>
15
+ `nothing to release since ${since ?? "the first commit"}: no feat, fix, perf, revert or breaking commit`;
16
+ export const isPrivate = (name: string): string => `${name} is private; set "private": false to publish it`;
17
+ export const stableOnly = (name: string, version: string): string =>
18
+ `--stable moves a 0.x version to 1.0.0; ${name} is ${version}`;
19
+ export const notSemver = (name: string, version: string): string => `${name} is at ${version}, which is not semver`;
20
+ export const tagExists = (tag: string): string =>
21
+ `the tag ${tag} already exists; a tag is never moved, so release the next version`;
22
+ export const gitFailed = (command: string, why: string): string => `git ${command} failed: ${why}`;
23
+ export const ciFailed = (short: string): string =>
24
+ `CI failed on ${short} (jobs above); fix it in a new commit, push, and run bun repo release again`;
25
+ export const ciSuperseded = (short: string): string =>
26
+ `a newer push cancelled CI on ${short}; pull and run bun repo release again`;
27
+ export const ciNoResult = (short: string, detail: string): string =>
28
+ `no CI result for ${short} (${detail}); it is pushed and untagged; run bun repo release again`;
29
+ export type Summary = { name: string; move: string; steps: readonly string[]; tag: string };
30
+ export const summary = ({ name, move, steps, tag }: Summary): string =>
31
+ [`${name} ${move}`, ...steps, "CI passed", `tagged ${tag}`].join(" · ");
32
+ export const WAIT_PREFIX = "repo release wait: ";
33
+ export const WAIT_USAGE = "name the tag as <name>@<version>, e.g. @workerstack/repo@1.0.0";
34
+ export const published = (version: string, tag: string | undefined): string =>
35
+ tag === undefined ? `published ${version}` : `published ${version} under ${tag}`;
36
+ export const notOnNpm = (spec: string, minutes: number): string =>
37
+ `${spec} is not on npm after ${minutes} minutes; see gh run list --workflow release`;
38
+ export const HOOKS_REFUSED =
39
+ "the hooks refused the release commit (output above); nothing was changed, so fix it and run bun repo release again";
40
+ export const GATE_PREFIX = "repo release gate: ";
41
+ export const tagMissing = (tag: string): string => `the tag ${tag} does not exist here; fetch the tags`;
42
+ export const noManifestAt = (short: string): string => `package.json at ${short} needs a name and a version`;
43
+ export const nameDiffers = (tagName: string, short: string, name: string): string =>
44
+ `the tag names ${tagName} but package.json at ${short} names ${name}`;
45
+ export const versionDiffers = (tagVersion: string, short: string, version: string): string =>
46
+ `the tag says ${tagVersion} but package.json at ${short} says ${version}; a tag is never moved, so release the next version`;
47
+ export const noSection = (short: string, version: string): string =>
48
+ `CHANGELOG.md at ${short} has no ## [${version}] section; publish is refused without one`;
49
+ export const notOnBranch = (short: string, branch: string): string =>
50
+ `${short} is not on ${branch}; tag a commit on ${branch} after its CI passed`;
51
+ export const noDefaultBranch = (why: string): string =>
52
+ `gh could not read the default branch (${why}); check gh auth status`;
53
+ export const gateOk = (tag: string, short: string, version: string): string =>
54
+ `${tag} is ${short}, version ${version}, changelog ok`;
55
+ /** The refusal line for a ci wait that did not pass, by how it ended. */
56
+ export const gateCiLines = {
57
+ failed: (short: string): string =>
58
+ `CI failed on ${short}; a tag is never moved, so fix it in a new commit and release the next version`,
59
+ superseded: (short: string): string => `a newer push cancelled CI on ${short}; rerun ci on it, then rerun this job`,
60
+ error: (short: string, detail: string): string =>
61
+ `no CI result for ${short} (${detail}); rerun this job once ci has run`,
62
+ };
63
+ export const PACK_PREFIX = "repo release pack: ";
64
+ export const PACK_ARGS = "takes no arguments; it packs the package in this checkout";
65
+ export const packFailed = (why: string): string => `bun pm pack failed: ${why}`;
66
+ export const PACKED_UNREADABLE = "the packed tarball has no readable package.json with a name and a version";
67
+ export const unpinned = (spec: string, dependency: string): string =>
68
+ `the packed package.json names ${spec} for ${dependency}; pin it before releasing`;
69
+ export const PUBLISH_PREFIX = "repo release publish: ";
70
+ export const headNotTag = (tag: string): string => `HEAD is not ${tag}; check out the tag first`;
71
+ export const alreadyOnNpm = (tag: string): string => `${tag} is already on npm; nothing to publish`;
72
+ export const listedNotServed = (version: string): string =>
73
+ `npm lists ${version} but its tarball is not served yet; waiting…`;
74
+ export const tarballNotServed = (tag: string, minutes: number): string =>
75
+ `the tarball of ${tag} is not served after ${minutes} minutes; rerun this job`;
76
+ export type Bootstrap = { name: string; tag: string; repo: string; url: string; workflow: string };
77
+ export const bootstrap = ({ name, tag, repo, url, workflow }: Bootstrap): string =>
78
+ `${name} is not on npm yet, and npm adds a trusted publisher only to a package that exists;\n` +
79
+ ` run \`npm login\` and \`bun repo release publish ${tag}\` at that tag on your machine, then add the\n` +
80
+ ` trusted publisher at ${url} (GitHub Actions, ${repo}, ${workflow})`;
81
+ export const tarballDiffers = (packed: string, tag: string): string =>
82
+ `the tarball holds ${packed} but the tag is ${tag}; nothing was published`;
83
+ export const npmFailed = (code: number, version: string): string =>
84
+ `npm publish exited ${code} and npm does not list ${version}; its output is above`;
85
+ export const publishedLine = (tag: string, npmTag: string): string =>
86
+ `packed ${tag} · published under ${npmTag} · tarball served`;
87
+ export const GITHUB_PREFIX = "repo release github: ";
88
+ export const noNotes = (version: string): string =>
89
+ `CHANGELOG.md has no ## [${version}] section to make the notes from`;
90
+ export const releaseCreated = (tag: string): string => `created release ${tag} from CHANGELOG.md`;
91
+ export const releaseExists = (tag: string): string => `release ${tag} already exists`;
92
+ export const createFailed = (why: string): string => `gh release create failed: ${why}`;
93
+ export const registryDown = (why: string, minutes: number): string =>
94
+ `registry.npmjs.org did not answer for ${minutes} minutes (${why}); rerun the release job`;
95
+ export const packCount = (count: number, dir: string): string =>
96
+ `bun pm pack left ${count} tarballs in ${dir}; expected exactly one`;