@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.
- package/CHANGELOG.md +45 -0
- package/LICENSE +21 -0
- package/README.md +0 -3
- package/agents/global-settings/antigravity.json +3 -0
- package/agents/global-settings/claude.json +6 -0
- package/agents/global-settings/codex.toml +2 -0
- package/agents/global-settings/cursor.json +3 -0
- package/agents/global-settings/grok.toml +36 -0
- package/agents/global-settings/manifest.json +36 -0
- package/agents/grok/hooks/orchestrate-parent.json +60 -0
- package/agents/grok/hooks/rtk.json +16 -0
- package/agents/grok/hooks/rulesync.json +3 -0
- package/agents/grok/roles/scout.toml +5 -0
- package/agents/hooks.jsonc +67 -0
- package/agents/mcp-policy.jsonc +16 -0
- package/agents/rules/overview.md +21 -0
- package/agents/settings/claude.json +5 -0
- package/agents/skills/ask/SKILL.md +13 -0
- package/agents/skills/auto/SKILL.md +13 -0
- package/agents/skills/away/SKILL.md +19 -0
- package/agents/skills/blast-radius/SKILL.md +54 -0
- package/agents/skills/bro/SKILL.md +9 -0
- package/agents/skills/browser/SKILL.md +16 -0
- package/agents/skills/clasp/SKILL.md +11 -0
- package/agents/skills/commit/SKILL.md +35 -0
- package/agents/skills/commit-and-push/SKILL.md +9 -0
- package/agents/skills/commit-push/SKILL.md +59 -0
- package/agents/skills/commit-push-deploy/SKILL.md +74 -0
- package/agents/skills/commit-push-release/SKILL.md +75 -0
- package/agents/skills/correct/SKILL.md +39 -0
- package/agents/skills/do/SKILL.md +21 -0
- package/agents/skills/explain/SKILL.md +13 -0
- package/agents/skills/google-workspace/SKILL.md +12 -0
- package/agents/skills/handoff/SKILL.md +81 -0
- package/agents/skills/implement-plan/SKILL.md +29 -0
- package/agents/skills/implement-roadmap/SKILL.md +27 -0
- package/agents/skills/jev/SKILL.md +17 -0
- package/agents/skills/listen/SKILL.md +15 -0
- package/agents/skills/pickup/SKILL.md +58 -0
- package/agents/skills/principle-attack-the-premise/SKILL.md +24 -0
- package/agents/skills/principle-boundary-discipline/SKILL.md +38 -0
- package/agents/skills/principle-build-the-lever/SKILL.md +28 -0
- package/agents/skills/principle-encode-lessons-in-structure/SKILL.md +15 -0
- package/agents/skills/principle-exhaust-the-design-space/SKILL.md +12 -0
- package/agents/skills/principle-experience-first/SKILL.md +22 -0
- package/agents/skills/principle-explain-the-number/SKILL.md +21 -0
- package/agents/skills/principle-fix-root-causes/SKILL.md +24 -0
- package/agents/skills/principle-foundational-thinking/SKILL.md +25 -0
- package/agents/skills/principle-guard-the-context-window/SKILL.md +16 -0
- package/agents/skills/principle-laziness-protocol/SKILL.md +13 -0
- package/agents/skills/principle-make-operations-idempotent/SKILL.md +25 -0
- package/agents/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md +18 -0
- package/agents/skills/principle-minimize-reader-load/SKILL.md +22 -0
- package/agents/skills/principle-model-the-domain/SKILL.md +26 -0
- package/agents/skills/principle-never-block-on-the-human/SKILL.md +17 -0
- package/agents/skills/principle-outcome-oriented-execution/SKILL.md +25 -0
- package/agents/skills/principle-prove-it-works/SKILL.md +25 -0
- package/agents/skills/principle-redesign-from-first-principles/SKILL.md +18 -0
- package/agents/skills/principle-separate-before-serializing-shared-state/SKILL.md +20 -0
- package/agents/skills/principle-sequence-verifiable-units/SKILL.md +22 -0
- package/agents/skills/principle-subtract-before-you-add/SKILL.md +21 -0
- package/agents/skills/principle-test-behavior-not-implementation/SKILL.md +24 -0
- package/agents/skills/principle-type-system-discipline/SKILL.md +17 -0
- package/agents/skills/review-plan/SKILL.md +28 -0
- package/agents/skills/review-roadmap/SKILL.md +28 -0
- package/agents/skills/show-me/SKILL.md +21 -0
- package/agents/skills/speak/SKILL.md +15 -0
- package/agents/skills/su/SKILL.md +11 -0
- package/agents/skills/teach/SKILL.md +25 -0
- package/agents/skills/tmp/SKILL.md +54 -0
- package/agents/skills/typescript-best-practices/SKILL.md +32 -0
- package/agents/skills/typescript-best-practices/references/patterns.md +328 -0
- package/agents/skills/unslop/SKILL.md +62 -0
- package/agents/skills/write-plan/SKILL.md +33 -0
- package/agents/skills/write-roadmap/SKILL.md +66 -0
- package/agents/subagents/implementer-deep.md +57 -0
- package/agents/subagents/implementer-fast.md +57 -0
- package/agents/subagents/orchestrator.md +43 -0
- package/agents/subagents/researcher.md +47 -0
- package/agents/subagents/scout.md +44 -0
- package/bin/repo.ts +4 -0
- package/cli/actionlint-default.test.ts +38 -0
- package/cli/actionlint-download.test.ts +162 -0
- package/cli/actionlint-real.test.ts +33 -0
- package/cli/actionlint.constants.ts +43 -0
- package/cli/actionlint.test.ts +108 -0
- package/cli/actionlint.ts +178 -0
- package/cli/bin.test.ts +54 -0
- package/cli/bin.ts +40 -0
- package/cli/check-changelog.test.ts +40 -0
- package/cli/check-changelog.ts +12 -0
- package/cli/check-comments.test.ts +157 -0
- package/cli/check-comments.ts +123 -0
- package/cli/check-env-modules.test.ts +85 -0
- package/cli/check-env-modules.ts +56 -0
- package/cli/check-magic.test.ts +139 -0
- package/cli/check-magic.ts +120 -0
- package/cli/check-manifests.constants.ts +4 -0
- package/cli/check-manifests.test.ts +210 -0
- package/cli/check-manifests.ts +71 -0
- package/cli/check-nightly.test.ts +103 -0
- package/cli/check-nightly.ts +37 -0
- package/cli/check-ratchets-missing.test.ts +46 -0
- package/cli/check-ratchets-mutation.test.ts +49 -0
- package/cli/check-ratchets.constants.ts +20 -0
- package/cli/check-ratchets.test.ts +190 -0
- package/cli/check-ratchets.ts +193 -0
- package/cli/check-rejections.test.ts +220 -0
- package/cli/check-rejections.ts +153 -0
- package/cli/check-reports.ts +37 -0
- package/cli/check-tsconfig.test.ts +507 -0
- package/cli/check-tsconfig.ts +205 -0
- package/cli/commands.test.ts +114 -0
- package/cli/commands.ts +218 -0
- package/cli/commit-changelog.ts +27 -0
- package/cli/commit-msg-changelog.test.ts +93 -0
- package/cli/commit-msg-range.test.ts +126 -0
- package/cli/commit-msg-red-proof.test.ts +147 -0
- package/cli/commit-msg-repo.test.ts +153 -0
- package/cli/commit-msg.test.ts +101 -0
- package/cli/commit-msg.ts +194 -0
- package/cli/commit-range.constants.ts +17 -0
- package/cli/commit-range.ts +57 -0
- package/cli/config-knip.test.ts +70 -0
- package/cli/config-nightly.test.ts +84 -0
- package/cli/config-tdd.test.ts +46 -0
- package/cli/config.test.ts +576 -0
- package/cli/config.ts +261 -0
- package/cli/conventional.ts +9 -0
- package/cli/coverage-config.ts +62 -0
- package/cli/coverage.constants.ts +6 -0
- package/cli/coverage.test.ts +199 -0
- package/cli/coverage.ts +127 -0
- package/cli/env.constants.ts +10 -0
- package/cli/env.test.ts +56 -0
- package/cli/env.ts +72 -0
- package/cli/fixtures/base-config.ts +13 -0
- package/cli/fixtures/gate-world.ts +48 -0
- package/cli/fixtures/knip-run.ts +38 -0
- package/cli/fixtures/mutate-fakes.ts +137 -0
- package/cli/fixtures/planted-repo.ts +50 -0
- package/cli/fixtures/publish-world.ts +71 -0
- package/cli/fixtures/red-proof-repo.ts +72 -0
- package/cli/fixtures/release-context.ts +15 -0
- package/cli/fixtures/release-world.ts +111 -0
- package/cli/fixtures/stub-bin.ts +40 -0
- package/cli/guard-diagnostics.test.ts +157 -0
- package/cli/guard-diagnostics.ts +195 -0
- package/cli/init.test.ts +487 -0
- package/cli/init.ts +275 -0
- package/cli/knip-check-ignores.test.ts +106 -0
- package/cli/knip-check-real.test.ts +65 -0
- package/cli/knip-check.constants.ts +46 -0
- package/cli/knip-check.test.ts +114 -0
- package/cli/knip-check.ts +169 -0
- package/cli/knip-schema.ts +67 -0
- package/cli/known-failures.constants.ts +22 -0
- package/cli/known-failures.test.ts +138 -0
- package/cli/known-failures.ts +131 -0
- package/cli/lint/as-needs-reason.ts +41 -0
- package/cli/lint/boundaries.ts +73 -0
- package/cli/lint/class-file.ts +39 -0
- package/cli/lint/config-file.ts +32 -0
- package/cli/lint/entry-file.ts +54 -0
- package/cli/lint/env-in-config.ts +66 -0
- package/cli/lint/filename-matches-export.ts +63 -0
- package/cli/lint/index-file.ts +22 -0
- package/cli/lint/index.ts +47 -0
- package/cli/lint/is-reason.ts +6 -0
- package/cli/lint/manifest.ts +46 -0
- package/cli/lint/names.ts +59 -0
- package/cli/lint/no-commented-out-code.ts +105 -0
- package/cli/lint/no-mock-of-subject.ts +64 -0
- package/cli/lint/no-module-side-effects.ts +94 -0
- package/cli/lint/no-module-state.ts +58 -0
- package/cli/lint/no-side-effect-import.ts +17 -0
- package/cli/lint/no-v8-ignore.ts +18 -0
- package/cli/lint/script-rules.ts +9 -0
- package/cli/lint/skip-on-platform.constants.ts +8 -0
- package/cli/lint/skip-on-platform.ts +118 -0
- package/cli/lint/todo-needs-id.ts +63 -0
- package/cli/lint/types-file.ts +28 -0
- package/cli/lint-schema.ts +32 -0
- package/cli/magic/fixtures/magic-repo.ts +64 -0
- package/cli/magic/magic-hit.ts +2 -0
- package/cli/magic/magic-nodes.ts +44 -0
- package/cli/magic/magic-numbers.test.ts +99 -0
- package/cli/magic/magic-numbers.ts +117 -0
- package/cli/magic/magic-strings.test.ts +38 -0
- package/cli/magic/magic-strings.ts +81 -0
- package/cli/main.test.ts +853 -0
- package/cli/main.ts +292 -0
- package/cli/mutate-args.ts +79 -0
- package/cli/mutate-check.test.ts +147 -0
- package/cli/mutate-deps.ts +42 -0
- package/cli/mutate-edges.test.ts +150 -0
- package/cli/mutate-files.test.ts +100 -0
- package/cli/mutate-files.ts +31 -0
- package/cli/mutate-floors.test.ts +91 -0
- package/cli/mutate-floors.ts +104 -0
- package/cli/mutate-real-deps.ts +57 -0
- package/cli/mutate-real.test.ts +96 -0
- package/cli/mutate-report.ts +96 -0
- package/cli/mutate-run.ts +128 -0
- package/cli/mutate-shards.ts +64 -0
- package/cli/mutate.constants.ts +67 -0
- package/cli/mutate.test.ts +182 -0
- package/cli/mutate.ts +64 -0
- package/cli/nested-vitest-flags.ts +8 -0
- package/cli/nightly-config.constants.ts +14 -0
- package/cli/nightly-config.ts +62 -0
- package/cli/prompt.test.ts +202 -0
- package/cli/prompt.ts +1 -0
- package/cli/red-proof-edges.test.ts +78 -0
- package/cli/red-proof-failures.test.ts +82 -0
- package/cli/red-proof-tree.test.ts +157 -0
- package/cli/red-proof-verdict.test.ts +44 -0
- package/cli/red-proof.constants.ts +14 -0
- package/cli/red-proof.ts +202 -0
- package/cli/release/changelog-parse.ts +83 -0
- package/cli/release/changelog.test.ts +141 -0
- package/cli/release/changelog.ts +106 -0
- package/cli/release/gate-refusals.test.ts +85 -0
- package/cli/release/gate.test.ts +63 -0
- package/cli/release/gate.ts +70 -0
- package/cli/release/git.ts +66 -0
- package/cli/release/github.test.ts +128 -0
- package/cli/release/github.ts +46 -0
- package/cli/release/list-dir.test.ts +11 -0
- package/cli/release/list-dir.ts +6 -0
- package/cli/release/main.test.ts +48 -0
- package/cli/release/main.ts +51 -0
- package/cli/release/manifest.test.ts +24 -0
- package/cli/release/manifest.ts +34 -0
- package/cli/release/pack.test.ts +107 -0
- package/cli/release/pack.ts +76 -0
- package/cli/release/plan.ts +127 -0
- package/cli/release/publish-run.test.ts +98 -0
- package/cli/release/publish.test.ts +127 -0
- package/cli/release/publish.ts +125 -0
- package/cli/release/refuse.ts +5 -0
- package/cli/release/release-messages.constants.ts +96 -0
- package/cli/release/release-refusals.test.ts +149 -0
- package/cli/release/release.constants.ts +128 -0
- package/cli/release/release.test.ts +199 -0
- package/cli/release/release.ts +145 -0
- package/cli/release/version.test.ts +118 -0
- package/cli/release/version.ts +69 -0
- package/cli/release/wait.test.ts +101 -0
- package/cli/release/wait.ts +95 -0
- package/cli/script-diagnostics.test.ts +76 -0
- package/cli/script-diagnostics.ts +62 -0
- package/cli/scripts/board.ts +519 -0
- package/cli/scripts/browser.ts +994 -0
- package/cli/scripts/change-set.ts +105 -0
- package/cli/scripts/check-globals.ts +199 -0
- package/cli/scripts/ci-skips.constants.ts +17 -0
- package/cli/scripts/ci-skips.ts +118 -0
- package/cli/scripts/ci-wait-run.ts +28 -0
- package/cli/scripts/ci-wait.constants.ts +44 -0
- package/cli/scripts/ci-wait.ts +175 -0
- package/cli/scripts/ci.constants.ts +15 -0
- package/cli/scripts/ci.ts +208 -0
- package/cli/scripts/clean.ts +363 -0
- package/cli/scripts/comments-step.ts +15 -0
- package/cli/scripts/doctor.ts +828 -0
- package/cli/scripts/emit-agent-shell-tools.ts +91 -0
- package/cli/scripts/emit-mcp-policy.ts +402 -0
- package/cli/scripts/folder-map.ts +353 -0
- package/cli/scripts/gate-fat-tools.ts +1095 -0
- package/cli/scripts/gitignore-guard.ts +338 -0
- package/cli/scripts/google-workspace.ts +779 -0
- package/cli/scripts/hook-command.ts +72 -0
- package/cli/scripts/hook-input.ts +178 -0
- package/cli/scripts/hook-io.constants.ts +5 -0
- package/cli/scripts/hook-io.ts +46 -0
- package/cli/scripts/hooks-union.ts +469 -0
- package/cli/scripts/interactive.ts +30 -0
- package/cli/scripts/jev.ts +420 -0
- package/cli/scripts/job.ts +934 -0
- package/cli/scripts/jsonc.ts +9 -0
- package/cli/scripts/lessons.ts +564 -0
- package/cli/scripts/lint-baseline.ts +233 -0
- package/cli/scripts/lint-main.ts +24 -0
- package/cli/scripts/lint-skips.ts +139 -0
- package/cli/scripts/lint.constants.ts +6 -0
- package/cli/scripts/lint.ts +142 -0
- package/cli/scripts/listen.ts +962 -0
- package/cli/scripts/managed-text.ts +47 -0
- package/cli/scripts/orchestrate-parent.ts +1155 -0
- package/cli/scripts/parse-need.ts +81 -0
- package/cli/scripts/persist-handoff.ts +228 -0
- package/cli/scripts/persist-implementation.ts +276 -0
- package/cli/scripts/persist-plan.ts +888 -0
- package/cli/scripts/persist-roadmap.ts +478 -0
- package/cli/scripts/persist-tmp.ts +177 -0
- package/cli/scripts/persist.ts +54 -0
- package/cli/scripts/phase-files.ts +121 -0
- package/cli/scripts/prompt.ts +38 -0
- package/cli/scripts/purge-global-mcps.ts +313 -0
- package/cli/scripts/rejections-step.ts +37 -0
- package/cli/scripts/rtk-grok.ts +102 -0
- package/cli/scripts/scrub-spawn-prompt.ts +103 -0
- package/cli/scripts/scrub-text-lib.ts +58 -0
- package/cli/scripts/session-map.ts +69 -0
- package/cli/scripts/setup.ts +862 -0
- package/cli/scripts/skill-conflicts.ts +82 -0
- package/cli/scripts/speak.ts +370 -0
- package/cli/scripts/strategy.ts +168 -0
- package/cli/scripts/sync-global-settings.ts +316 -0
- package/cli/scripts/turn-steps.ts +249 -0
- package/cli/scripts/turn.ts +157 -0
- package/cli/scripts/typecheck-step.ts +43 -0
- package/cli/scripts/vscode-tasks.ts +175 -0
- package/cli/scripts/workspace-root.ts +160 -0
- package/cli/scripts/worktree.ts +806 -0
- package/cli/spawn.test.ts +34 -0
- package/cli/spawn.ts +27 -0
- package/cli/staged-change.constants.ts +11 -0
- package/cli/staged-change.ts +79 -0
- package/cli/stryker-preload.test.ts +50 -0
- package/cli/stryker-preload.ts +9 -0
- package/cli/sync/agents/frontmatter.test.ts +197 -0
- package/cli/sync/agents/frontmatter.ts +140 -0
- package/cli/sync/agents/hooks.test.ts +517 -0
- package/cli/sync/agents/hooks.ts +401 -0
- package/cli/sync/agents/rules.test.ts +229 -0
- package/cli/sync/agents/rules.ts +84 -0
- package/cli/sync/agents/sources.test.ts +375 -0
- package/cli/sync/agents/sources.ts +244 -0
- package/cli/sync/agents/subagents.test.ts +149 -0
- package/cli/sync/agents/subagents.ts +158 -0
- package/cli/sync/agents-text.test.ts +31 -0
- package/cli/sync/agents-text.ts +36 -0
- package/cli/sync/fixtures/copied.README.md +35 -0
- package/cli/sync/fixtures/copied.tgz +0 -0
- package/cli/sync/fixtures/maps/golden/INDEX.md +22 -0
- package/cli/sync/fixtures/maps-ws.tgz +0 -0
- package/cli/sync/git-files.test.ts +340 -0
- package/cli/sync/git-files.ts +197 -0
- package/cli/sync/git-hooks.test.ts +94 -0
- package/cli/sync/golden.test.ts +15 -0
- package/cli/sync/hooks-mode.test.ts +99 -0
- package/cli/sync/hosts.ts +28 -0
- package/cli/sync/knip-config.constants.ts +4 -0
- package/cli/sync/knip-config.test.ts +147 -0
- package/cli/sync/knip-config.ts +108 -0
- package/cli/sync/manifest.test.ts +78 -0
- package/cli/sync/manifest.ts +60 -0
- package/cli/sync/maps.test.ts +304 -0
- package/cli/sync/maps.ts +297 -0
- package/cli/sync/migrate.test.ts +2000 -0
- package/cli/sync/migrate.ts +1104 -0
- package/cli/sync/nightly-jobs.test.ts +63 -0
- package/cli/sync/nightly.constants.ts +48 -0
- package/cli/sync/nightly.test.ts +100 -0
- package/cli/sync/nightly.ts +75 -0
- package/cli/sync/outputs.test.ts +351 -0
- package/cli/sync/outputs.ts +168 -0
- package/cli/sync/oxlint-config.test.ts +178 -0
- package/cli/sync/oxlint-config.ts +168 -0
- package/cli/sync/release-jobs.test.ts +29 -0
- package/cli/sync/release-reports.test.ts +31 -0
- package/cli/sync/release-workflow.constants.ts +61 -0
- package/cli/sync/release-workflow.test.ts +192 -0
- package/cli/sync/release-workflow.ts +86 -0
- package/cli/sync/release.constants.ts +93 -0
- package/cli/sync/release.test.ts +187 -0
- package/cli/sync/release.ts +112 -0
- package/cli/sync/repo-dir.test.ts +371 -0
- package/cli/sync/repo-dir.ts +205 -0
- package/cli/sync/standards.constants.ts +8 -0
- package/cli/sync/standards.test.ts +206 -0
- package/cli/sync/standards.ts +179 -0
- package/cli/sync/stock-history.json +2796 -0
- package/cli/sync/stock-history.test.ts +66 -0
- package/cli/sync/stock-history.ts +84 -0
- package/cli/sync/stock.test.ts +47 -0
- package/cli/sync/stock.ts +24 -0
- package/cli/sync/trees.test.ts +621 -0
- package/cli/sync/trees.ts +280 -0
- package/cli/sync/validate.test.ts +299 -0
- package/cli/sync/validate.ts +402 -0
- package/cli/sync/workspace.test.ts +180 -0
- package/cli/sync/workspace.ts +138 -0
- package/cli/sync/write.test.ts +341 -0
- package/cli/sync/write.ts +231 -0
- package/cli/sync.test.ts +1495 -0
- package/cli/sync.ts +360 -0
- package/cli/tdd-scope.test.ts +36 -0
- package/cli/tdd-scope.ts +41 -0
- package/cli/test-files.ts +16 -0
- package/cli/ts-program.ts +69 -0
- package/cli/tsconfig.constants.ts +8 -0
- package/cli/workerstack-config.types.ts +21 -0
- package/dist/cli/env.d.ts +10 -0
- package/dist/cli/env.js +64 -0
- package/dist/presets/temp-root-env.d.ts +10 -0
- package/dist/presets/temp-root-env.js +20 -0
- package/dist/presets/temp-root.constants.d.ts +6 -0
- package/dist/presets/temp-root.constants.js +6 -0
- package/dist/presets/temp-root.d.ts +2 -0
- package/dist/presets/temp-root.js +60 -0
- package/dist/presets/vitest.constants.d.ts +16 -0
- package/dist/presets/vitest.constants.js +16 -0
- package/dist/presets/vitest.d.ts +11 -0
- package/dist/presets/vitest.js +67 -0
- package/dist/presets/workers-plugin.d.ts +10 -0
- package/dist/presets/workers-plugin.js +54 -0
- package/package.json +141 -4
- package/references/INDEX.md +100 -0
- package/references/browser.md +66 -0
- package/references/cascade.md +15 -0
- package/references/clasp.md +21 -0
- package/references/cloudflare.md +36 -0
- package/references/google-workspace.md +79 -0
- package/references/hooks.md +103 -0
- package/references/implementation-format.md +119 -0
- package/references/jev.md +56 -0
- package/references/lessons.md +73 -0
- package/references/listen.md +80 -0
- package/references/map.md +107 -0
- package/references/output.md +47 -0
- package/references/plan-format.md +264 -0
- package/references/review-loop.md +48 -0
- package/references/roadmap-format.md +104 -0
- package/references/rtk.md +27 -0
- package/references/scope.md +76 -0
- package/references/setup.md +42 -0
- package/references/speak.md +59 -0
- package/tsconfig/base.json +28 -0
- package/tsconfig/bun.d.ts +1 -0
- package/tsconfig/bun.json +8 -0
- package/tsconfig/reset.d.ts +1 -0
- package/tsconfig/strict.json +12 -0
- package/tsconfig/web.json +7 -0
- package/tsconfig/worker.d.ts +1 -0
- package/tsconfig/worker.json +8 -0
package/cli/env.test.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { childEnv, readEnv } from "./env.ts";
|
|
3
|
+
|
|
4
|
+
/** The message of the error `run` throws, empty when it throws nothing. */
|
|
5
|
+
function thrownBy(run: () => unknown): string {
|
|
6
|
+
try {
|
|
7
|
+
run();
|
|
8
|
+
} catch (error) {
|
|
9
|
+
return error instanceof Error ? error.message : "";
|
|
10
|
+
}
|
|
11
|
+
return "";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe("readEnv", () => {
|
|
15
|
+
it("parses the given source", () => {
|
|
16
|
+
expect(readEnv({ HOME: "/home/x", PATH: "/bin" })).toMatchObject({ HOME: "/home/x", PATH: "/bin" });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("leaves unset variables undefined", () => {
|
|
20
|
+
expect(readEnv({}).WORKSPACE_ACCOUNT).toBeUndefined();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("reads process.env by default", () => {
|
|
24
|
+
vi.stubEnv("WORKSPACE_ACCOUNT", "me@example.com");
|
|
25
|
+
expect(readEnv().WORKSPACE_ACCOUNT).toBe("me@example.com");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("throws one line per bad variable, naming it", () => {
|
|
29
|
+
const bad: Record<string, unknown> = { PATH: 1, HOME: 2 };
|
|
30
|
+
const lines = thrownBy(() => readEnv(bad)).split("\n");
|
|
31
|
+
expect(lines).toHaveLength(2);
|
|
32
|
+
expect(lines.every((line) => line.startsWith("repo: environment: "))).toBe(true);
|
|
33
|
+
expect(lines.some((line) => line.startsWith("repo: environment: HOME: "))).toBe(true);
|
|
34
|
+
expect(lines.some((line) => line.startsWith("repo: environment: PATH: "))).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe("childEnv", () => {
|
|
39
|
+
it("copies process.env and applies overrides", () => {
|
|
40
|
+
vi.stubEnv("CHILD_ENV_KEPT", "kept");
|
|
41
|
+
vi.stubEnv("CHILD_ENV_REPLACED", "old");
|
|
42
|
+
const env = childEnv({ CHILD_ENV_REPLACED: "new", CHILD_ENV_ADDED: "added" });
|
|
43
|
+
expect(env).toMatchObject({ CHILD_ENV_KEPT: "kept", CHILD_ENV_REPLACED: "new", CHILD_ENV_ADDED: "added" });
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("drops an override that is undefined", () => {
|
|
47
|
+
vi.stubEnv("CHILD_ENV_GONE", "here");
|
|
48
|
+
expect("CHILD_ENV_GONE" in childEnv({ CHILD_ENV_GONE: undefined })).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("returns a copy, not process.env", () => {
|
|
52
|
+
const env = childEnv();
|
|
53
|
+
env.CHILD_ENV_COPY = "x";
|
|
54
|
+
expect("CHILD_ENV_COPY" in childEnv()).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
});
|
package/cli/env.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/** Every variable this repo's code reads, sorted; add a name here to read it. */
|
|
4
|
+
const ENV_VARIABLES = [
|
|
5
|
+
"CI",
|
|
6
|
+
"CLAUDE_EFFORT",
|
|
7
|
+
"CLAUDE_PROJECT_DIR",
|
|
8
|
+
"CLAUDE_SESSION_ID",
|
|
9
|
+
"ELEVENLABS_API_KEY",
|
|
10
|
+
"ELEVENLABS_MODEL_ID",
|
|
11
|
+
"ELEVENLABS_VOICE_ID",
|
|
12
|
+
"GITHUB_ACTIONS",
|
|
13
|
+
"GIT_CONFIG_GLOBAL",
|
|
14
|
+
"GIT_DIR",
|
|
15
|
+
"GROK_HOOK_EVENT",
|
|
16
|
+
"GROK_SESSION_ID",
|
|
17
|
+
"GROK_WORKSPACE_ROOT",
|
|
18
|
+
"HOME",
|
|
19
|
+
"LISTEN_DEVICE",
|
|
20
|
+
"LISTEN_FFMPEG_FORMAT",
|
|
21
|
+
"LISTEN_MODEL",
|
|
22
|
+
"LISTEN_SECONDS",
|
|
23
|
+
"LISTEN_STT",
|
|
24
|
+
"LISTEN_WHISPER_MODEL",
|
|
25
|
+
"NODE_PATH",
|
|
26
|
+
"OPENAI_API_KEY",
|
|
27
|
+
"OPENAI_TTS_MODEL",
|
|
28
|
+
"OPENAI_TTS_VOICE",
|
|
29
|
+
"PATH",
|
|
30
|
+
"PATHEXT",
|
|
31
|
+
"REPO_BOARD_HOME",
|
|
32
|
+
"REPO_BOARD_PROVIDER",
|
|
33
|
+
"REPO_BOARD_SESSION",
|
|
34
|
+
"REPO_BROWSER",
|
|
35
|
+
"REPO_E2E_KEEP",
|
|
36
|
+
"REPO_E2E_ROOT",
|
|
37
|
+
"REPO_E2E_TARBALL",
|
|
38
|
+
"SPEAK_PROVIDER",
|
|
39
|
+
"TURN_RECORD",
|
|
40
|
+
"TYPESAFE_API_KEY",
|
|
41
|
+
"USERPROFILE",
|
|
42
|
+
"WHISPER_MODEL",
|
|
43
|
+
"WORKSPACE_ACCOUNT",
|
|
44
|
+
] as const;
|
|
45
|
+
|
|
46
|
+
export type Env = { [Name in (typeof ENV_VARIABLES)[number]]?: string | undefined };
|
|
47
|
+
|
|
48
|
+
/** Each variable is an optional string, unless a command cannot run without it. */
|
|
49
|
+
function envSchema(): z.ZodType<Env, Record<string, unknown>> {
|
|
50
|
+
const text = z.string().optional();
|
|
51
|
+
return z.object(Object.fromEntries(ENV_VARIABLES.map((name) => [name, text])));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** `repo: environment: <NAME>: <issue>`, one line per bad variable. */
|
|
55
|
+
function describe(error: z.ZodError): string {
|
|
56
|
+
return error.issues.map((issue) => `repo: environment: ${issue.path.join(".")}: ${issue.message}`).join("\n");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The environment parsed against the schema; throws a line per bad variable. */
|
|
60
|
+
export function readEnv(source: Record<string, unknown> = process.env): Env {
|
|
61
|
+
const parsed = envSchema().safeParse(source);
|
|
62
|
+
if (!parsed.success) throw new Error(describe(parsed.error));
|
|
63
|
+
return parsed.data;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** A copy of the environment with `overrides` applied and every undefined value dropped, for spawn. */
|
|
67
|
+
export function childEnv(overrides: Record<string, string | undefined> = {}): Record<string, string> {
|
|
68
|
+
const merged = { ...process.env, ...overrides };
|
|
69
|
+
return Object.fromEntries(
|
|
70
|
+
Object.entries(merged).filter((entry): entry is [string, string] => entry[1] !== undefined),
|
|
71
|
+
);
|
|
72
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { DEFAULT_COMMIT, type ResolvedConfig } from "../config.ts";
|
|
2
|
+
import { DEFAULT_NIGHTLY } from "../nightly-config.ts";
|
|
3
|
+
import { DEFAULT_COVERAGE } from "../coverage-config.ts";
|
|
4
|
+
|
|
5
|
+
/** The resolved-config keys a test rarely varies: the default commit limit, no lint.skip, no knip.ignore and the default coverage paths. */
|
|
6
|
+
export const BASE_CONFIG: Pick<ResolvedConfig, "commit" | "lint" | "knip" | "coverage" | "nightly" | "tdd"> = {
|
|
7
|
+
commit: DEFAULT_COMMIT,
|
|
8
|
+
lint: { skip: [] },
|
|
9
|
+
knip: { ignore: [] },
|
|
10
|
+
coverage: DEFAULT_COVERAGE,
|
|
11
|
+
nightly: DEFAULT_NIGHTLY,
|
|
12
|
+
tdd: false,
|
|
13
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { gate } from "../release/gate.ts";
|
|
2
|
+
import { spawnRunner } from "../scripts/ci-wait-run.ts";
|
|
3
|
+
import type { Runner } from "../scripts/ci-wait.ts";
|
|
4
|
+
import { commit, fakeGh, gitIn, NAME, type Behavior, type World } from "./release-world.ts";
|
|
5
|
+
|
|
6
|
+
const CHANGELOG = (version: string): string =>
|
|
7
|
+
`# Changelog\n\n## [Unreleased]\n\n## [${version}] - 2026-11-09\n\n### Added\n\n- x\n\n## [0.1.0] - 2026-10-01\n\n### Added\n\n- first\n`;
|
|
8
|
+
|
|
9
|
+
/** A gh that answers `repo view` with `branch` as the default branch (a failure for null) and the rest as the release world's. */
|
|
10
|
+
export function ghFor(branch: string | null, behavior: Behavior = {}, calls: string[] = []): Runner {
|
|
11
|
+
const rest = fakeGh(behavior);
|
|
12
|
+
return (args, cwd) => {
|
|
13
|
+
calls.push(args.join(" "));
|
|
14
|
+
if (args[0] !== "repo") return rest(args, cwd);
|
|
15
|
+
const body = JSON.stringify({ defaultBranchRef: { name: branch } });
|
|
16
|
+
return branch === null ? { code: 1, stdout: "", stderr: "not found" } : { code: 0, stdout: body, stderr: "" };
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** What a release commit holds: the version in package.json, the tag's version, and whether it reaches origin. */
|
|
21
|
+
export type Tagged = { version: string; tagVersion?: string; push?: boolean; changelog?: boolean };
|
|
22
|
+
|
|
23
|
+
/** Commits `version` with its changelog section (unless `changelog` is false), tags it, and returns the short sha. */
|
|
24
|
+
export function tagRelease(w: World, { version, tagVersion = version, push = true, changelog = true }: Tagged): string {
|
|
25
|
+
const files = { "package.json": { name: NAME, version }, ...(changelog ? { "CHANGELOG.md": CHANGELOG(version) } : {}) };
|
|
26
|
+
commit(w, `chore: release ${version}`, files, push);
|
|
27
|
+
gitIn(w.work, "tag", "-a", `${NAME}@${tagVersion}`, "-m", "t");
|
|
28
|
+
return gitIn(w.work, "rev-parse", "--short=7", "HEAD");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Runs `repo release gate` in the world's checkout with fakes of gh, the clock and the sleep. */
|
|
32
|
+
export async function runGate(w: World, argv: string[], runner: Runner = ghFor("main")) {
|
|
33
|
+
const out: string[] = [];
|
|
34
|
+
const err: string[] = [];
|
|
35
|
+
let clock = 0;
|
|
36
|
+
const code = await gate(argv, {
|
|
37
|
+
cwd: w.work,
|
|
38
|
+
stdout: (s) => out.push(s),
|
|
39
|
+
stderr: (s) => err.push(s),
|
|
40
|
+
git: spawnRunner("git"),
|
|
41
|
+
gh: runner,
|
|
42
|
+
now: () => clock,
|
|
43
|
+
sleep: (ms) => ((clock += ms), Promise.resolve()),
|
|
44
|
+
});
|
|
45
|
+
return { code, out: out.join(""), err: err.join("") };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const refused = (line: string) => ({ code: 1, out: "", err: `repo release gate: ${line}\n` });
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import type { KnipRun } from "../knip-check.ts";
|
|
5
|
+
import type { Workspace } from "../sync/workspace.ts";
|
|
6
|
+
|
|
7
|
+
const dirs: string[] = [];
|
|
8
|
+
|
|
9
|
+
/** Removes every root `knipRoot` made; call it from an `afterEach`. */
|
|
10
|
+
export function removeKnipRoots(): void {
|
|
11
|
+
for (const dir of dirs.splice(0)) fs.rmSync(dir, { recursive: true, force: true });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** A temp root holding `files` (path to text) and a manifest declaring `dependencies`, as its workspace. */
|
|
15
|
+
export function knipRoot(files: Record<string, string>, dependencies: Record<string, string> = {}): Workspace {
|
|
16
|
+
const root = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), "workerstack-knip-")));
|
|
17
|
+
dirs.push(root);
|
|
18
|
+
const manifest = { name: "app", dependencies };
|
|
19
|
+
const all = { "package.json": JSON.stringify({ ...manifest, type: "module" }), ...files };
|
|
20
|
+
for (const [rel, text] of Object.entries(all)) {
|
|
21
|
+
fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true });
|
|
22
|
+
fs.writeFileSync(path.join(root, rel), text);
|
|
23
|
+
}
|
|
24
|
+
return { root, manifest, packages: [] };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** One knip json report: per file, the issues by type. */
|
|
28
|
+
export const report = (...issues: Record<string, unknown>[]): string => JSON.stringify({ issues });
|
|
29
|
+
|
|
30
|
+
/** A runner that answers with `stdout`, `code` and `stderr`, and records the arguments and cwd it was called with. */
|
|
31
|
+
export function answering(stdout: string, code = 0, stderr = ""): KnipRun & { calls: [string[], string][] } {
|
|
32
|
+
const calls: [string[], string][] = [];
|
|
33
|
+
const run = (args: string[], cwd: string) => {
|
|
34
|
+
calls.push([args, cwd]);
|
|
35
|
+
return { code, stdout, stderr };
|
|
36
|
+
};
|
|
37
|
+
return Object.assign(run, { calls });
|
|
38
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import type { Captured } from "../scripts/lint.ts";
|
|
3
|
+
import type { MutateConfig, MutateDeps } from "../mutate-deps.ts";
|
|
4
|
+
|
|
5
|
+
export const SHA = "09ed564aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
|
|
6
|
+
/** The repo root, a native absolute path (`C:\repo` on Windows). */
|
|
7
|
+
export const ROOT = path.resolve("/repo");
|
|
8
|
+
/** A native absolute path under the repo root. */
|
|
9
|
+
export const at = (...parts: string[]): string => path.join(ROOT, ...parts);
|
|
10
|
+
/** A native absolute path outside the repo, where a test asks for its report. */
|
|
11
|
+
export const OUT = path.resolve("/out/m.json");
|
|
12
|
+
export const BASE = path.resolve("/tmp/workerstack-mutate-1");
|
|
13
|
+
export const CLONE = path.join(BASE, "repo");
|
|
14
|
+
export const STRYKER_PKG = path.join(CLONE, "node_modules/@stryker-mutator/core/package.json");
|
|
15
|
+
export const RUNNER_PKG = path.join(CLONE, "node_modules/@stryker-mutator/vitest-runner/package.json");
|
|
16
|
+
const OK: Captured = { code: 0, stdout: "", stderr: "" };
|
|
17
|
+
|
|
18
|
+
export type Sim = {
|
|
19
|
+
tracked: string[];
|
|
20
|
+
dirty: boolean;
|
|
21
|
+
/** The sources of the clone, by root-relative path. */
|
|
22
|
+
sources: Record<string, string>;
|
|
23
|
+
installed: boolean;
|
|
24
|
+
gitFails: string | null;
|
|
25
|
+
installFails: boolean;
|
|
26
|
+
/** What a Stryker run does: its exit, its output and the raw report it leaves. */
|
|
27
|
+
stryker: { code: number; output: string; report: unknown };
|
|
28
|
+
config: MutateConfig | string[];
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const rawReport = (files: Record<string, string[]>) => ({
|
|
32
|
+
files: Object.fromEntries(
|
|
33
|
+
Object.entries(files).map(([file, statuses]) => [
|
|
34
|
+
file,
|
|
35
|
+
{
|
|
36
|
+
mutants: statuses.map((status, i) => ({
|
|
37
|
+
status,
|
|
38
|
+
mutatorName: "ArithmeticOperator",
|
|
39
|
+
location: { start: { line: i + 1, column: 2 } },
|
|
40
|
+
})),
|
|
41
|
+
},
|
|
42
|
+
]),
|
|
43
|
+
),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export function sim(over: Partial<Sim> = {}): Sim {
|
|
47
|
+
return {
|
|
48
|
+
tracked: ["src/a.ts", "src/b.ts", "src/a.test.ts", "src/fixtures/c.ts", "src/types.d.ts", "docs/x.ts"],
|
|
49
|
+
dirty: false,
|
|
50
|
+
sources: { "src/a.ts": "a\n".repeat(30), "src/b.ts": "b\n".repeat(10) },
|
|
51
|
+
installed: true,
|
|
52
|
+
gitFails: null,
|
|
53
|
+
installFails: false,
|
|
54
|
+
stryker: { code: 0, output: "", report: rawReport({ "src/a.ts": ["Killed", "Survived"], "src/b.ts": ["Killed"] }) },
|
|
55
|
+
config: { root: ROOT, mutate: ["src/**/*.ts"], floors: "mutation-floors.json" },
|
|
56
|
+
...over,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export { rawReport };
|
|
61
|
+
|
|
62
|
+
export type Fakes = { deps: MutateDeps; calls: string[]; out: string[]; err: string[]; files: Map<string, string> };
|
|
63
|
+
|
|
64
|
+
type RawReport = { files?: Record<string, unknown> };
|
|
65
|
+
type StrykerConfig = { mutate: string[]; jsonReporter: { fileName: string } };
|
|
66
|
+
|
|
67
|
+
/** The raw report a Stryker run leaves: the planned one, cut to the files the run's config mutates. */
|
|
68
|
+
function leave(state: Sim, files: Map<string, string>, configPath: string): void {
|
|
69
|
+
const config = JSON.parse(files.get(configPath) ?? "{}") /* the config the command wrote */ as StrykerConfig;
|
|
70
|
+
const planned = state.stryker.report as /* the test plants an object */ RawReport;
|
|
71
|
+
const mutated = Object.entries(typeof planned.files === "object" ? planned.files : {}).filter(([file]) => config.mutate.includes(file));
|
|
72
|
+
const planted = typeof planned.files === "object";
|
|
73
|
+
const report = planted ? { files: Object.fromEntries(mutated) } : planned;
|
|
74
|
+
files.set(config.jsonReporter.fileName, JSON.stringify(report));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function gitOf(state: Sim, calls: string[]): MutateDeps["git"] {
|
|
78
|
+
return (_cwd, args) => {
|
|
79
|
+
calls.push(`git ${args.join(" ")}`);
|
|
80
|
+
if (state.gitFails !== null && args.includes(state.gitFails)) return { code: 128, stdout: "", stderr: "boom\n" };
|
|
81
|
+
if (args[0] === "rev-parse") return { ...OK, stdout: `${SHA}\n` };
|
|
82
|
+
if (args[0] === "status") return { ...OK, stdout: state.dirty ? " M src/a.ts\n" : "" };
|
|
83
|
+
if (args[0] === "ls-tree") return { ...OK, stdout: `${state.tracked.join("\n")}\n` };
|
|
84
|
+
return OK;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function runOf(state: Sim, calls: string[], files: Map<string, string>): MutateDeps["run"] {
|
|
89
|
+
return (args, cwd) => {
|
|
90
|
+
calls.push(`bun ${args.join(" ")} @${cwd}`);
|
|
91
|
+
if (args[0] === "install") return state.installFails ? { code: 1, stdout: "", stderr: "lock\n" } : OK;
|
|
92
|
+
leave(state, files, args.at(-1) ?? "");
|
|
93
|
+
return { code: state.stryker.code, stdout: state.stryker.output, stderr: "" };
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function fakes(state: Sim = sim(), packages: string[] = []): Fakes {
|
|
98
|
+
const calls: string[] = [];
|
|
99
|
+
const files = new Map<string, string>();
|
|
100
|
+
const out: string[] = [];
|
|
101
|
+
const err: string[] = [];
|
|
102
|
+
const git = gitOf(state, calls);
|
|
103
|
+
const run = runOf(state, calls, files);
|
|
104
|
+
const deps: MutateDeps = {
|
|
105
|
+
cwd: ROOT,
|
|
106
|
+
config: async () => state.config,
|
|
107
|
+
packages: () => packages,
|
|
108
|
+
git,
|
|
109
|
+
run,
|
|
110
|
+
preload: "/pkg/cli/stryker-preload.ts",
|
|
111
|
+
out: (text) => out.push(text),
|
|
112
|
+
err: (text) => err.push(text),
|
|
113
|
+
fs: {
|
|
114
|
+
mkdtemp: (prefix) => {
|
|
115
|
+
calls.push(`mkdtemp ${prefix}`);
|
|
116
|
+
return BASE;
|
|
117
|
+
},
|
|
118
|
+
rm: (target) => {
|
|
119
|
+
calls.push(`rm ${target}`);
|
|
120
|
+
for (const key of [...files.keys()]) if (key.startsWith(target)) files.delete(key);
|
|
121
|
+
},
|
|
122
|
+
read: (file) => {
|
|
123
|
+
if (file === STRYKER_PKG || file === RUNNER_PKG) {
|
|
124
|
+
return state.installed ? JSON.stringify({ bin: { stryker: "bin/stryker.js" } }) : null;
|
|
125
|
+
}
|
|
126
|
+
const source = state.sources[path.relative(CLONE, file).replaceAll(path.sep, "/")];
|
|
127
|
+
return files.get(file) ?? source ?? null;
|
|
128
|
+
},
|
|
129
|
+
write: (file, text) => void files.set(file, text),
|
|
130
|
+
list: (dir) => {
|
|
131
|
+
const names = [...files.keys()].filter((k) => k.startsWith(`${dir}${path.sep}`)).map((k) => k.slice(dir.length + 1));
|
|
132
|
+
return names.length > 0 ? names : null;
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
return { deps, calls, out, err, files };
|
|
137
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { childEnv } from "../env.ts";
|
|
6
|
+
import { toolEnv } from "../scripts/lint.ts";
|
|
7
|
+
|
|
8
|
+
const dirs: string[] = [];
|
|
9
|
+
|
|
10
|
+
/** Removes every repo `tempDir` made; call it from an `afterEach`. */
|
|
11
|
+
export function removePlanted(): void {
|
|
12
|
+
for (const dir of dirs.splice(0)) fs.rmSync(dir, { recursive: true, force: true });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const env = toolEnv(childEnv());
|
|
16
|
+
|
|
17
|
+
/** Runs git in `cwd` with a committer identity and no signing. */
|
|
18
|
+
export const git = (cwd: string, ...args: string[]) =>
|
|
19
|
+
execFileSync("git", ["-c", "user.name=t", "-c", "user.email=t@t", "-c", "commit.gpgsign=false", ...args], {
|
|
20
|
+
cwd,
|
|
21
|
+
env,
|
|
22
|
+
encoding: "utf8",
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/** A new temp dir, removed by `removePlanted`. */
|
|
26
|
+
export function tempDir(): string {
|
|
27
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "check-ratchets-"));
|
|
28
|
+
dirs.push(dir);
|
|
29
|
+
return dir;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Writes `files` (path → text, objects as JSON) under `dir`. */
|
|
33
|
+
export function write(dir: string, files: Record<string, unknown>): void {
|
|
34
|
+
for (const [rel, content] of Object.entries(files)) {
|
|
35
|
+
fs.mkdirSync(path.dirname(path.join(dir, rel)), { recursive: true });
|
|
36
|
+
const text = typeof content === "string" ? content : `${JSON.stringify(content, null, 2)}\n`;
|
|
37
|
+
fs.writeFileSync(path.join(dir, rel), text);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** A repo whose `main` commit holds `files`, with a `feature` branch checked out. */
|
|
42
|
+
export function repoOnMain(files: Record<string, unknown>): string {
|
|
43
|
+
const dir = tempDir();
|
|
44
|
+
git(dir, "init", "-q", "-b", "main");
|
|
45
|
+
write(dir, files);
|
|
46
|
+
git(dir, "add", "-A");
|
|
47
|
+
git(dir, "commit", "-q", "-m", "main");
|
|
48
|
+
git(dir, "checkout", "-q", "-b", "feature");
|
|
49
|
+
return dir;
|
|
50
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { releasePublish } from "../release/publish.ts";
|
|
2
|
+
import { spawnRunner } from "../scripts/ci-wait-run.ts";
|
|
3
|
+
import type { Runner } from "../scripts/ci-wait.ts";
|
|
4
|
+
import { NAME, type World } from "./release-world.ts";
|
|
5
|
+
|
|
6
|
+
const DOC_URL = "https://registry.npmjs.org/@acme%2fapp";
|
|
7
|
+
const ok = (stdout = "") => ({ code: 0, stdout, stderr: "" });
|
|
8
|
+
|
|
9
|
+
/** What the fake registry holds: the package, a listed version, and how many tarball requests answer 404 first or fail. */
|
|
10
|
+
export type Registry = { exists: boolean; listed: boolean; notServed: number; offline?: number; docFails?: number };
|
|
11
|
+
/** What the fake npm does on `publish`: its exit code, and whether the version is listed afterwards. */
|
|
12
|
+
export type Npm = { code: number; lists: boolean };
|
|
13
|
+
export type Options = { ci?: boolean; npm?: Npm; packed?: { name: string; version: string }; bun?: Runner };
|
|
14
|
+
|
|
15
|
+
/** A fetch over `registry` for `version` that records the urls it was asked for. */
|
|
16
|
+
function registryFetch(registry: Registry, version: string, urls: string[]): (url: string) => Promise<Response> {
|
|
17
|
+
return (url) => {
|
|
18
|
+
urls.push(url);
|
|
19
|
+
if (url === DOC_URL && (registry.docFails ?? 0) > 0) {
|
|
20
|
+
registry.docFails = (registry.docFails ?? 0) - 1;
|
|
21
|
+
return Promise.resolve(new Response("down", { status: 503 }));
|
|
22
|
+
}
|
|
23
|
+
if (url === DOC_URL) {
|
|
24
|
+
const body = registry.exists
|
|
25
|
+
? { versions: registry.listed ? { [version]: {} } : { "0.0.1": {} }, "dist-tags": { latest: "0.0.1" } }
|
|
26
|
+
: {};
|
|
27
|
+
return Promise.resolve(new Response(JSON.stringify(body), { status: registry.exists ? 200 : 404 }));
|
|
28
|
+
}
|
|
29
|
+
if ((registry.offline ?? 0) > 0) {
|
|
30
|
+
registry.offline = (registry.offline ?? 0) - 1;
|
|
31
|
+
return Promise.reject(new Error("offline"));
|
|
32
|
+
}
|
|
33
|
+
const served = registry.notServed <= 0;
|
|
34
|
+
registry.notServed -= 1;
|
|
35
|
+
return Promise.resolve(new Response("tgz", { status: served ? 200 : 404 }));
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Runs `release publish` in the world's checkout against fakes of bun, tar, npm and the registry. */
|
|
40
|
+
export async function publish(w: World, argv: string[], registry: Registry, options: Options = {}) {
|
|
41
|
+
const version = (argv[0] ?? "").split("@").pop() ?? "";
|
|
42
|
+
const out: string[] = [];
|
|
43
|
+
const err: string[] = [];
|
|
44
|
+
const npmCalls: string[] = [];
|
|
45
|
+
const urls: string[] = [];
|
|
46
|
+
const sleeps: number[] = [];
|
|
47
|
+
const bunCalls: string[] = [];
|
|
48
|
+
const npm: Runner = (args) => {
|
|
49
|
+
npmCalls.push(args.join(" "));
|
|
50
|
+
registry.listed = registry.listed || (options.npm?.lists ?? true);
|
|
51
|
+
return { code: options.npm?.code ?? 0, stdout: "", stderr: "" };
|
|
52
|
+
};
|
|
53
|
+
let clock = 0;
|
|
54
|
+
const code = await releasePublish(argv, {
|
|
55
|
+
cwd: w.work,
|
|
56
|
+
stdout: (s) => out.push(s),
|
|
57
|
+
stderr: (s) => err.push(s),
|
|
58
|
+
git: spawnRunner("git"),
|
|
59
|
+
bun: options.bun ?? ((args) => (bunCalls.push(args.join(" ")), ok())),
|
|
60
|
+
tar: () => ok(JSON.stringify(options.packed ?? { name: NAME, version })),
|
|
61
|
+
npm,
|
|
62
|
+
fetch: registryFetch(registry, version, urls),
|
|
63
|
+
now: () => clock,
|
|
64
|
+
sleep: (ms) => ((clock += ms), sleeps.push(ms), Promise.resolve()),
|
|
65
|
+
ci: options.ci ?? false,
|
|
66
|
+
tmp: "/tmp",
|
|
67
|
+
mkdtemp: (prefix) => `${prefix}x1`,
|
|
68
|
+
list: () => [`acme-app-${version}.tgz`],
|
|
69
|
+
});
|
|
70
|
+
return { code, out: out.join(""), err: err.join(""), npmCalls, bunCalls, urls, sleeps };
|
|
71
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { git, tempDir, write } from "./planted-repo.ts";
|
|
4
|
+
import type { RedProofResult, VitestRun } from "../red-proof.ts";
|
|
5
|
+
|
|
6
|
+
/** This checkout's installed modules, which a planted repo links so its vitest resolves. */
|
|
7
|
+
export const CHECKOUT_MODULES = path.resolve(import.meta.dirname, "..", "..", "node_modules");
|
|
8
|
+
|
|
9
|
+
/** A test file body that passes (`ok`) or fails (`red`), importing `./x.ts` when `useX` is set. */
|
|
10
|
+
export const PASS = `import { expect, it } from "vitest";\nit("p", () => expect(1).toBe(1));\n`;
|
|
11
|
+
export const FAIL = `import { expect, it } from "vitest";\nit("f", () => expect(1).toBe(2));\n`;
|
|
12
|
+
export const NEEDS_X = `import { expect, it } from "vitest";\nimport { x } from "./x.ts";\nit("x", () => expect(x()).toBe(1));\n`;
|
|
13
|
+
|
|
14
|
+
/** Links the checkout's modules into `dir` so vitest resolves from there. */
|
|
15
|
+
export function linkModules(dir: string): void {
|
|
16
|
+
fs.symlinkSync(CHECKOUT_MODULES, path.join(dir, "node_modules"));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** A planted repo with one commit holding `files` (plus a package.json and a .gitignore), modules linked in. */
|
|
20
|
+
export function plant(files: Record<string, unknown>, rootManifest = true): string {
|
|
21
|
+
const dir = tempDir();
|
|
22
|
+
git(dir, "init", "-q", "-b", "main");
|
|
23
|
+
write(dir, { ".gitignore": "node_modules\n.tmp\n", ...(rootManifest ? { "package.json": { name: "p" } } : {}) });
|
|
24
|
+
write(dir, files);
|
|
25
|
+
linkModules(dir);
|
|
26
|
+
git(dir, "add", "-A");
|
|
27
|
+
git(dir, "commit", "-q", "-m", "base");
|
|
28
|
+
return dir;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A planted repo with no commit yet. */
|
|
32
|
+
export function plantUnborn(files: Record<string, unknown>): string {
|
|
33
|
+
const dir = tempDir();
|
|
34
|
+
git(dir, "init", "-q", "-b", "main");
|
|
35
|
+
write(dir, files);
|
|
36
|
+
git(dir, "add", "-A");
|
|
37
|
+
return dir;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Writes `files` and stages everything. */
|
|
41
|
+
export function stage(dir: string, files: Record<string, unknown>): void {
|
|
42
|
+
write(dir, files);
|
|
43
|
+
git(dir, "add", "-A");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The name-status of the staged change, as `status\tpath` lines. */
|
|
47
|
+
export const staged = (dir: string): string => git(dir, "diff", "--cached", "--name-status", "--no-renames");
|
|
48
|
+
|
|
49
|
+
/** The vitest runner stand-in: calls `onRun` with the cwd and args, writes a report failing the files `fails` names. */
|
|
50
|
+
export function fakeVitest(
|
|
51
|
+
fails: (file: string, cwd: string) => boolean,
|
|
52
|
+
onRun: (cwd: string, args: string[]) => void = () => undefined,
|
|
53
|
+
): VitestRun {
|
|
54
|
+
return (args, cwd) => {
|
|
55
|
+
onRun(cwd, args);
|
|
56
|
+
const output = outputOf(args);
|
|
57
|
+
const files = args.filter((arg) => arg !== "run" && !arg.startsWith("-"));
|
|
58
|
+
const testResults = files.map((file) => ({
|
|
59
|
+
name: path.join(cwd, file),
|
|
60
|
+
status: fails(file, cwd) ? "failed" : "passed",
|
|
61
|
+
}));
|
|
62
|
+
fs.writeFileSync(output, JSON.stringify({ testResults }));
|
|
63
|
+
return { code: testResults.some((r) => r.status === "failed") ? 1 : 0, stderr: "" };
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The problem a refusal carries, or an empty string for a pass. */
|
|
68
|
+
export const problemOf = (result: RedProofResult): string => ("problem" in result ? result.problem : "");
|
|
69
|
+
|
|
70
|
+
/** The `--outputFile=` path vitest was asked to write. */
|
|
71
|
+
export const outputOf = (args: string[]): string =>
|
|
72
|
+
args.filter((arg) => arg.startsWith("--outputFile=")).join("").slice("--outputFile=".length);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { BASE_CONFIG } from "./base-config.ts";
|
|
2
|
+
import type { Pack } from "../config.ts";
|
|
3
|
+
import type { SyncContext } from "../sync/outputs.ts";
|
|
4
|
+
import type { PackageJson } from "../sync/workspace.ts";
|
|
5
|
+
|
|
6
|
+
export const HEADER = "# Generated by repo sync. Do not edit.";
|
|
7
|
+
|
|
8
|
+
export function context(packs: Pack[], manifest: PackageJson = {}, files: Record<string, string> = {}): SyncContext {
|
|
9
|
+
return {
|
|
10
|
+
root: "/repo",
|
|
11
|
+
config: { packs, agents: { hosts: [], skills: [] }, ...BASE_CONFIG },
|
|
12
|
+
workspace: { root: "/repo", manifest, packages: [] },
|
|
13
|
+
read: (rel) => files[rel] ?? null,
|
|
14
|
+
};
|
|
15
|
+
}
|