@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/main.test.ts
ADDED
|
@@ -0,0 +1,853 @@
|
|
|
1
|
+
import { BASE_CONFIG } from "./fixtures/base-config.ts";
|
|
2
|
+
import { execFileSync as runGit } from "node:child_process";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
8
|
+
|
|
9
|
+
const init = vi.hoisted(() => vi.fn());
|
|
10
|
+
const isInteractive = vi.hoisted(() => vi.fn(() => false));
|
|
11
|
+
const select = vi.hoisted(() => vi.fn());
|
|
12
|
+
const confirm = vi.hoisted(() => vi.fn());
|
|
13
|
+
const spinner = vi.hoisted(() => vi.fn(() => ({ start: vi.fn(), stop: vi.fn() })));
|
|
14
|
+
const bailIfCancel = vi.hoisted(() => vi.fn(<T>(v: T) => v));
|
|
15
|
+
const mapCommand = vi.hoisted(() => vi.fn((_cwd: string, dir: string) => `map:${dir}\n`));
|
|
16
|
+
const runSync = vi.hoisted(() =>
|
|
17
|
+
vi.fn(
|
|
18
|
+
async (
|
|
19
|
+
_args: string[],
|
|
20
|
+
_io: { cwd: string; stdout: (s: string) => void; stderr: (s: string) => void },
|
|
21
|
+
): Promise<number> => 0,
|
|
22
|
+
),
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
// Wraps the real readFileSync; the version tests swap one return value.
|
|
26
|
+
const readFileSync = vi.hoisted(() => vi.fn());
|
|
27
|
+
vi.mock("node:fs", async (importOriginal) => {
|
|
28
|
+
const actual = await importOriginal<typeof import("node:fs")>();
|
|
29
|
+
readFileSync.mockImplementation(actual.readFileSync);
|
|
30
|
+
return { ...actual, default: { ...actual, readFileSync }, readFileSync };
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const runCommand = vi.hoisted(() => vi.fn(async (_name: string, _argv: string[]): Promise<number> => 0));
|
|
34
|
+
vi.mock(new URL("./commands.ts", import.meta.url).href, async (importOriginal) => {
|
|
35
|
+
const actual = await importOriginal<typeof import("./commands.ts")>();
|
|
36
|
+
return { ...actual, runCommand };
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
vi.mock(new URL("./init.ts", import.meta.url).href, async (importOriginal) => {
|
|
40
|
+
const actual = await importOriginal<typeof import("./init.ts")>();
|
|
41
|
+
return { ...actual, init };
|
|
42
|
+
});
|
|
43
|
+
vi.mock(new URL("./sync.ts", import.meta.url).href, () => ({ runSync }));
|
|
44
|
+
vi.mock(new URL("./scripts/folder-map.ts", import.meta.url).href, () => ({ mapCommand }));
|
|
45
|
+
vi.mock(new URL("./prompt.ts", import.meta.url).href, () => ({
|
|
46
|
+
isInteractive,
|
|
47
|
+
select,
|
|
48
|
+
confirm,
|
|
49
|
+
spinner,
|
|
50
|
+
bailIfCancel,
|
|
51
|
+
}));
|
|
52
|
+
import { APPLY_GLOBALS_CONFIRM, parseArgv, start } from "./main.ts";
|
|
53
|
+
import { standards } from "./sync/standards.ts";
|
|
54
|
+
|
|
55
|
+
/** A resolved config's agents with no host and no skill. */
|
|
56
|
+
const NO_AGENTS = { hosts: [], skills: [] };
|
|
57
|
+
|
|
58
|
+
const USAGE = `Usage: repo [command]
|
|
59
|
+
|
|
60
|
+
Default (no command): TTY menu; else init — full setup
|
|
61
|
+
|
|
62
|
+
Commands:
|
|
63
|
+
init Full setup: write workerstack.config.ts and package.json, bun install (whose postinstall runs repo sync), repo yolo
|
|
64
|
+
check Tsconfig bases, manifests, disable comments, lint.skip entries, generated files current, lint within .repo/lint-baseline.json, knip, actionlint on workflows, and floors, known failures and the baseline against main
|
|
65
|
+
sync Write the committed generated files from workerstack.config.ts (--check: report drift; --postinstall: never fail)
|
|
66
|
+
map [dir] Code-only folder map (git-tracked, no files); dir for depth
|
|
67
|
+
job Compressed search, exec, slice, fetch and test dumps (--job search|exec|slice|fetch|test)
|
|
68
|
+
worktree Managed worktrees: add, list, finish, remove
|
|
69
|
+
persist Save a plan, roadmap, handoff, implementation or tmp body from stdin (persist <kind> [--workspace dir])
|
|
70
|
+
phase-files Map a parallel group's unstaged changes to its phases
|
|
71
|
+
lessons Match, hit and list lessons
|
|
72
|
+
browser Browse with Lightpanda, Chromium when blocked
|
|
73
|
+
google-workspace Google Sheets, Docs, Slides, Calendar and Gmail
|
|
74
|
+
jev Yes/no or named-option decision on saved text
|
|
75
|
+
listen Speech-to-text from the mic
|
|
76
|
+
speak Text-to-speech
|
|
77
|
+
setup Handheld keys and OAuth (speak, listen, google-workspace, browser, jev)
|
|
78
|
+
doctor Check the tools and versions this repo needs
|
|
79
|
+
clean Remove scratch and stale generated files (--dry-run lists only)
|
|
80
|
+
yolo Turn on full-permission mode on every host (--check, --dry-run, --host <id>)
|
|
81
|
+
purge-mcps Purge global MCP servers (dry-run; --apply to write)
|
|
82
|
+
tasks Write .vscode/tasks.json from package scripts (VS Code and Cursor)
|
|
83
|
+
lint Lint with the standards pack's oxlint config (args go to oxlint; --baseline: lower .repo/lint-baseline.json)
|
|
84
|
+
fmt Format with the standards pack's oxfmt config (--check: report only; args go to oxfmt)
|
|
85
|
+
turn Gate the changed files: fmt, lint, typecheck, related tests, sync, check (--staged: the index)
|
|
86
|
+
ci Run every gate (--report: skipped tests); skips <dir>: tests skipped on every OS; wait: the run for HEAD
|
|
87
|
+
coverage run the suite with coverage and raise coverage-floors.json; a drop fails
|
|
88
|
+
mutate mutation-test the tracked files in a clone of HEAD (--shard i/n, --report <file>); check <dir> [--write]: score reports against mutation-floors.json
|
|
89
|
+
release Release the root package: bump from commits, stamp CHANGELOG.md, commit, push, wait on CI, tag ([--stable | --pre <id>]); wait <name>@<version>: poll npm
|
|
90
|
+
commit-msg Check a commit message file against the staged change (the commit-msg git hook), or each commit of --range <base>..<head>
|
|
91
|
+
hook <name> Run a hook: gate-fat-tools, orchestrate-parent, rtk-grok, scrub-spawn-prompt, session-map, worktree
|
|
92
|
+
help Show this help
|
|
93
|
+
version Print package version
|
|
94
|
+
|
|
95
|
+
Flags:
|
|
96
|
+
--skip-globals Skip repo yolo (otherwise init always runs it)
|
|
97
|
+
--yes, -y Non-interactive
|
|
98
|
+
--non-interactive Same as --yes
|
|
99
|
+
--packs <list> init: skill packs written as agents.skills into a new config: google-workspace,listen,speak,clasp or none
|
|
100
|
+
`;
|
|
101
|
+
|
|
102
|
+
const pkgRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
103
|
+
function readVersion(file: string): string {
|
|
104
|
+
const pkg = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
105
|
+
if (typeof pkg !== "object" || pkg === null || !("version" in pkg) || typeof pkg.version !== "string") {
|
|
106
|
+
throw new Error(`${file} has no version`);
|
|
107
|
+
}
|
|
108
|
+
return pkg.version;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const version = readVersion(path.join(pkgRoot, "package.json"));
|
|
112
|
+
|
|
113
|
+
describe("start", () => {
|
|
114
|
+
let argv: string[];
|
|
115
|
+
let exitCode: typeof process.exitCode;
|
|
116
|
+
let stdoutWrite: typeof process.stdout.write;
|
|
117
|
+
const chunks: string[] = [];
|
|
118
|
+
|
|
119
|
+
beforeEach(() => {
|
|
120
|
+
argv = process.argv.slice();
|
|
121
|
+
exitCode = process.exitCode;
|
|
122
|
+
process.exitCode = 0;
|
|
123
|
+
chunks.length = 0;
|
|
124
|
+
stdoutWrite = process.stdout.write.bind(process.stdout);
|
|
125
|
+
process.stdout.write = ((buf: string | Uint8Array) => {
|
|
126
|
+
chunks.push(String(buf));
|
|
127
|
+
return true;
|
|
128
|
+
}) as typeof process.stdout.write;
|
|
129
|
+
init.mockReset();
|
|
130
|
+
runCommand.mockReset();
|
|
131
|
+
runSync.mockClear();
|
|
132
|
+
runCommand.mockResolvedValue(0);
|
|
133
|
+
isInteractive.mockReset();
|
|
134
|
+
isInteractive.mockReturnValue(false);
|
|
135
|
+
select.mockReset();
|
|
136
|
+
confirm.mockReset();
|
|
137
|
+
spinner.mockReset();
|
|
138
|
+
spinner.mockImplementation(() => ({ start: vi.fn(), stop: vi.fn() }));
|
|
139
|
+
bailIfCancel.mockReset();
|
|
140
|
+
bailIfCancel.mockImplementation((v) => v);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
afterEach(() => {
|
|
144
|
+
process.argv = argv;
|
|
145
|
+
process.exitCode = exitCode;
|
|
146
|
+
process.stdout.write = stdoutWrite;
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it.each(["help", "--help", "-h"])("%s writes usage", async (cmd) => {
|
|
150
|
+
process.argv = ["node", "repo", cmd];
|
|
151
|
+
await start();
|
|
152
|
+
expect(chunks.join("")).toBe(USAGE);
|
|
153
|
+
expect(process.exitCode).toBe(0);
|
|
154
|
+
expect(init).not.toHaveBeenCalled();
|
|
155
|
+
expect(select).not.toHaveBeenCalled();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it.each(["version", "--version", "-v"])("%s writes package version", async (cmd) => {
|
|
159
|
+
process.argv = ["node", "repo", cmd];
|
|
160
|
+
await start();
|
|
161
|
+
expect(chunks.join("")).toBe(`${version}\n`);
|
|
162
|
+
expect(process.exitCode).toBe(0);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it.each([["null"], ['{"name":"x"}'], ['{"version":3}']])(
|
|
166
|
+
"version refuses a package.json that is %s",
|
|
167
|
+
async (text) => {
|
|
168
|
+
readFileSync.mockReturnValueOnce(text);
|
|
169
|
+
process.argv = ["node", "repo", "version"];
|
|
170
|
+
await expect(start()).rejects.toThrow("package.json has no version");
|
|
171
|
+
},
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
it("no-arg runs init on cwd", async () => {
|
|
175
|
+
process.argv = ["node", "repo"];
|
|
176
|
+
await start();
|
|
177
|
+
expect(init).toHaveBeenCalledOnce();
|
|
178
|
+
expect(init).toHaveBeenCalledWith(process.cwd(), {
|
|
179
|
+
skipGlobals: false,
|
|
180
|
+
});
|
|
181
|
+
expect(select).not.toHaveBeenCalled();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("init runs init on cwd", async () => {
|
|
185
|
+
process.argv = ["node", "repo", "init"];
|
|
186
|
+
await start();
|
|
187
|
+
expect(init).toHaveBeenCalledOnce();
|
|
188
|
+
expect(init).toHaveBeenCalledWith(process.cwd(), {
|
|
189
|
+
skipGlobals: false,
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it.each([
|
|
194
|
+
["node", "repo", "--skip-globals"],
|
|
195
|
+
["node", "repo", "init", "--skip-globals"],
|
|
196
|
+
["node", "repo", "--skip-globals", "init"],
|
|
197
|
+
])("passes skipGlobals when argv is %s", async (...argv) => {
|
|
198
|
+
process.argv = argv;
|
|
199
|
+
await start();
|
|
200
|
+
expect(init).toHaveBeenCalledOnce();
|
|
201
|
+
expect(init).toHaveBeenCalledWith(process.cwd(), {
|
|
202
|
+
skipGlobals: true,
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it.each(["--yes", "-y", "--non-interactive"])("%s no-arg still init, no menu", async (flag) => {
|
|
207
|
+
process.argv = ["node", "repo", flag];
|
|
208
|
+
await start();
|
|
209
|
+
expect(isInteractive).toHaveBeenCalledOnce();
|
|
210
|
+
expect(select).not.toHaveBeenCalled();
|
|
211
|
+
expect(init).toHaveBeenCalledOnce();
|
|
212
|
+
expect(init).toHaveBeenCalledWith(process.cwd(), {
|
|
213
|
+
skipGlobals: false,
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("sync --check forwards its args and io to runSync and keeps its exit code", async () => {
|
|
218
|
+
const errs: string[] = [];
|
|
219
|
+
const stderrWrite = process.stderr.write;
|
|
220
|
+
process.stderr.write = ((buf: string | Uint8Array) => {
|
|
221
|
+
errs.push(String(buf));
|
|
222
|
+
return true;
|
|
223
|
+
}) as typeof process.stderr.write;
|
|
224
|
+
runSync.mockReset();
|
|
225
|
+
runSync.mockImplementationOnce(async (_args, io) => {
|
|
226
|
+
io.stdout("out\n");
|
|
227
|
+
io.stderr("err\n");
|
|
228
|
+
return 1;
|
|
229
|
+
});
|
|
230
|
+
process.argv = ["node", "repo", "sync", "--check"];
|
|
231
|
+
try {
|
|
232
|
+
await start();
|
|
233
|
+
} finally {
|
|
234
|
+
process.stderr.write = stderrWrite;
|
|
235
|
+
}
|
|
236
|
+
expect(runSync).toHaveBeenCalledOnce();
|
|
237
|
+
expect(runSync).toHaveBeenCalledWith(["--check"], {
|
|
238
|
+
cwd: process.cwd(),
|
|
239
|
+
stdout: expect.any(Function),
|
|
240
|
+
stderr: expect.any(Function),
|
|
241
|
+
});
|
|
242
|
+
expect(chunks.join("")).toBe("out\n");
|
|
243
|
+
expect(errs.join("")).toBe("err\n");
|
|
244
|
+
expect(process.exitCode).toBe(1);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("TTY menu runs a registry command in-process", async () => {
|
|
248
|
+
isInteractive.mockReturnValue(true);
|
|
249
|
+
select.mockResolvedValue("doctor");
|
|
250
|
+
process.argv = ["node", "repo"];
|
|
251
|
+
await start();
|
|
252
|
+
expect(runCommand).toHaveBeenCalledWith("doctor", []);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("map prints the folder map without running a script", async () => {
|
|
256
|
+
process.argv = ["node", "repo", "map"];
|
|
257
|
+
await start();
|
|
258
|
+
expect(mapCommand).toHaveBeenLastCalledWith(process.cwd(), "");
|
|
259
|
+
expect(chunks.join("")).toBe("map:\n");
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("map <dir> scopes the folder map", async () => {
|
|
263
|
+
process.argv = ["node", "repo", "map", "packages/web"];
|
|
264
|
+
await start();
|
|
265
|
+
expect(mapCommand).toHaveBeenLastCalledWith(process.cwd(), "packages/web");
|
|
266
|
+
expect(chunks.join("")).toBe("map:packages/web\n");
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it.each(["nope", "generate", "upgrade"])(
|
|
270
|
+
"%s is an unknown command: one line on stderr and exitCode 1",
|
|
271
|
+
async (cmd) => {
|
|
272
|
+
const errs: string[] = [];
|
|
273
|
+
const stderrWrite = process.stderr.write;
|
|
274
|
+
process.stderr.write = ((buf: string | Uint8Array) => {
|
|
275
|
+
errs.push(String(buf));
|
|
276
|
+
return true;
|
|
277
|
+
}) as typeof process.stderr.write;
|
|
278
|
+
process.argv = ["node", "repo", cmd];
|
|
279
|
+
try {
|
|
280
|
+
await start();
|
|
281
|
+
} finally {
|
|
282
|
+
process.stderr.write = stderrWrite;
|
|
283
|
+
}
|
|
284
|
+
expect(errs.join("")).toBe(`repo: unknown command "${cmd}"; run \`repo --help\`\n`);
|
|
285
|
+
expect(chunks.join("")).toBe("");
|
|
286
|
+
expect(process.exitCode).toBe(1);
|
|
287
|
+
expect(init).not.toHaveBeenCalled();
|
|
288
|
+
expect(runSync).not.toHaveBeenCalled();
|
|
289
|
+
},
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
it("TTY no cmd shows menu default init", async () => {
|
|
293
|
+
isInteractive.mockReturnValue(true);
|
|
294
|
+
select.mockResolvedValue("init");
|
|
295
|
+
confirm.mockResolvedValue(true);
|
|
296
|
+
process.argv = ["node", "repo"];
|
|
297
|
+
await start();
|
|
298
|
+
expect(select).toHaveBeenCalledOnce();
|
|
299
|
+
// The call exists: the line above asserts exactly one.
|
|
300
|
+
const opts = select.mock.calls[0]![0] as {
|
|
301
|
+
initialValue: string;
|
|
302
|
+
options: { value: string }[];
|
|
303
|
+
};
|
|
304
|
+
expect(opts.initialValue).toBe("init");
|
|
305
|
+
expect(opts.options.map((o) => o.value)).toEqual([
|
|
306
|
+
"init",
|
|
307
|
+
"check",
|
|
308
|
+
"yolo",
|
|
309
|
+
"purge-mcps",
|
|
310
|
+
"doctor",
|
|
311
|
+
"clean",
|
|
312
|
+
"setup",
|
|
313
|
+
"tasks",
|
|
314
|
+
"help",
|
|
315
|
+
"quit",
|
|
316
|
+
]);
|
|
317
|
+
expect(init).toHaveBeenCalled();
|
|
318
|
+
expect(isInteractive).toHaveBeenCalledOnce();
|
|
319
|
+
expect(confirm).toHaveBeenCalledWith({
|
|
320
|
+
message: APPLY_GLOBALS_CONFIRM,
|
|
321
|
+
initialValue: true,
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it("TTY menu help writes usage", async () => {
|
|
326
|
+
isInteractive.mockReturnValue(true);
|
|
327
|
+
select.mockResolvedValue("help");
|
|
328
|
+
process.argv = ["node", "repo"];
|
|
329
|
+
await start();
|
|
330
|
+
expect(chunks.join("")).toBe(USAGE);
|
|
331
|
+
expect(process.exitCode).toBe(0);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it("TTY menu quit does nothing", async () => {
|
|
335
|
+
isInteractive.mockReturnValue(true);
|
|
336
|
+
select.mockResolvedValue("quit");
|
|
337
|
+
process.argv = ["node", "repo"];
|
|
338
|
+
await start();
|
|
339
|
+
expect(init).not.toHaveBeenCalled();
|
|
340
|
+
expect(process.exitCode).toBe(0);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it("TTY cancel exits 0", async () => {
|
|
344
|
+
isInteractive.mockReturnValue(true);
|
|
345
|
+
const exit = vi.spyOn(process, "exit").mockImplementation((() => {
|
|
346
|
+
throw new Error("EXIT");
|
|
347
|
+
}) as typeof process.exit);
|
|
348
|
+
select.mockImplementation(async () => {
|
|
349
|
+
process.exit(0);
|
|
350
|
+
});
|
|
351
|
+
process.argv = ["node", "repo"];
|
|
352
|
+
await expect(start()).rejects.toThrow("EXIT");
|
|
353
|
+
expect(exit).toHaveBeenCalledWith(0);
|
|
354
|
+
expect(init).not.toHaveBeenCalled();
|
|
355
|
+
exit.mockRestore();
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it("TTY init confirms globals; spinner onPhase stop before inherit", async () => {
|
|
359
|
+
isInteractive.mockReturnValue(true);
|
|
360
|
+
const spin = { start: vi.fn(), stop: vi.fn() };
|
|
361
|
+
spinner.mockReturnValue(spin);
|
|
362
|
+
confirm.mockResolvedValue(true);
|
|
363
|
+
init.mockImplementation((_dest: string, opts: { onPhase?: (n: string, a: string) => void }) => {
|
|
364
|
+
opts.onPhase?.("merge", "start");
|
|
365
|
+
opts.onPhase?.("merge", "stop");
|
|
366
|
+
opts.onPhase?.("install", "start");
|
|
367
|
+
opts.onPhase?.("install", "stop");
|
|
368
|
+
});
|
|
369
|
+
process.argv = ["node", "repo", "init"];
|
|
370
|
+
await start();
|
|
371
|
+
expect(confirm).toHaveBeenCalled();
|
|
372
|
+
const globalsConfirm = confirm.mock.calls.find(
|
|
373
|
+
(c) => (c[0] as { message?: string }).message === APPLY_GLOBALS_CONFIRM,
|
|
374
|
+
);
|
|
375
|
+
expect(globalsConfirm?.[0]).toMatchObject({
|
|
376
|
+
message: APPLY_GLOBALS_CONFIRM,
|
|
377
|
+
initialValue: true,
|
|
378
|
+
});
|
|
379
|
+
expect(init).toHaveBeenCalledWith(
|
|
380
|
+
process.cwd(),
|
|
381
|
+
expect.objectContaining({
|
|
382
|
+
skipGlobals: false,
|
|
383
|
+
onPhase: expect.any(Function),
|
|
384
|
+
}),
|
|
385
|
+
);
|
|
386
|
+
expect(isInteractive).toHaveBeenCalledOnce();
|
|
387
|
+
expect(spin.start).toHaveBeenCalledWith("merge");
|
|
388
|
+
expect(spin.stop).toHaveBeenCalledWith("merge");
|
|
389
|
+
expect(spin.start).toHaveBeenCalledWith("install");
|
|
390
|
+
expect(spin.stop).toHaveBeenCalledWith("install");
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it("TTY init --skip-globals default confirm false maps to skip", async () => {
|
|
394
|
+
isInteractive.mockReturnValue(true);
|
|
395
|
+
confirm.mockResolvedValue(false);
|
|
396
|
+
process.argv = ["node", "repo", "init", "--skip-globals"];
|
|
397
|
+
await start();
|
|
398
|
+
expect(confirm).toHaveBeenCalledWith({
|
|
399
|
+
message: APPLY_GLOBALS_CONFIRM,
|
|
400
|
+
initialValue: false,
|
|
401
|
+
});
|
|
402
|
+
expect(init).toHaveBeenCalledWith(
|
|
403
|
+
process.cwd(),
|
|
404
|
+
expect.objectContaining({
|
|
405
|
+
skipGlobals: true,
|
|
406
|
+
onPhase: expect.any(Function),
|
|
407
|
+
}),
|
|
408
|
+
);
|
|
409
|
+
expect(isInteractive).toHaveBeenCalledOnce();
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
describe("start packs", () => {
|
|
414
|
+
it("TTY init passes --packs to init", async () => {
|
|
415
|
+
isInteractive.mockReturnValue(true);
|
|
416
|
+
confirm.mockResolvedValue(true);
|
|
417
|
+
process.argv = ["node", "repo", "init", "--packs", "listen"];
|
|
418
|
+
await start();
|
|
419
|
+
expect(init).toHaveBeenCalledWith(
|
|
420
|
+
process.cwd(),
|
|
421
|
+
expect.objectContaining({ packs: ["listen"], skipGlobals: false }),
|
|
422
|
+
);
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
describe("parseArgv --packs", () => {
|
|
427
|
+
it("pair, equals, and none", () => {
|
|
428
|
+
expect(parseArgv(["init", "--packs", "speak,listen"])).toMatchObject({
|
|
429
|
+
cmd: "init",
|
|
430
|
+
packs: ["listen", "speak"],
|
|
431
|
+
packsError: false,
|
|
432
|
+
});
|
|
433
|
+
expect(parseArgv(["init", "--packs=google-workspace,clasp"])).toMatchObject({
|
|
434
|
+
packs: ["google-workspace", "clasp"],
|
|
435
|
+
packsError: false,
|
|
436
|
+
});
|
|
437
|
+
expect(parseArgv(["init", "--packs", "none"])).toMatchObject({ packs: [], packsError: false });
|
|
438
|
+
expect(parseArgv(["init", "--packs="])).toMatchObject({ packs: [], packsError: false });
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
it("missing, flag, and unknown are packsError", () => {
|
|
442
|
+
expect(parseArgv(["init", "--packs"])).toMatchObject({ packsError: true });
|
|
443
|
+
expect(parseArgv(["init", "--packs", "--yes"])).toMatchObject({ packsError: true });
|
|
444
|
+
expect(parseArgv(["init", "--packs", "nope"])).toMatchObject({ packsError: true });
|
|
445
|
+
expect(parseArgv(["init", "--packs=nope"])).toMatchObject({ packsError: true });
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it("unknown --packs writes usage", async () => {
|
|
449
|
+
const lines: string[] = [];
|
|
450
|
+
const stdoutWrite = process.stdout.write;
|
|
451
|
+
const exitCode = process.exitCode;
|
|
452
|
+
const argv = process.argv.slice();
|
|
453
|
+
process.stdout.write = ((buf: string | Uint8Array) => {
|
|
454
|
+
lines.push(String(buf));
|
|
455
|
+
return true;
|
|
456
|
+
}) as typeof process.stdout.write;
|
|
457
|
+
process.exitCode = 0;
|
|
458
|
+
process.argv = ["node", "repo", "init", "--packs", "nope"];
|
|
459
|
+
init.mockClear();
|
|
460
|
+
try {
|
|
461
|
+
await start();
|
|
462
|
+
expect(lines.join("")).toBe(USAGE);
|
|
463
|
+
expect(process.exitCode).toBe(1);
|
|
464
|
+
expect(init).not.toHaveBeenCalled();
|
|
465
|
+
} finally {
|
|
466
|
+
process.stdout.write = stdoutWrite;
|
|
467
|
+
process.exitCode = exitCode;
|
|
468
|
+
process.argv = argv;
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
describe("start check", () => {
|
|
474
|
+
const SETS_FILES = (rel: string) =>
|
|
475
|
+
`repo check: tsconfig: ${rel} sets "files", which replaces the base's reset.d.ts. List sources under "include".`;
|
|
476
|
+
const NO_BASE = (rel: string) =>
|
|
477
|
+
`repo check: tsconfig: ${rel} does not extend a base. Extend "@workerstack/repo/tsconfig/base", "bun", "worker" or "web".`;
|
|
478
|
+
const NO_SRC = (dir: string) =>
|
|
479
|
+
`repo check: tsconfig: ${dir} has no tsconfig.src.json; add one with "extends": ["./tsconfig.json", "@workerstack/repo/tsconfig/strict"] and an "exclude" that repeats tsconfig.json's and adds the tests`;
|
|
480
|
+
|
|
481
|
+
let argv: string[];
|
|
482
|
+
let exitCode: typeof process.exitCode;
|
|
483
|
+
let stderrWrite: typeof process.stderr.write;
|
|
484
|
+
let dir: string;
|
|
485
|
+
const stderr: string[] = [];
|
|
486
|
+
/** What check printed to stderr but knip's lines, which the tests of other checks do not pin. */
|
|
487
|
+
const besidesKnip = () => stderr.filter((line) => !line.startsWith("repo check: knip")).join("");
|
|
488
|
+
|
|
489
|
+
function write(rel: string, content: object) {
|
|
490
|
+
const file = path.join(dir, rel);
|
|
491
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
492
|
+
fs.writeFileSync(file, `${JSON.stringify(content)}\n`);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/** A root tsconfig.json on the bun base and its strict tsconfig.src.json, which check passes. */
|
|
496
|
+
function writeConfigs() {
|
|
497
|
+
write("tsconfig.json", { extends: "@workerstack/repo/tsconfig/bun", include: ["src"] });
|
|
498
|
+
write("tsconfig.src.json", {
|
|
499
|
+
extends: ["./tsconfig.json", "@workerstack/repo/tsconfig/strict"],
|
|
500
|
+
exclude: ["node_modules", "**/*.test.ts"],
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
beforeEach(() => {
|
|
505
|
+
argv = process.argv.slice();
|
|
506
|
+
exitCode = process.exitCode;
|
|
507
|
+
// bun cannot unset an exit code once set, so a clean start is 0.
|
|
508
|
+
process.exitCode = 0;
|
|
509
|
+
// A consumer repo: git, with this checkout linked where an install puts it.
|
|
510
|
+
dir = fs.mkdtempSync(path.join(os.tmpdir(), "repo-check-"));
|
|
511
|
+
runGit("git", ["init", "-q"], { cwd: dir });
|
|
512
|
+
// A public package must keep a changelog, which check reads.
|
|
513
|
+
fs.writeFileSync(path.join(dir, "CHANGELOG.md"), "# Changelog\n\n## [Unreleased]\n");
|
|
514
|
+
fs.writeFileSync(path.join(dir, ".gitignore"), "node_modules\n");
|
|
515
|
+
const scope = path.join(dir, "node_modules", "@workerstack");
|
|
516
|
+
fs.mkdirSync(scope, { recursive: true });
|
|
517
|
+
fs.symlinkSync(pkgRoot, path.join(scope, "repo"), "junction");
|
|
518
|
+
vi.spyOn(process, "cwd").mockReturnValue(dir);
|
|
519
|
+
stderr.length = 0;
|
|
520
|
+
stderrWrite = process.stderr.write;
|
|
521
|
+
process.stderr.write = ((buf: string | Uint8Array) => {
|
|
522
|
+
stderr.push(String(buf));
|
|
523
|
+
return true;
|
|
524
|
+
}) as typeof process.stderr.write;
|
|
525
|
+
runSync.mockReset();
|
|
526
|
+
runSync.mockResolvedValue(0);
|
|
527
|
+
isInteractive.mockReset();
|
|
528
|
+
isInteractive.mockReturnValue(false);
|
|
529
|
+
process.argv = ["node", "repo", "check"];
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
afterEach(() => {
|
|
533
|
+
process.stderr.write = stderrWrite;
|
|
534
|
+
vi.mocked(process.cwd).mockRestore();
|
|
535
|
+
process.argv = argv;
|
|
536
|
+
process.exitCode = exitCode;
|
|
537
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
it("prints every bad tsconfig, still runs repo sync --check and exits 1", async () => {
|
|
541
|
+
write("a/tsconfig.json", { extends: "@workerstack/repo/tsconfig/bun", files: ["src/a.ts"] });
|
|
542
|
+
write("b/tsconfig.json", { include: ["src"] });
|
|
543
|
+
await start();
|
|
544
|
+
expect(besidesKnip()).toBe(
|
|
545
|
+
[SETS_FILES("a/tsconfig.json"), NO_SRC("a"), NO_BASE("b/tsconfig.json"), NO_SRC("b"), ""].join("\n"),
|
|
546
|
+
);
|
|
547
|
+
expect(runSync).toHaveBeenCalledOnce();
|
|
548
|
+
expect(runSync).toHaveBeenCalledWith(["--check"], expect.objectContaining({ cwd: dir }));
|
|
549
|
+
expect(process.exitCode).toBe(1);
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
it("prints each CHANGELOG.md problem of a public root and exits 1", async () => {
|
|
553
|
+
writeConfigs();
|
|
554
|
+
write("package.json", {});
|
|
555
|
+
fs.writeFileSync(path.join(dir, "CHANGELOG.md"), "# Changelog\n\n## [Unreleased]\n\n### Improved\n");
|
|
556
|
+
await start();
|
|
557
|
+
expect(besidesKnip()).toContain(
|
|
558
|
+
'repo check: CHANGELOG.md:5: "### Improved" is not a section; use Added, Changed, Deprecated, Removed, Fixed or Security\n',
|
|
559
|
+
);
|
|
560
|
+
expect(process.exitCode).toBe(1);
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
it("prints a second env.ts in one package and exits 1", async () => {
|
|
564
|
+
writeConfigs();
|
|
565
|
+
write("package.json", {});
|
|
566
|
+
fs.mkdirSync(path.join(dir, "src"));
|
|
567
|
+
fs.writeFileSync(path.join(dir, "env.ts"), "export {};\n");
|
|
568
|
+
fs.writeFileSync(path.join(dir, "src/env.ts"), "export {};\n");
|
|
569
|
+
await start();
|
|
570
|
+
expect(besidesKnip()).toBe("repo check: env: env.ts and src/env.ts are both env.ts in .; keep one\n");
|
|
571
|
+
expect(runSync).toHaveBeenCalledOnce();
|
|
572
|
+
expect(process.exitCode).toBe(1);
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
it("leaves the exit code at 0 when every tsconfig keeps a base", async () => {
|
|
576
|
+
writeConfigs();
|
|
577
|
+
await start();
|
|
578
|
+
expect(besidesKnip()).toBe("");
|
|
579
|
+
expect(runSync).toHaveBeenCalledOnce();
|
|
580
|
+
expect(runSync).toHaveBeenCalledWith(["--check"], expect.objectContaining({ cwd: dir }));
|
|
581
|
+
expect(process.exitCode).toBe(0);
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
it("with the standards pack on, prints each lint count over .repo/lint-baseline.json and exits 1", async () => {
|
|
585
|
+
writeConfigs();
|
|
586
|
+
fs.writeFileSync(path.join(dir, "workerstack.config.ts"), 'export default { packs: ["standards"] };\n');
|
|
587
|
+
const ctx = {
|
|
588
|
+
root: "",
|
|
589
|
+
config: { packs: ["standards" as const], agents: NO_AGENTS, ...BASE_CONFIG },
|
|
590
|
+
};
|
|
591
|
+
for (const output of standards(
|
|
592
|
+
{ ...ctx, workspace: { root: "", manifest: {}, packages: [] }, read: () => null },
|
|
593
|
+
[],
|
|
594
|
+
).outputs)
|
|
595
|
+
if (output.kind === "file") {
|
|
596
|
+
fs.mkdirSync(path.dirname(path.join(dir, output.path)), { recursive: true });
|
|
597
|
+
fs.writeFileSync(path.join(dir, output.path), output.content);
|
|
598
|
+
}
|
|
599
|
+
fs.mkdirSync(path.join(dir, "src"));
|
|
600
|
+
fs.writeFileSync(path.join(dir, "src/a.ts"), "export const a = (xs: string[]): string => xs.find(Boolean)!;\n");
|
|
601
|
+
await start();
|
|
602
|
+
expect(besidesKnip()).toBe(
|
|
603
|
+
"repo check: .repo/lint-baseline.json: src/a.ts has 1 typescript(no-non-null-assertion), the baseline allows 0\n",
|
|
604
|
+
);
|
|
605
|
+
expect(process.exitCode).toBe(1);
|
|
606
|
+
}, 60_000);
|
|
607
|
+
|
|
608
|
+
it("with the standards pack on, prints each manifest problem and exits 1", async () => {
|
|
609
|
+
writeConfigs();
|
|
610
|
+
write("package.json", { name: "app", sideEffects: ["*.css"] });
|
|
611
|
+
fs.writeFileSync(path.join(dir, "workerstack.config.ts"), 'export default { packs: ["standards"] };\n');
|
|
612
|
+
await start();
|
|
613
|
+
expect(besidesKnip()).toBe(
|
|
614
|
+
[
|
|
615
|
+
'repo check: package.json: "private" is missing; set true, or false to publish it',
|
|
616
|
+
'repo check: package.json: "sideEffects" is ["*.css"]; set it to false and import CSS from an app entry',
|
|
617
|
+
`repo check: package.json: "packageManager" is missing; set it to "bun@${Bun.version}"`,
|
|
618
|
+
"",
|
|
619
|
+
].join("\n"),
|
|
620
|
+
);
|
|
621
|
+
expect(process.exitCode).toBe(1);
|
|
622
|
+
}, 60_000);
|
|
623
|
+
|
|
624
|
+
it("with the standards pack on, prints each refused comment with its file and line and exits 1", async () => {
|
|
625
|
+
writeConfigs();
|
|
626
|
+
fs.writeFileSync(path.join(dir, "workerstack.config.ts"), 'export default { packs: ["standards"] };\n');
|
|
627
|
+
fs.mkdirSync(path.join(dir, "src"));
|
|
628
|
+
fs.writeFileSync(path.join(dir, "src/a.ts"), "export const a = 1;\n// @ts-ignore\n");
|
|
629
|
+
await start();
|
|
630
|
+
expect(besidesKnip()).toBe(
|
|
631
|
+
'repo check: src/a.ts:2: "@ts-ignore" hides a type error; fix the type, or write @ts-expect-error with a reason\n',
|
|
632
|
+
);
|
|
633
|
+
expect(process.exitCode).toBe(1);
|
|
634
|
+
}, 60_000);
|
|
635
|
+
|
|
636
|
+
it("with the release pack on, prints the Stryker install and the empty coverage.mutate and exits 1", async () => {
|
|
637
|
+
writeConfigs();
|
|
638
|
+
fs.writeFileSync(path.join(dir, "workerstack.config.ts"), 'export default { packs: ["release"] };\n');
|
|
639
|
+
await start();
|
|
640
|
+
expect(besidesKnip()).toContain(
|
|
641
|
+
[
|
|
642
|
+
"repo check: the release pack runs mutation tests nightly; bun add -d @stryker-mutator/core@10.1.1 @stryker-mutator/vitest-runner@10.1.1",
|
|
643
|
+
"repo check: coverage.mutate (src/**/*.ts) matches no tracked file; set coverage.mutate in workerstack.config.ts",
|
|
644
|
+
"",
|
|
645
|
+
].join("\n"),
|
|
646
|
+
);
|
|
647
|
+
expect(process.exitCode).toBe(1);
|
|
648
|
+
}, 60_000);
|
|
649
|
+
|
|
650
|
+
it("with the standards pack on, prints knip's problems to stderr and its ignores to stdout, and exits 1", async () => {
|
|
651
|
+
writeConfigs();
|
|
652
|
+
write("package.json", { name: "app", private: true, sideEffects: false, packageManager: `bun@${Bun.version}` });
|
|
653
|
+
const ignore = '{ file: "src/b.ts", reason: "loaded by name" }';
|
|
654
|
+
const config = `export default { packs: ["standards"], knip: { ignore: [${ignore}] } };\n`;
|
|
655
|
+
fs.writeFileSync(path.join(dir, "workerstack.config.ts"), config);
|
|
656
|
+
fs.mkdirSync(path.join(dir, "src"));
|
|
657
|
+
fs.writeFileSync(path.join(dir, "src/a.ts"), "export const a = 1;\n");
|
|
658
|
+
fs.writeFileSync(path.join(dir, "src/b.ts"), "export const b = 1;\n");
|
|
659
|
+
const stdout = vi.spyOn(process.stdout, "write").mockImplementation(() => true);
|
|
660
|
+
await start();
|
|
661
|
+
const printed = stdout.mock.calls.map(([line]) => String(line));
|
|
662
|
+
stdout.mockRestore();
|
|
663
|
+
expect(printed).toContain("repo check: knip: 1 ignore: file src/b.ts (loaded by name)\n");
|
|
664
|
+
expect(stderr).toContain("repo check: knip: src/a.ts: unused file; delete it\n");
|
|
665
|
+
expect(stderr).not.toContain("repo check: knip: src/b.ts: unused file; delete it\n");
|
|
666
|
+
expect(process.exitCode).toBe(1);
|
|
667
|
+
}, 60_000);
|
|
668
|
+
|
|
669
|
+
it("with the release pack on, prints actionlint's problem for a workflow to stderr and exits 1", async () => {
|
|
670
|
+
writeConfigs();
|
|
671
|
+
fs.writeFileSync(path.join(dir, "workerstack.config.ts"), 'export default { packs: ["release"] };\n');
|
|
672
|
+
const flows = path.join(dir, ".github", "workflows");
|
|
673
|
+
fs.mkdirSync(flows, { recursive: true });
|
|
674
|
+
const job = " build:\n runs_on: ubuntu-latest\n steps:\n - run: echo hi\n";
|
|
675
|
+
fs.writeFileSync(path.join(flows, "other.yml"), `name: x\non: push\njobs:\n${job}`);
|
|
676
|
+
await start();
|
|
677
|
+
const line = 'repo check: actionlint: .github/workflows/other.yml:5:5: unexpected key "runs_on" for "job" section';
|
|
678
|
+
expect(stderr.some((text) => text.startsWith(line) && text.endsWith("[syntax-check]\n"))).toBe(true);
|
|
679
|
+
expect(process.exitCode).toBe(1);
|
|
680
|
+
}, 180_000);
|
|
681
|
+
|
|
682
|
+
it("with the standards pack on, prints each live lint.skip and its count, and exits 1 on a stale entry", async () => {
|
|
683
|
+
writeConfigs();
|
|
684
|
+
write("package.json", { name: "app", private: true, sideEffects: false, packageManager: `bun@${Bun.version}` });
|
|
685
|
+
const reason = "the lookup result is always present";
|
|
686
|
+
const skip = (file: string) => ({ files: [file], rules: ["typescript/no-non-null-assertion"], reason });
|
|
687
|
+
const config = { packs: ["standards"], lint: { skip: [skip("src/a.ts"), skip("src/b.ts")] } };
|
|
688
|
+
fs.writeFileSync(path.join(dir, "workerstack.config.ts"), `export default ${JSON.stringify(config)};\n`);
|
|
689
|
+
fs.mkdirSync(path.join(dir, "src"));
|
|
690
|
+
fs.writeFileSync(path.join(dir, "src/a.ts"), "export const a = (xs: string[]): string => xs.find(Boolean)!;\n");
|
|
691
|
+
fs.writeFileSync(path.join(dir, "src/b.ts"), "export const b = 1;\n");
|
|
692
|
+
const stdout = vi.spyOn(process.stdout, "write").mockImplementation(() => true);
|
|
693
|
+
await start();
|
|
694
|
+
const printed = stdout.mock.calls.map(([line]) => String(line));
|
|
695
|
+
stdout.mockRestore();
|
|
696
|
+
expect(printed).toEqual([
|
|
697
|
+
`repo check: skip typescript/no-non-null-assertion in src/a.ts: 1 hit (${reason})\n`,
|
|
698
|
+
"repo check: skips: typescript/no-non-null-assertion 1\n",
|
|
699
|
+
]);
|
|
700
|
+
expect(besidesKnip()).toBe(
|
|
701
|
+
'repo check: lint.skip[1]: typescript/no-non-null-assertion has no hit in "src/b.ts"; delete it from the entry\n',
|
|
702
|
+
);
|
|
703
|
+
expect(process.exitCode).toBe(1);
|
|
704
|
+
expect(fs.readdirSync(dir).filter((name) => name.startsWith(".oxlintrc.unskipped-"))).toEqual([]);
|
|
705
|
+
}, 60_000);
|
|
706
|
+
|
|
707
|
+
it("with the standards pack on, reports a skipped magic rule's hits and counts the other over the baseline", async () => {
|
|
708
|
+
write("package.json", { name: "app", private: true, sideEffects: false, packageManager: `bun@${Bun.version}` });
|
|
709
|
+
const reason = "retry tuning lives here for now";
|
|
710
|
+
const skip = { files: ["src/retry.ts"], rules: ["repo/magic-number"], reason };
|
|
711
|
+
const config = { packs: ["standards"], lint: { skip: [skip] } };
|
|
712
|
+
fs.writeFileSync(path.join(dir, "workerstack.config.ts"), `export default ${JSON.stringify(config)};\n`);
|
|
713
|
+
const synced = { packs: ["standards" as const], agents: NO_AGENTS, ...BASE_CONFIG };
|
|
714
|
+
const workspace = { root: "", manifest: {}, packages: [] };
|
|
715
|
+
for (const output of standards({ root: "", config: synced, workspace, read: () => null }, []).outputs)
|
|
716
|
+
if (output.kind === "file") {
|
|
717
|
+
fs.mkdirSync(path.dirname(path.join(dir, output.path)), { recursive: true });
|
|
718
|
+
fs.writeFileSync(path.join(dir, output.path), output.content);
|
|
719
|
+
}
|
|
720
|
+
fs.mkdirSync(path.join(dir, "src"));
|
|
721
|
+
const retry =
|
|
722
|
+
'export const wait = (): number => 5000 + 3;\nexport const is = (m: string): boolean => m === "fast";\n';
|
|
723
|
+
fs.writeFileSync(path.join(dir, "src/retry.ts"), retry);
|
|
724
|
+
const stdout = vi.spyOn(process.stdout, "write").mockImplementation(() => true);
|
|
725
|
+
await start();
|
|
726
|
+
const printed = stdout.mock.calls.map(([line]) => String(line));
|
|
727
|
+
stdout.mockRestore();
|
|
728
|
+
expect(printed).toEqual([
|
|
729
|
+
`repo check: skip repo/magic-number in src/retry.ts: 2 hits (${reason})\n`,
|
|
730
|
+
"repo check: skips: repo/magic-number 2\n",
|
|
731
|
+
]);
|
|
732
|
+
expect(besidesKnip()).toBe(
|
|
733
|
+
"repo check: .repo/lint-baseline.json: src/retry.ts has 1 repo(magic-string), the baseline allows 0\n",
|
|
734
|
+
);
|
|
735
|
+
expect(process.exitCode).toBe(1);
|
|
736
|
+
}, 60_000);
|
|
737
|
+
|
|
738
|
+
it("with the standards pack on, prints each reject file problem and counts an untested guard over the baseline", async () => {
|
|
739
|
+
write("package.json", { name: "app", private: true, sideEffects: false, packageManager: `bun@${Bun.version}` });
|
|
740
|
+
fs.writeFileSync(path.join(dir, "workerstack.config.ts"), 'export default { packs: ["standards"] };\n');
|
|
741
|
+
const synced = { packs: ["standards" as const], agents: NO_AGENTS, ...BASE_CONFIG };
|
|
742
|
+
const workspace = { root: "", manifest: {}, packages: [] };
|
|
743
|
+
for (const output of standards({ root: "", config: synced, workspace, read: () => null }, []).outputs)
|
|
744
|
+
if (output.kind === "file") {
|
|
745
|
+
fs.mkdirSync(path.dirname(path.join(dir, output.path)), { recursive: true });
|
|
746
|
+
fs.writeFileSync(path.join(dir, output.path), output.content);
|
|
747
|
+
}
|
|
748
|
+
fs.mkdirSync(path.join(dir, "src"));
|
|
749
|
+
const guard = "export const isUser = (value: unknown): value is string[] => Array.isArray(value);\n";
|
|
750
|
+
fs.writeFileSync(path.join(dir, "src/is-user.ts"), guard);
|
|
751
|
+
fs.writeFileSync(path.join(dir, "src/user.reject.ts"), 'lod("u2");\n');
|
|
752
|
+
await start();
|
|
753
|
+
expect(besidesKnip()).toBe(
|
|
754
|
+
[
|
|
755
|
+
"repo check: rejections: src/user.reject.ts: line 1 fails unmarked: TS2304: Cannot find name 'lod'.",
|
|
756
|
+
"repo check: .repo/lint-baseline.json: src/is-user.ts has 1 repo(guard-rejects), the baseline allows 0",
|
|
757
|
+
"",
|
|
758
|
+
].join("\n"),
|
|
759
|
+
);
|
|
760
|
+
expect(process.exitCode).toBe(1);
|
|
761
|
+
}, 60_000);
|
|
762
|
+
|
|
763
|
+
/** Commits the working tree to `main`, then moves to a `feature` branch. */
|
|
764
|
+
function commitMain(): void {
|
|
765
|
+
const git = (...args: string[]) =>
|
|
766
|
+
runGit(
|
|
767
|
+
"git",
|
|
768
|
+
[
|
|
769
|
+
"-c",
|
|
770
|
+
"user.name=t",
|
|
771
|
+
"-c",
|
|
772
|
+
"user.email=t@t",
|
|
773
|
+
"-c",
|
|
774
|
+
"commit.gpgsign=false",
|
|
775
|
+
"-c",
|
|
776
|
+
"core.autocrlf=false",
|
|
777
|
+
...args,
|
|
778
|
+
],
|
|
779
|
+
{ cwd: dir },
|
|
780
|
+
);
|
|
781
|
+
git("checkout", "-q", "-b", "main");
|
|
782
|
+
git("add", "-A");
|
|
783
|
+
git("commit", "-q", "-m", "main");
|
|
784
|
+
git("checkout", "-q", "-b", "feature");
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
it("fails a floor lower than main's", async () => {
|
|
788
|
+
writeConfigs();
|
|
789
|
+
write("coverage-floors.json", { "src/a.ts": { statements: 90, branches: 80, functions: 90 } });
|
|
790
|
+
fs.mkdirSync(path.join(dir, "src"));
|
|
791
|
+
fs.writeFileSync(path.join(dir, "src/a.ts"), "export const a = 1;\n");
|
|
792
|
+
commitMain();
|
|
793
|
+
write("coverage-floors.json", { "src/a.ts": { statements: 90, branches: 70, functions: 90 } });
|
|
794
|
+
await start();
|
|
795
|
+
expect(besidesKnip()).toBe(
|
|
796
|
+
"repo check: coverage-floors.json: src/a.ts branches 70 is below main's 80; floors only rise\n",
|
|
797
|
+
);
|
|
798
|
+
expect(process.exitCode).toBe(1);
|
|
799
|
+
}, 60_000);
|
|
800
|
+
|
|
801
|
+
it("fails an it.fails case that known-failures.json does not list", async () => {
|
|
802
|
+
writeConfigs();
|
|
803
|
+
fs.mkdirSync(path.join(dir, "src"));
|
|
804
|
+
fs.writeFileSync(path.join(dir, "src/a.test.ts"), 'it.fails("parses times", () => {});\n');
|
|
805
|
+
await start();
|
|
806
|
+
expect(besidesKnip()).toBe(
|
|
807
|
+
'repo check: src/a.test.ts:1: it.fails("parses times") is not in known-failures.json; list it or fix it\n',
|
|
808
|
+
);
|
|
809
|
+
expect(process.exitCode).toBe(1);
|
|
810
|
+
}, 60_000);
|
|
811
|
+
|
|
812
|
+
it("reads no test files outside a git checkout", async () => {
|
|
813
|
+
writeConfigs();
|
|
814
|
+
fs.rmSync(path.join(dir, ".git"), { recursive: true, force: true });
|
|
815
|
+
await start();
|
|
816
|
+
expect(besidesKnip()).toBe("");
|
|
817
|
+
expect(process.exitCode).toBe(0);
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
it("without the standards pack says nothing of disable comments", async () => {
|
|
821
|
+
writeConfigs();
|
|
822
|
+
fs.mkdirSync(path.join(dir, "src"));
|
|
823
|
+
fs.writeFileSync(path.join(dir, "src/a.ts"), "export const a = 1;\n// @ts-ignore\n");
|
|
824
|
+
await start();
|
|
825
|
+
expect(besidesKnip()).toBe("");
|
|
826
|
+
expect(process.exitCode).toBe(0);
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
it("without the standards pack prints no manifest lines", async () => {
|
|
830
|
+
writeConfigs();
|
|
831
|
+
write("package.json", { name: "app" });
|
|
832
|
+
await start();
|
|
833
|
+
expect(besidesKnip()).toBe("");
|
|
834
|
+
expect(process.exitCode).toBe(0);
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
it("leaves an unreadable manifest to sync --check and prints nothing twice", async () => {
|
|
838
|
+
writeConfigs();
|
|
839
|
+
write("package.json", { name: "app", private: "yes" });
|
|
840
|
+
fs.writeFileSync(path.join(dir, "workerstack.config.ts"), 'export default { packs: ["standards"] };\n');
|
|
841
|
+
runSync.mockResolvedValue(1);
|
|
842
|
+
await start();
|
|
843
|
+
expect(besidesKnip()).not.toContain("repo check: package.json");
|
|
844
|
+
expect(process.exitCode).toBe(1);
|
|
845
|
+
}, 60_000);
|
|
846
|
+
|
|
847
|
+
it("exits 1 when sync --check finds drift", async () => {
|
|
848
|
+
writeConfigs();
|
|
849
|
+
runSync.mockResolvedValue(1);
|
|
850
|
+
await start();
|
|
851
|
+
expect(process.exitCode).toBe(1);
|
|
852
|
+
});
|
|
853
|
+
});
|