@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,192 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { HEADER, context } from "../fixtures/release-context.ts";
3
+ import { GENERATORS } from "./outputs.ts";
4
+ import { releaseWorkflow } from "./release-workflow.ts";
5
+
6
+ const PATH = ".github/workflows/release.yml";
7
+ const CHECKOUT = "actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1";
8
+ const SETUP_BUN = "oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0";
9
+ const SETUP_NODE = "actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0";
10
+ const PUBLIC = { name: "@acme/app", private: false };
11
+
12
+ const EXPECTED = `${HEADER}
13
+ name: release
14
+
15
+ on:
16
+ push:
17
+ tags:
18
+ - "@acme/app@*"
19
+ workflow_dispatch:
20
+ inputs:
21
+ tag:
22
+ description: "Existing tag to release, e.g. @acme/app@1.0.0"
23
+ required: true
24
+
25
+ concurrency:
26
+ group: release-\${{ inputs.tag || github.ref_name }}
27
+ cancel-in-progress: false
28
+
29
+ permissions:
30
+ contents: read
31
+
32
+ jobs:
33
+ gate:
34
+ name: gate
35
+ runs-on: ubuntu-latest
36
+ timeout-minutes: 60
37
+ permissions:
38
+ actions: read
39
+ contents: read
40
+ env:
41
+ TAG: \${{ inputs.tag || github.ref_name }}
42
+ GH_TOKEN: \${{ github.token }}
43
+ steps:
44
+ - uses: ${CHECKOUT}
45
+ with:
46
+ ref: \${{ inputs.tag || github.ref }}
47
+ fetch-depth: 0
48
+ - uses: ${SETUP_BUN}
49
+ with:
50
+ bun-version-file: package.json
51
+ - run: bun install --frozen-lockfile
52
+ - run: bun repo release gate "$TAG"
53
+
54
+ publish:
55
+ name: publish
56
+ needs: gate
57
+ runs-on: ubuntu-latest
58
+ timeout-minutes: 25
59
+ permissions:
60
+ contents: read
61
+ id-token: write
62
+ env:
63
+ TAG: \${{ inputs.tag || github.ref_name }}
64
+ steps:
65
+ - uses: ${CHECKOUT}
66
+ with:
67
+ ref: \${{ inputs.tag || github.ref }}
68
+ - uses: ${SETUP_BUN}
69
+ with:
70
+ bun-version-file: package.json
71
+ - run: bun install --frozen-lockfile
72
+ - uses: ${SETUP_NODE}
73
+ with:
74
+ node-version: "24"
75
+ package-manager-cache: false
76
+ - run: npm install --global npm@12.2.2
77
+ - run: bun repo release publish "$TAG"
78
+
79
+ github:
80
+ name: github
81
+ needs: publish
82
+ runs-on: ubuntu-latest
83
+ timeout-minutes: 10
84
+ permissions:
85
+ contents: write
86
+ env:
87
+ TAG: \${{ inputs.tag || github.ref_name }}
88
+ GH_TOKEN: \${{ github.token }}
89
+ steps:
90
+ - uses: ${CHECKOUT}
91
+ with:
92
+ ref: \${{ inputs.tag || github.ref }}
93
+ - uses: ${SETUP_BUN}
94
+ with:
95
+ bun-version-file: package.json
96
+ - run: bun install --frozen-lockfile
97
+ - run: bun repo release github "$TAG"
98
+ `;
99
+
100
+ const generated = (): string => {
101
+ const output = releaseWorkflow(context(["release"], PUBLIC)).outputs[0];
102
+ return output?.kind === "file" ? output.content : "";
103
+ };
104
+
105
+ /** The text of the job `name`, from its heading to the next job's. */
106
+ const job = (name: string): string => {
107
+ const rest = generated().split(`\n ${name}:\n`)[1] ?? "";
108
+ return rest.split(/\n [a-z]+:\n/)[0] ?? "";
109
+ };
110
+
111
+ describe("release workflow", () => {
112
+ it("is a generator", () => {
113
+ expect(GENERATORS).toContain(releaseWorkflow);
114
+ });
115
+
116
+ it("writes the whole release.yml for a public root package", () => {
117
+ expect(releaseWorkflow(context(["release"], PUBLIC))).toEqual({
118
+ outputs: [{ kind: "file", path: PATH, content: EXPECTED, tracked: true, managed: "whole" }],
119
+ errors: [],
120
+ });
121
+ });
122
+
123
+ it.each([
124
+ ["a private root", { name: "@acme/app", private: true }],
125
+ ["a root that does not say it is public", { name: "@acme/app" }],
126
+ ["a root with no name", { private: false }],
127
+ ])("writes nothing for %s", (_, manifest) => {
128
+ expect(releaseWorkflow(context(["release"], manifest))).toEqual({ outputs: [], errors: [] });
129
+ });
130
+
131
+ it("writes nothing with the pack off", () => {
132
+ expect(releaseWorkflow(context(["agents", "standards", "docs"], PUBLIC))).toEqual({ outputs: [], errors: [] });
133
+ });
134
+ });
135
+
136
+ describe("release workflow owns its file", () => {
137
+ it("rewrites a release.yml that has the generated header on its first line", () => {
138
+ const result = releaseWorkflow(context(["release"], PUBLIC, { [PATH]: `${HEADER}\nname: old\n` }));
139
+ expect(result.errors).toEqual([]);
140
+ expect(result.outputs).toHaveLength(1);
141
+ });
142
+
143
+ it("refuses a hand-written release.yml", () => {
144
+ expect(releaseWorkflow(context(["release"], PUBLIC, { [PATH]: "name: Release\n" }))).toEqual({
145
+ outputs: [],
146
+ errors: [".github/workflows/release.yml is hand-written; delete it so repo sync can own it"],
147
+ });
148
+ });
149
+ });
150
+
151
+ describe("release workflow safety", () => {
152
+ it.each(["gate", "publish", "github"])("checks out the tag in the %s job", (name) => {
153
+ expect(job(name)).toContain("ref: ${{ inputs.tag || github.ref }}");
154
+ });
155
+
156
+ it("gives gh the token in the gate and github jobs", () => {
157
+ expect(job("gate")).toContain("GH_TOKEN: ${{ github.token }}");
158
+ expect(job("github")).toContain("GH_TOKEN: ${{ github.token }}");
159
+ });
160
+
161
+ it("holds no npm token anywhere", () => {
162
+ expect(generated()).not.toMatch(/NPM_TOKEN|NODE_AUTH_TOKEN|secrets\./);
163
+ });
164
+ });
165
+
166
+ describe("release workflow publishing", () => {
167
+ it("grants id-token: write to the publish job only", () => {
168
+ expect(generated().match(/id-token: write/g)).toHaveLength(1);
169
+ expect(job("publish")).toContain("id-token: write");
170
+ });
171
+
172
+ it("pins every action to a 40-character sha", () => {
173
+ const uses = generated().match(/uses: .*/g) ?? [];
174
+ expect(uses).toHaveLength(7);
175
+ for (const line of uses) expect(line).toMatch(/^uses: [\w./-]+@[0-9a-f]{40} # v\d/);
176
+ });
177
+ });
178
+
179
+ describe("release workflow triggers", () => {
180
+ it("runs only on the tag and by hand", () => {
181
+ const on = generated().split("\njobs:")[0]?.split("\non:\n")[1]?.split("\n\nconcurrency")[0] ?? "";
182
+ expect(on.match(/^ {2}\w+:/gm)).toEqual([" push:", " workflow_dispatch:"]);
183
+ });
184
+
185
+ it("runs the release commands through bun repo, never a root repo.ts", () => {
186
+ expect(generated().match(/bun repo release \w+/g)).toEqual([
187
+ "bun repo release gate",
188
+ "bun repo release publish",
189
+ "bun repo release github",
190
+ ]);
191
+ });
192
+ });
@@ -0,0 +1,86 @@
1
+ import { lines } from "../scripts/managed-text.ts";
2
+ import type { Generator } from "./outputs.ts";
3
+ import {
4
+ BUN_STEPS,
5
+ CHECKOUT_TAG_FULL_STEP,
6
+ CHECKOUT_TAG_STEP,
7
+ GATE_ENV,
8
+ GATE_PERMISSIONS,
9
+ GATE_TIMEOUT_MINUTES,
10
+ GITHUB_PERMISSIONS,
11
+ GITHUB_TIMEOUT_MINUTES,
12
+ NODE_STEPS,
13
+ PREAMBLE,
14
+ PUBLISH_ENV,
15
+ PUBLISH_PERMISSIONS,
16
+ PUBLISH_TIMEOUT_MINUTES,
17
+ RELEASE_HEADER,
18
+ RELEASE_PATH,
19
+ } from "./release-workflow.constants.ts";
20
+
21
+ type Job = {
22
+ id: string;
23
+ needs?: string;
24
+ minutes: number;
25
+ permissions: string;
26
+ env: string;
27
+ steps: readonly string[];
28
+ command: string;
29
+ };
30
+
31
+ const render = (job: Job): string =>
32
+ [
33
+ ` ${job.id}:`,
34
+ ` name: ${job.id}`,
35
+ ...(job.needs === undefined ? [] : [` needs: ${job.needs}`]),
36
+ " runs-on: ubuntu-latest",
37
+ ` timeout-minutes: ${job.minutes}`,
38
+ job.permissions,
39
+ job.env,
40
+ " steps:",
41
+ ...job.steps,
42
+ ` - run: bun repo release ${job.command} "$TAG"`,
43
+ ].join("\n");
44
+
45
+ const JOBS: readonly Job[] = [
46
+ {
47
+ id: "gate",
48
+ minutes: GATE_TIMEOUT_MINUTES,
49
+ permissions: GATE_PERMISSIONS,
50
+ env: GATE_ENV,
51
+ steps: [CHECKOUT_TAG_FULL_STEP, ...BUN_STEPS],
52
+ command: "gate",
53
+ },
54
+ {
55
+ id: "publish",
56
+ needs: "gate",
57
+ minutes: PUBLISH_TIMEOUT_MINUTES,
58
+ permissions: PUBLISH_PERMISSIONS,
59
+ env: PUBLISH_ENV,
60
+ steps: [CHECKOUT_TAG_STEP, ...BUN_STEPS, ...NODE_STEPS],
61
+ command: "publish",
62
+ },
63
+ {
64
+ id: "github",
65
+ needs: "publish",
66
+ minutes: GITHUB_TIMEOUT_MINUTES,
67
+ permissions: GITHUB_PERMISSIONS,
68
+ env: GATE_ENV,
69
+ steps: [CHECKOUT_TAG_STEP, ...BUN_STEPS],
70
+ command: "github",
71
+ },
72
+ ];
73
+
74
+ /** Under the release pack with a public root package, the tracked, whole-file `release.yml`; a hand-written one is refused. */
75
+ export const releaseWorkflow: Generator = (ctx) => {
76
+ const { name, private: isPrivate } = ctx.workspace.manifest;
77
+ if (!ctx.config.packs.includes("release") || name === undefined || isPrivate !== false) {
78
+ return { outputs: [], errors: [] };
79
+ }
80
+ const existing = ctx.read(RELEASE_PATH);
81
+ if (existing !== null && lines(existing)[0] !== RELEASE_HEADER) {
82
+ return { outputs: [], errors: [`${RELEASE_PATH} is hand-written; delete it so repo sync can own it`] };
83
+ }
84
+ const content = `${RELEASE_HEADER}\n${PREAMBLE.replaceAll("%NAME%", name)}\n${JOBS.map(render).join("\n\n")}\n`;
85
+ return { outputs: [{ kind: "file", path: RELEASE_PATH, content, tracked: true, managed: "whole" }], errors: [] };
86
+ };
@@ -0,0 +1,93 @@
1
+ /** The generated header, which must be the first line of a ci.yml sync owns. */
2
+ export const CI_HEADER: string = "# Generated by repo sync. Do not edit.";
3
+ export const CI_PATH: string = ".github/workflows/ci.yml";
4
+ export const E2E_SCRIPT: string = "test:e2e";
5
+
6
+ /** Action pins, each a full commit SHA with its release as the comment. */
7
+ const CHECKOUT: string = "actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1";
8
+ export const UPLOAD: string = "actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2";
9
+ export const DOWNLOAD: string = "actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2";
10
+ const SETUP_BUN: string = "oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0";
11
+
12
+ const OS_LIST: string = "[ubuntu-latest, windows-latest, macos-latest]";
13
+ export const TEST_TIMEOUT_MINUTES: number = 30;
14
+ export const SLOW_TIMEOUT_MINUTES: number = 40;
15
+
16
+ const ROOT_REFUSAL: string = "CI must run as a non-root user; the suite assumes unprivileged file permissions";
17
+
18
+ /** The workflow text before the jobs: name, triggers, concurrency and permissions. */
19
+ export const PREAMBLE: string = `name: ci
20
+
21
+ on:
22
+ push:
23
+ branches: [main]
24
+ pull_request:
25
+ workflow_dispatch:
26
+
27
+ concurrency:
28
+ group: ci-\${{ github.workflow }}-\${{ github.ref }}
29
+ cancel-in-progress: true
30
+
31
+ permissions:
32
+ contents: read
33
+
34
+ jobs:`;
35
+
36
+ /** The shared checkout, bun and install steps; `fetchDepth` adds full history. */
37
+ export const SETUP_BUN_STEP: string = ` - uses: ${SETUP_BUN}
38
+ with:
39
+ bun-version-file: package.json`;
40
+ export const CHECKOUT_STEP: string = ` - uses: ${CHECKOUT}`;
41
+ export const CHECKOUT_FULL_STEP: string = ` - uses: ${CHECKOUT}
42
+ with:
43
+ fetch-depth: 0`;
44
+ export const INSTALL_STEP: string = " - run: bun install --frozen-lockfile";
45
+
46
+ export const NON_ROOT_STEP: string = ` - name: Refuse to run as root
47
+ if: runner.os != 'Windows'
48
+ run: |
49
+ if [ "$(id -u)" = "0" ]; then echo "${ROOT_REFUSAL}"; exit 1; fi`;
50
+
51
+ export const MATRIX: string = ` runs-on: \${{ matrix.os }}
52
+ timeout-minutes: %MINUTES%
53
+ strategy:
54
+ fail-fast: false
55
+ matrix:
56
+ os: ${OS_LIST}`;
57
+
58
+ export const NO_NODE_STEPS: string = ` - name: Remove node
59
+ run: |
60
+ sudo rm -f $(which -a node nodejs)
61
+ sudo rm -rf /opt/hostedtoolcache/node
62
+ hash -r
63
+ - name: Assert node is gone
64
+ run: |
65
+ if command -v node; then echo "node is still on PATH"; exit 1; fi`;
66
+
67
+ /** The test job's last steps: the gates write this OS's skip report and it is uploaded, even when a gate failed. */
68
+ export const REPORT_STEPS: string = ` - name: Run the gates and write the skip report
69
+ shell: bash
70
+ env:
71
+ REPORT: ci-report-\${{ runner.os }}.json
72
+ COVERAGE: \${{ runner.os != 'Linux' && '--no-coverage' || '' }}
73
+ run: bun repo ci --report "$REPORT" $COVERAGE
74
+ - uses: ${UPLOAD}
75
+ if: always()
76
+ with:
77
+ name: ci-report-\${{ matrix.os }}
78
+ path: ci-report-\${{ runner.os }}.json
79
+ if-no-files-found: error`;
80
+
81
+ /** The step that checks every commit of the push or pull request; an empty BASE leaves HEAD alone to be checked. */
82
+ export const COMMITS_STEP: string = ` - name: Check every pushed commit
83
+ env:
84
+ BASE: \${{ github.event.pull_request.base.sha || github.event.before }}
85
+ run: bun repo commit-msg --range "$BASE..HEAD"`;
86
+
87
+ /** The skips job's steps after the install: the per-OS reports are downloaded into one directory and compared. */
88
+ export const SKIPS_STEPS: string = ` - uses: ${DOWNLOAD}
89
+ with:
90
+ pattern: ci-report-*
91
+ merge-multiple: true
92
+ path: reports
93
+ - run: bun repo ci skips reports`;
@@ -0,0 +1,187 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { HEADER, context } from "../fixtures/release-context.ts";
3
+ import { type SyncContext } from "./outputs.ts";
4
+ import { release } from "./release.ts";
5
+ import type { PackageJson } from "./workspace.ts";
6
+
7
+ const CI_PATH = ".github/workflows/ci.yml";
8
+
9
+ const CHECKOUT = "actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1";
10
+ const SETUP_BUN = "oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0";
11
+ const UPLOAD = "actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2";
12
+ const DOWNLOAD = "actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2";
13
+
14
+ const HEAD = `${HEADER}
15
+ name: ci
16
+
17
+ on:
18
+ push:
19
+ branches: [main]
20
+ pull_request:
21
+ workflow_dispatch:
22
+
23
+ concurrency:
24
+ group: ci-\${{ github.workflow }}-\${{ github.ref }}
25
+ cancel-in-progress: true
26
+
27
+ permissions:
28
+ contents: read
29
+
30
+ jobs:
31
+ test:
32
+ name: test (\${{ matrix.os }})
33
+ runs-on: \${{ matrix.os }}
34
+ timeout-minutes: 30
35
+ strategy:
36
+ fail-fast: false
37
+ matrix:
38
+ os: [ubuntu-latest, windows-latest, macos-latest]
39
+ steps:
40
+ - uses: ${CHECKOUT}
41
+ - uses: ${SETUP_BUN}
42
+ with:
43
+ bun-version-file: package.json
44
+ - name: Refuse to run as root
45
+ if: runner.os != 'Windows'
46
+ run: |
47
+ if [ "$(id -u)" = "0" ]; then echo "CI must run as a non-root user; the suite assumes unprivileged file permissions"; exit 1; fi
48
+ - run: bun install --frozen-lockfile
49
+ - name: Run the gates and write the skip report
50
+ shell: bash
51
+ env:
52
+ REPORT: ci-report-\${{ runner.os }}.json
53
+ COVERAGE: \${{ runner.os != 'Linux' && '--no-coverage' || '' }}
54
+ run: bun repo ci --report "$REPORT" $COVERAGE
55
+ - uses: ${UPLOAD}
56
+ if: always()
57
+ with:
58
+ name: ci-report-\${{ matrix.os }}
59
+ path: ci-report-\${{ runner.os }}.json
60
+ if-no-files-found: error
61
+
62
+ commits:
63
+ name: commits
64
+ runs-on: ubuntu-latest
65
+ timeout-minutes: 30
66
+ steps:
67
+ - uses: ${CHECKOUT}
68
+ with:
69
+ fetch-depth: 0
70
+ - uses: ${SETUP_BUN}
71
+ with:
72
+ bun-version-file: package.json
73
+ - run: bun install --frozen-lockfile
74
+ - name: Check every pushed commit
75
+ env:
76
+ BASE: \${{ github.event.pull_request.base.sha || github.event.before }}
77
+ run: bun repo commit-msg --range "$BASE..HEAD"
78
+
79
+ skips:
80
+ name: skips
81
+ needs: test
82
+ runs-on: ubuntu-latest
83
+ timeout-minutes: 30
84
+ steps:
85
+ - uses: ${CHECKOUT}
86
+ - uses: ${SETUP_BUN}
87
+ with:
88
+ bun-version-file: package.json
89
+ - run: bun install --frozen-lockfile
90
+ - uses: ${DOWNLOAD}
91
+ with:
92
+ pattern: ci-report-*
93
+ merge-multiple: true
94
+ path: reports
95
+ - run: bun repo ci skips reports
96
+
97
+ no-node:
98
+ name: no-node
99
+ runs-on: ubuntu-latest
100
+ timeout-minutes: 40
101
+ steps:
102
+ - uses: ${CHECKOUT}
103
+ - uses: ${SETUP_BUN}
104
+ with:
105
+ bun-version-file: package.json
106
+ - name: Remove node
107
+ run: |
108
+ sudo rm -f $(which -a node nodejs)
109
+ sudo rm -rf /opt/hostedtoolcache/node
110
+ hash -r
111
+ - name: Assert node is gone
112
+ run: |
113
+ if command -v node; then echo "node is still on PATH"; exit 1; fi
114
+ - run: bun install --frozen-lockfile
115
+ - run: bun repo ci
116
+ `;
117
+
118
+ const NO_NODE_SHALLOW = ` steps:
119
+ - uses: ${CHECKOUT}
120
+ - uses: ${SETUP_BUN}
121
+ with:
122
+ bun-version-file: package.json
123
+ - name: Remove node`;
124
+ const NO_NODE_FULL = NO_NODE_SHALLOW.replace(
125
+ `- uses: ${CHECKOUT}`,
126
+ `- uses: ${CHECKOUT}\n with:\n fetch-depth: 0`,
127
+ );
128
+
129
+ const E2E_IN_NO_NODE = ` - run: bun run test:e2e
130
+ `;
131
+
132
+ const E2E_JOB = `
133
+ e2e:
134
+ name: e2e (\${{ matrix.os }})
135
+ runs-on: \${{ matrix.os }}
136
+ timeout-minutes: 40
137
+ strategy:
138
+ fail-fast: false
139
+ matrix:
140
+ os: [ubuntu-latest, windows-latest, macos-latest]
141
+ defaults:
142
+ run:
143
+ shell: bash
144
+ steps:
145
+ - uses: ${CHECKOUT}
146
+ with:
147
+ fetch-depth: 0
148
+ - uses: ${SETUP_BUN}
149
+ with:
150
+ bun-version-file: package.json
151
+ - run: bun install --frozen-lockfile
152
+ - run: bun run test:e2e
153
+ `;
154
+
155
+ const lookup = (ctx: SyncContext) => release(ctx).outputs[0];
156
+
157
+ describe("release", () => {
158
+ it("writes the whole tracked ci.yml without an e2e job when the root has no test:e2e script", () => {
159
+ expect(release(context(["release"]))).toEqual({
160
+ outputs: [{ kind: "file", path: CI_PATH, content: HEAD, tracked: true, managed: "whole" }],
161
+ errors: [],
162
+ });
163
+ });
164
+
165
+ it("adds the e2e step and job when the root has a test:e2e script", () => {
166
+ const manifest: PackageJson = { scripts: { "test:e2e": "vitest" } };
167
+ expect(lookup(context(["release"], manifest))).toEqual({
168
+ kind: "file",
169
+ path: CI_PATH,
170
+ content: HEAD.replace(NO_NODE_SHALLOW, NO_NODE_FULL) + E2E_IN_NO_NODE + E2E_JOB,
171
+ tracked: true,
172
+ managed: "whole",
173
+ });
174
+ });
175
+
176
+ it("ignores a scripts field that is not an object with a test:e2e string", () => {
177
+ expect(lookup(context(["release"], { scripts: "nope" }))).toMatchObject({ content: HEAD });
178
+ expect(lookup(context(["release"], { scripts: { "test:e2e": 1 } }))).toMatchObject({ content: HEAD });
179
+ expect(lookup(context(["release"], { scripts: null }))).toMatchObject({ content: HEAD });
180
+ });
181
+
182
+ it("never uses setup-node", () => {
183
+ const manifest: PackageJson = { scripts: { "test:e2e": "vitest" } };
184
+ const output = lookup(context(["release"], manifest));
185
+ expect(output?.kind === "file" && output.content.includes("setup-node")).toBe(false);
186
+ });
187
+ });
@@ -0,0 +1,112 @@
1
+ import { lines } from "../scripts/managed-text.ts";
2
+ import type { Generator, SyncContext } from "./outputs.ts";
3
+ import {
4
+ CHECKOUT_FULL_STEP,
5
+ CHECKOUT_STEP,
6
+ CI_HEADER,
7
+ CI_PATH,
8
+ COMMITS_STEP,
9
+ E2E_SCRIPT,
10
+ INSTALL_STEP,
11
+ MATRIX,
12
+ NON_ROOT_STEP,
13
+ NO_NODE_STEPS,
14
+ PREAMBLE,
15
+ REPORT_STEPS,
16
+ SETUP_BUN_STEP,
17
+ SKIPS_STEPS,
18
+ SLOW_TIMEOUT_MINUTES,
19
+ TEST_TIMEOUT_MINUTES,
20
+ } from "./release.constants.ts";
21
+
22
+ const matrix = (minutes: number) => MATRIX.replace("%MINUTES%", String(minutes));
23
+
24
+ /** Whether the root manifest's `scripts` has a string `name`. */
25
+ export function hasScript(ctx: SyncContext, name: string): boolean {
26
+ const scripts: unknown = ctx.workspace.manifest.scripts;
27
+ if (typeof scripts !== "object" || scripts === null) return false;
28
+ return typeof Reflect.get(scripts, name) === "string";
29
+ }
30
+
31
+ const testJob = () =>
32
+ [
33
+ " test:",
34
+ " name: test (${{ matrix.os }})",
35
+ matrix(TEST_TIMEOUT_MINUTES),
36
+ " steps:",
37
+ CHECKOUT_STEP,
38
+ SETUP_BUN_STEP,
39
+ NON_ROOT_STEP,
40
+ INSTALL_STEP,
41
+ REPORT_STEPS,
42
+ ].join("\n");
43
+
44
+ const commitsJob = () =>
45
+ [
46
+ " commits:",
47
+ " name: commits",
48
+ " runs-on: ubuntu-latest",
49
+ ` timeout-minutes: ${TEST_TIMEOUT_MINUTES}`,
50
+ " steps:",
51
+ CHECKOUT_FULL_STEP,
52
+ SETUP_BUN_STEP,
53
+ INSTALL_STEP,
54
+ COMMITS_STEP,
55
+ ].join("\n");
56
+
57
+ const skipsJob = () =>
58
+ [
59
+ " skips:",
60
+ " name: skips",
61
+ " needs: test",
62
+ " runs-on: ubuntu-latest",
63
+ ` timeout-minutes: ${TEST_TIMEOUT_MINUTES}`,
64
+ " steps:",
65
+ CHECKOUT_STEP,
66
+ SETUP_BUN_STEP,
67
+ INSTALL_STEP,
68
+ SKIPS_STEPS,
69
+ ].join("\n");
70
+
71
+ const noNodeJob = (e2e: boolean) =>
72
+ [
73
+ " no-node:",
74
+ " name: no-node",
75
+ " runs-on: ubuntu-latest",
76
+ ` timeout-minutes: ${SLOW_TIMEOUT_MINUTES}`,
77
+ " steps:",
78
+ e2e ? CHECKOUT_FULL_STEP : CHECKOUT_STEP,
79
+ SETUP_BUN_STEP,
80
+ NO_NODE_STEPS,
81
+ INSTALL_STEP,
82
+ " - run: bun repo ci",
83
+ ...(e2e ? [` - run: bun run ${E2E_SCRIPT}`] : []),
84
+ ].join("\n");
85
+
86
+ const e2eJob = () =>
87
+ [
88
+ " e2e:",
89
+ " name: e2e (${{ matrix.os }})",
90
+ matrix(SLOW_TIMEOUT_MINUTES),
91
+ " defaults:",
92
+ " run:",
93
+ " shell: bash",
94
+ " steps:",
95
+ CHECKOUT_FULL_STEP,
96
+ SETUP_BUN_STEP,
97
+ INSTALL_STEP,
98
+ ` - run: bun run ${E2E_SCRIPT}`,
99
+ ].join("\n");
100
+
101
+ /** Under the release pack, the tracked, whole-file `.github/workflows/ci.yml`; a hand-written one is refused. */
102
+ export const release: Generator = (ctx) => {
103
+ if (!ctx.config.packs.includes("release")) return { outputs: [], errors: [] };
104
+ const existing = ctx.read(CI_PATH);
105
+ if (existing !== null && lines(existing)[0] !== CI_HEADER) {
106
+ return { outputs: [], errors: [`${CI_PATH} is hand-written; delete it so repo sync can own it`] };
107
+ }
108
+ const e2e = hasScript(ctx, E2E_SCRIPT);
109
+ const jobs = [testJob(), commitsJob(), skipsJob(), noNodeJob(e2e), ...(e2e ? [e2eJob()] : [])];
110
+ const content = `${CI_HEADER}\n${PREAMBLE}\n${jobs.join("\n\n")}\n`;
111
+ return { outputs: [{ kind: "file", path: CI_PATH, content, tracked: true, managed: "whole" }], errors: [] };
112
+ };