@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/init.ts
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { execFileSync } from "./spawn.ts";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { CONFIG_FILE, HOSTS, SKILL_PACKS, defaultPacks } from "./config.ts";
|
|
6
|
+
import { PRINT_WIDTH } from "./sync/standards.ts";
|
|
7
|
+
import { PACKAGE_NAME } from "./sync/stock.ts";
|
|
8
|
+
|
|
9
|
+
export { PACKAGE_NAME };
|
|
10
|
+
|
|
11
|
+
const PKG_ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
12
|
+
|
|
13
|
+
/** The `repo:*` scripts older inits copied into a consumer, with every value they wrote: init removes one still equal to one of them. */
|
|
14
|
+
const OLD_DEFAULT_SCRIPTS: Record<string, readonly string[]> = {
|
|
15
|
+
"repo:build": ["tsc -p tsconfig.build.json"],
|
|
16
|
+
"repo:prepack": ["bun run build"],
|
|
17
|
+
"repo:rulesync:generate": [
|
|
18
|
+
"bun ./.rulesync/scripts/guarded-roots.ts generate",
|
|
19
|
+
"node ./.rulesync/scripts/guarded-roots.mjs generate",
|
|
20
|
+
"node ./scripts/guarded-roots.mjs generate",
|
|
21
|
+
"rulesync generate -t claudecode,codexcli,antigravity-cli,grokcli -f rules,subagents,skills && rulesync generate -t claudecode,codexcli,antigravity-cli -f hooks && node scripts/emit-mcp-policy.mjs && node scripts/emit-gemini.mjs && node scripts/emit-agent-shell-tools.mjs && node scripts/hooks-union.mjs",
|
|
22
|
+
"rulesync generate -t claudecode,codexcli,antigravity-cli,grokcli -f rules,subagents && rulesync generate -t claudecode,codexcli,antigravity-cli -f hooks && node scripts/emit-mcp-policy.mjs",
|
|
23
|
+
],
|
|
24
|
+
"repo:rulesync:watch": [
|
|
25
|
+
"bun ./.rulesync/scripts/guarded-roots.ts watch",
|
|
26
|
+
"node ./.rulesync/scripts/guarded-roots.mjs watch",
|
|
27
|
+
"node ./scripts/guarded-roots.mjs watch",
|
|
28
|
+
"node scripts/emit-mcp-policy.mjs && node scripts/emit-gemini.mjs && node scripts/emit-agent-shell-tools.mjs && rulesync generate -t claudecode,codexcli,antigravity-cli,grokcli -f rules,subagents,skills --watch",
|
|
29
|
+
"node scripts/emit-mcp-policy.mjs && rulesync generate -t claudecode,codexcli,antigravity-cli,grokcli -f rules,subagents --watch",
|
|
30
|
+
],
|
|
31
|
+
"repo:rulesync:check": [
|
|
32
|
+
"bun ./.rulesync/scripts/guarded-roots.ts check",
|
|
33
|
+
"node ./.rulesync/scripts/guarded-roots.mjs check",
|
|
34
|
+
"node ./scripts/guarded-roots.mjs check",
|
|
35
|
+
"rulesync generate -t claudecode,codexcli,antigravity-cli,grokcli -f rules,skills --check && rulesync generate -t codexcli,antigravity-cli,grokcli -f subagents --check && rulesync generate -t claudecode,codexcli,antigravity-cli -f hooks --check && node scripts/emit-mcp-policy.mjs --check && node scripts/emit-gemini.mjs --check && node scripts/emit-agent-shell-tools.mjs --check && node scripts/check-globals.mjs && node scripts/hooks-union.mjs --check",
|
|
36
|
+
"rulesync generate -t claudecode,codexcli,antigravity-cli,grokcli -f rules,subagents --check && rulesync generate -t claudecode,codexcli,antigravity-cli -f hooks --check && node scripts/emit-mcp-policy.mjs --check && node scripts/check-globals.mjs",
|
|
37
|
+
],
|
|
38
|
+
"repo:hooks:union": [
|
|
39
|
+
"bun .rulesync/scripts/hooks-union.ts",
|
|
40
|
+
"node .rulesync/scripts/hooks-union.mjs",
|
|
41
|
+
"node scripts/hooks-union.mjs",
|
|
42
|
+
],
|
|
43
|
+
"repo:hooks:check": [
|
|
44
|
+
"bun .rulesync/scripts/hooks-union.ts --check",
|
|
45
|
+
"node .rulesync/scripts/hooks-union.mjs --check",
|
|
46
|
+
"node scripts/hooks-union.mjs --check",
|
|
47
|
+
],
|
|
48
|
+
"repo:doctor": [
|
|
49
|
+
"repo doctor",
|
|
50
|
+
"bun ./.rulesync/scripts/doctor.ts",
|
|
51
|
+
"node ./.rulesync/scripts/doctor.mjs",
|
|
52
|
+
"node ./scripts/doctor.mjs",
|
|
53
|
+
],
|
|
54
|
+
"repo:clean": ["repo clean", "bun ./.rulesync/scripts/clean.ts", "node ./.rulesync/scripts/clean.mjs"],
|
|
55
|
+
"repo:setup": [
|
|
56
|
+
"repo setup",
|
|
57
|
+
"bun ./.rulesync/scripts/setup.ts",
|
|
58
|
+
"node ./.rulesync/scripts/setup.mjs",
|
|
59
|
+
"node ./scripts/setup.mjs",
|
|
60
|
+
],
|
|
61
|
+
"repo:tasks": ["repo tasks", "bun .rulesync/scripts/vscode-tasks.ts", "node .rulesync/scripts/vscode-tasks.mjs"],
|
|
62
|
+
"repo:rulesync:doctor": ["rulesync doctor"],
|
|
63
|
+
"repo:sync:globals": [
|
|
64
|
+
"repo sync-globals",
|
|
65
|
+
"bun .rulesync/scripts/sync-global-settings.ts",
|
|
66
|
+
"node .rulesync/scripts/sync-global-settings.mjs",
|
|
67
|
+
"node scripts/sync-global-settings.mjs",
|
|
68
|
+
],
|
|
69
|
+
"repo:sync:globals:check": [
|
|
70
|
+
"bun .rulesync/scripts/check-globals.ts",
|
|
71
|
+
"node .rulesync/scripts/check-globals.mjs",
|
|
72
|
+
"node scripts/check-globals.mjs",
|
|
73
|
+
],
|
|
74
|
+
"repo:sync:globals:apply": [
|
|
75
|
+
"repo sync-globals --apply",
|
|
76
|
+
"bun .rulesync/scripts/sync-global-settings.ts --apply",
|
|
77
|
+
"node .rulesync/scripts/sync-global-settings.mjs --apply",
|
|
78
|
+
"node scripts/sync-global-settings.mjs --apply",
|
|
79
|
+
],
|
|
80
|
+
"repo:purge:global-mcps": [
|
|
81
|
+
"repo purge-mcps",
|
|
82
|
+
"bun .rulesync/scripts/purge-global-mcps.ts",
|
|
83
|
+
"node .rulesync/scripts/purge-global-mcps.mjs",
|
|
84
|
+
"node scripts/purge-global-mcps.mjs",
|
|
85
|
+
],
|
|
86
|
+
"repo:purge:global-mcps:apply": [
|
|
87
|
+
"repo purge-mcps --apply",
|
|
88
|
+
"bun .rulesync/scripts/purge-global-mcps.ts --apply",
|
|
89
|
+
"node .rulesync/scripts/purge-global-mcps.mjs --apply",
|
|
90
|
+
"node scripts/purge-global-mcps.mjs --apply",
|
|
91
|
+
],
|
|
92
|
+
"repo:typecheck": ["tsc -p ."],
|
|
93
|
+
"repo:test:e2e": ["vitest run -c vitest.e2e.config.ts"],
|
|
94
|
+
"repo:antigravity": ["antigravity --dangerously-skip-permissions"],
|
|
95
|
+
"repo:claude": ["claude --dangerously-skip-permissions"],
|
|
96
|
+
"repo:codex": ["codex --dangerously-bypass-approvals-and-sandbox"],
|
|
97
|
+
"repo:gemini": ["gemini --yolo"],
|
|
98
|
+
"repo:grok": ["grok --yolo"],
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
function sortKeys<T>(obj: Record<string, T>): Record<string, T> {
|
|
102
|
+
// k comes from Object.keys(obj), so it is always present
|
|
103
|
+
return Object.fromEntries(
|
|
104
|
+
Object.keys(obj)
|
|
105
|
+
.sort()
|
|
106
|
+
.map((k) => [k, obj[k]!]),
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function samePath(a: string, b: string): boolean {
|
|
111
|
+
try {
|
|
112
|
+
return fs.realpathSync.native(a) === fs.realpathSync.native(b);
|
|
113
|
+
} catch {
|
|
114
|
+
return path.resolve(a) === path.resolve(b);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** The slice of package.json init reads; other keys pass through. */
|
|
119
|
+
interface PackageJson {
|
|
120
|
+
name?: string;
|
|
121
|
+
version?: string;
|
|
122
|
+
scripts?: Record<string, string>;
|
|
123
|
+
dependencies?: Record<string, string>;
|
|
124
|
+
devDependencies?: Record<string, string>;
|
|
125
|
+
[key: string]: unknown;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function isRecord(v: unknown): v is Record<string, unknown> {
|
|
129
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function isStringRecord(v: unknown): v is Record<string, string> {
|
|
133
|
+
return isRecord(v) && Object.values(v).every((x) => typeof x === "string");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** True when name and version are strings and the script and dependency maps hold only strings. */
|
|
137
|
+
function isPackageJson(v: unknown): v is PackageJson {
|
|
138
|
+
return (
|
|
139
|
+
isRecord(v) &&
|
|
140
|
+
[v.name, v.version].every((x) => x === undefined || typeof x === "string") &&
|
|
141
|
+
[v.scripts, v.dependencies, v.devDependencies].every((x) => x === undefined || isStringRecord(x))
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** The JSON file at `file`, refused with a clear error unless `is` accepts its shape. */
|
|
146
|
+
function readJsonFile<T>(file: string, is: (v: unknown) => v is T): T {
|
|
147
|
+
const value: unknown = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
148
|
+
if (!is(value)) throw new Error(`${file} does not hold the shape repo needs`);
|
|
149
|
+
return value;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function placeDevDependency(destPkg: PackageJson, srcPkg?: PackageJson): void {
|
|
153
|
+
const deps = destPkg.dependencies && typeof destPkg.dependencies === "object" ? destPkg.dependencies : null;
|
|
154
|
+
const devDeps: Record<string, string> = { ...(destPkg.devDependencies || {}) };
|
|
155
|
+
destPkg.devDependencies = devDeps;
|
|
156
|
+
const fromDep = deps?.[PACKAGE_NAME];
|
|
157
|
+
const fromDev = devDeps[PACKAGE_NAME];
|
|
158
|
+
if (fromDep != null && deps) {
|
|
159
|
+
delete deps[PACKAGE_NAME];
|
|
160
|
+
if (fromDev == null) devDeps[PACKAGE_NAME] = fromDep;
|
|
161
|
+
if (Object.keys(deps).length === 0) delete destPkg.dependencies;
|
|
162
|
+
}
|
|
163
|
+
if (devDeps[PACKAGE_NAME] == null && srcPkg?.name === PACKAGE_NAME && srcPkg.version) {
|
|
164
|
+
devDeps[PACKAGE_NAME] = srcPkg.version;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** The skill packs of a `--packs` list (comma separated, `none` or empty for no packs), once each in `SKILL_PACKS` order; an unknown name throws. */
|
|
169
|
+
export function parsePacksFlag(value: string): string[] {
|
|
170
|
+
const text = value.trim();
|
|
171
|
+
if (!text || text === "none") return [];
|
|
172
|
+
const names = text
|
|
173
|
+
.split(",")
|
|
174
|
+
.map((name) => name.trim())
|
|
175
|
+
.filter(Boolean);
|
|
176
|
+
for (const name of names) {
|
|
177
|
+
if (!SKILL_PACKS.some((pack) => pack === name)) throw new Error(`unknown pack ${name}`);
|
|
178
|
+
}
|
|
179
|
+
return SKILL_PACKS.filter((pack) => names.includes(pack));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const SYNC_POSTINSTALL = "repo sync --postinstall";
|
|
183
|
+
|
|
184
|
+
/** Write `workerstack.config.ts`, formatted as oxfmt would, with the default packs (agents, standards and docs), every host and the chosen skill packs, only when the repo has no config. */
|
|
185
|
+
export function writeConfigIfMissing(dest: string, skills: readonly string[] = []): void {
|
|
186
|
+
const configPath = path.join(dest, CONFIG_FILE);
|
|
187
|
+
if (fs.existsSync(configPath)) return;
|
|
188
|
+
const list = (names: readonly string[]) => names.map((n) => JSON.stringify(n)).join(", ");
|
|
189
|
+
const keys = [`hosts: [${list(HOSTS)}]`, ...(skills.length > 0 ? [`skills: [${list(skills)}]`] : [])];
|
|
190
|
+
// Laid out as the standards pack's oxfmt would: one line while it fits the width, else one key per line.
|
|
191
|
+
const inline = ` agents: { ${keys.join(", ")} },`;
|
|
192
|
+
const agents =
|
|
193
|
+
inline.length <= PRINT_WIDTH ? inline : ` agents: {\n${keys.map((key) => ` ${key},\n`).join("")} },`;
|
|
194
|
+
fs.writeFileSync(
|
|
195
|
+
configPath,
|
|
196
|
+
`import { defineConfig } from "${PACKAGE_NAME}";\n\nexport default defineConfig({\n packs: [${list(defaultPacks())}],\n${agents}\n});\n`,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** The root keys `repo check` requires; a value already there stays, even one check would fail. */
|
|
201
|
+
function defaultManifestKeys(pkg: PackageJson) {
|
|
202
|
+
pkg.private ??= true;
|
|
203
|
+
// A root with workspaces is not a package, so `repo check` does not ask it for sideEffects.
|
|
204
|
+
if (pkg.workspaces === undefined) pkg.sideEffects ??= false;
|
|
205
|
+
pkg.packageManager ??= `bun@${Bun.version}`;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Put the postinstall that runs `repo sync` and the `@workerstack/repo` devDependency into the repo's
|
|
210
|
+
* package.json, default `private`, `sideEffects` and `packageManager` when absent, and drop the `repo:*` scripts an older init copied while they still hold a value it wrote.
|
|
211
|
+
*/
|
|
212
|
+
export function mergePackageJson(dest: string, pkgRoot: string): void {
|
|
213
|
+
const destPath = path.join(dest, "package.json");
|
|
214
|
+
let destPkg: PackageJson = {};
|
|
215
|
+
if (fs.existsSync(destPath)) {
|
|
216
|
+
destPkg = readJsonFile(destPath, isPackageJson);
|
|
217
|
+
}
|
|
218
|
+
if (destPkg.name === PACKAGE_NAME) return;
|
|
219
|
+
|
|
220
|
+
const srcPkg = readJsonFile(path.join(pkgRoot, "package.json"), isPackageJson);
|
|
221
|
+
const destScripts: Record<string, string> = { ...(destPkg.scripts || {}) };
|
|
222
|
+
for (const [name, value] of Object.entries(destScripts)) {
|
|
223
|
+
if (OLD_DEFAULT_SCRIPTS[name]?.includes(value)) delete destScripts[name];
|
|
224
|
+
}
|
|
225
|
+
const postinstall = destScripts.postinstall;
|
|
226
|
+
if (postinstall == null) destScripts.postinstall = SYNC_POSTINSTALL;
|
|
227
|
+
else if (!postinstall.includes("repo sync")) destScripts.postinstall = `${SYNC_POSTINSTALL} && ${postinstall}`;
|
|
228
|
+
placeDevDependency(destPkg, srcPkg);
|
|
229
|
+
defaultManifestKeys(destPkg);
|
|
230
|
+
destPkg.scripts = sortKeys(destScripts);
|
|
231
|
+
destPkg.devDependencies = sortKeys(destPkg.devDependencies!);
|
|
232
|
+
fs.writeFileSync(destPath, `${JSON.stringify(destPkg, null, 2)}\n`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export interface InitOptions {
|
|
236
|
+
pkgRoot?: string;
|
|
237
|
+
execFileSync?: (cmd: string, args: string[], opts: import("node:child_process").ExecFileSyncOptions) => unknown;
|
|
238
|
+
skipGlobals?: boolean;
|
|
239
|
+
onPhase?: (name: string, state: "start" | "stop") => void;
|
|
240
|
+
/** Skill packs written as `agents.skills` into a new config. */
|
|
241
|
+
packs?: string[];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Write the config and package.json entries a repo needs, install, and apply the global settings. The agent
|
|
246
|
+
* trees are not copied: the postinstall of `bun install` runs `repo sync`, which generates them.
|
|
247
|
+
* @param dest Workspace to initialise.
|
|
248
|
+
* @param [options]
|
|
249
|
+
* @returns
|
|
250
|
+
*/
|
|
251
|
+
export function init(
|
|
252
|
+
dest: string,
|
|
253
|
+
{ pkgRoot: pkgRootOpt, execFileSync: exec = execFileSync, skipGlobals = false, onPhase, packs }: InitOptions = {},
|
|
254
|
+
): void {
|
|
255
|
+
dest = path.resolve(dest);
|
|
256
|
+
const pkgRoot = path.resolve(pkgRootOpt ?? PKG_ROOT);
|
|
257
|
+
// This package is its own workspace: nothing to write, install or apply.
|
|
258
|
+
if (samePath(dest, pkgRoot)) return;
|
|
259
|
+
|
|
260
|
+
onPhase?.("merge", "start");
|
|
261
|
+
try {
|
|
262
|
+
mergePackageJson(dest, pkgRoot);
|
|
263
|
+
writeConfigIfMissing(dest, packs);
|
|
264
|
+
} finally {
|
|
265
|
+
onPhase?.("merge", "stop");
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const bun = (name: string, args: string[]) => {
|
|
269
|
+
onPhase?.(name, "start");
|
|
270
|
+
onPhase?.(name, "stop");
|
|
271
|
+
exec("bun", args, { cwd: dest, stdio: "inherit" });
|
|
272
|
+
};
|
|
273
|
+
bun("install", ["install"]);
|
|
274
|
+
if (!skipGlobals) bun("yolo", ["repo", "yolo"]);
|
|
275
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { answering, knipRoot, removeKnipRoots, report } from "./fixtures/knip-run.ts";
|
|
3
|
+
import { checkKnip } from "./knip-check.ts";
|
|
4
|
+
import type { KnipIgnore } from "./knip-schema.ts";
|
|
5
|
+
|
|
6
|
+
afterEach(removeKnipRoots);
|
|
7
|
+
|
|
8
|
+
const item = { name: "oxlint", line: 1, col: 1, pos: 0 };
|
|
9
|
+
const dep = (reason = "spawned by path"): KnipIgnore => ({ dependency: "oxlint", reason });
|
|
10
|
+
|
|
11
|
+
/** Checks `ignores` against `issues` in a root with `files` and a manifest depending on oxlint. */
|
|
12
|
+
function check(ignores: KnipIgnore[], issues: Record<string, unknown>[], files: Record<string, string> = {}) {
|
|
13
|
+
return checkKnip(knipRoot(files, { oxlint: "1" }), ignores, answering(report(...issues)));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe("an ignore matched by issues", () => {
|
|
17
|
+
it("is no problem and is reported with its reason", () => {
|
|
18
|
+
const out = check([dep()], [{ file: "package.json", dependencies: [item] }]);
|
|
19
|
+
expect(out).toEqual({ report: ["knip: 1 ignore: dependency oxlint (spawned by path)"], problems: [] });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("a dependency entry takes the unused, dev, optional peer and unlisted issues of that name only", () => {
|
|
23
|
+
const issues = [
|
|
24
|
+
{ file: "package.json", devDependencies: [item], optionalPeerDependencies: [item], dependencies: [item] },
|
|
25
|
+
{ file: "a.ts", unlisted: [item, { ...item, name: "other" }], binaries: [item] },
|
|
26
|
+
];
|
|
27
|
+
expect(check([dep()], issues).problems).toEqual([
|
|
28
|
+
'knip: a.ts: unlisted dependency "other"; add it to package.json',
|
|
29
|
+
'knip: a.ts: unlisted binary "oxlint"; add the package that provides it to package.json',
|
|
30
|
+
]);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("a binary entry takes the binaries of that name", () => {
|
|
34
|
+
const out = check(
|
|
35
|
+
[{ binary: "oxlint", reason: "run by a hook" }],
|
|
36
|
+
[{ file: "a.ts", binaries: [item, { ...item, name: "tsc" }], dependencies: [item] }],
|
|
37
|
+
);
|
|
38
|
+
expect(out.report).toEqual(["knip: 1 ignore: binary oxlint (run by a hook)"]);
|
|
39
|
+
expect(out.problems).toEqual([
|
|
40
|
+
'knip: a.ts: unused dependency "oxlint"; remove it',
|
|
41
|
+
'knip: a.ts: unlisted binary "tsc"; add the package that provides it to package.json',
|
|
42
|
+
]);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe("an ignore matched by files and several entries", () => {
|
|
47
|
+
it("a file entry takes every issue of the files its path or glob matches", () => {
|
|
48
|
+
const issues = [
|
|
49
|
+
{ file: "src/old.ts", exports: [item], files: [{ name: "src/old.ts" }] },
|
|
50
|
+
{ file: "src/new.ts", exports: [item] },
|
|
51
|
+
];
|
|
52
|
+
const out = check([{ file: "src/old*.ts", reason: "loaded by name" }], issues, { "src/old.ts": "" });
|
|
53
|
+
expect(out.report).toEqual(["knip: 1 ignore: file src/old*.ts (loaded by name)"]);
|
|
54
|
+
expect(out.problems).toEqual(['knip: src/new.ts: unused export "oxlint"; delete it or stop exporting it']);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("several entries make one line, plural", () => {
|
|
58
|
+
const issues = [
|
|
59
|
+
{ file: "package.json", dependencies: [item] },
|
|
60
|
+
{ file: "b.ts", binaries: [item] },
|
|
61
|
+
];
|
|
62
|
+
const out = check([dep("r1"), { binary: "oxlint", reason: "r2" }], issues);
|
|
63
|
+
expect(out.report).toEqual(["knip: 2 ignores: dependency oxlint (r1), binary oxlint (r2)"]);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe("a stale entry", () => {
|
|
68
|
+
it("a file that no longer exists is stale before the run", () => {
|
|
69
|
+
const out = check([{ file: "src/old.ts", reason: "r" }], [{ file: "src/old.ts", exports: [item] }]);
|
|
70
|
+
expect(out.problems).toEqual(["knip.ignore[0]: src/old.ts no longer exists; delete the entry"]);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("a dependency in no package.json is stale before the run", () => {
|
|
74
|
+
const out = check([{ dependency: "left-pad", reason: "r" }], []);
|
|
75
|
+
expect(out.problems).toEqual(['knip.ignore[0]: dependency "left-pad" is in no package.json; delete the entry']);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe("a stale entry once knip has run", () => {
|
|
80
|
+
it("a dependency of a workspace package is in a package.json", () => {
|
|
81
|
+
const ws = knipRoot({});
|
|
82
|
+
const manifest = { name: "a", devDependencies: { "left-pad": "1" } };
|
|
83
|
+
const stdout = report({ file: "a/package.json", devDependencies: [{ name: "left-pad" }] });
|
|
84
|
+
const out = checkKnip(
|
|
85
|
+
{ ...ws, packages: [{ dir: "a", manifest }] },
|
|
86
|
+
[{ dependency: "left-pad", reason: "r" }],
|
|
87
|
+
answering(stdout),
|
|
88
|
+
);
|
|
89
|
+
expect(out.problems).toEqual([]);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("an entry that matches no issue is stale after the run", () => {
|
|
93
|
+
const issues = [{ file: "b.ts", exports: [item] }];
|
|
94
|
+
const out = check([dep(), { file: "a.ts", reason: "r" }, { binary: "tsc", reason: "r" }], issues, {
|
|
95
|
+
"a.ts": "",
|
|
96
|
+
"b.ts": "",
|
|
97
|
+
});
|
|
98
|
+
expect(out.problems).toEqual([
|
|
99
|
+
'knip: b.ts: unused export "oxlint"; delete it or stop exporting it',
|
|
100
|
+
'knip.ignore[0]: dependency "oxlint" has no knip issue any more; delete the entry',
|
|
101
|
+
'knip.ignore[1]: file "a.ts" has no knip issue any more; delete the entry',
|
|
102
|
+
'knip.ignore[2]: binary "tsc" has no knip issue any more; delete the entry',
|
|
103
|
+
]);
|
|
104
|
+
expect(out.report).toEqual([]);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { afterEach, expect, it } from "vitest";
|
|
4
|
+
import { knipRoot, removeKnipRoots } from "./fixtures/knip-run.ts";
|
|
5
|
+
import { checkKnipAt } from "./knip-check.ts";
|
|
6
|
+
|
|
7
|
+
afterEach(removeKnipRoots);
|
|
8
|
+
|
|
9
|
+
const SLOW = 120_000;
|
|
10
|
+
const INSTALL = path.join(import.meta.dirname, "..", "node_modules");
|
|
11
|
+
|
|
12
|
+
it(
|
|
13
|
+
"runs the real knip on a planted repo: an unused export and dependency fail, an ignore reports",
|
|
14
|
+
{
|
|
15
|
+
timeout: SLOW,
|
|
16
|
+
},
|
|
17
|
+
async () => {
|
|
18
|
+
const files = {
|
|
19
|
+
"src/index.ts": 'import { used } from "./lib.ts";\nconsole.log(used);\n',
|
|
20
|
+
"src/lib.ts": "export const used = 1;\nexport const unused = 2;\n",
|
|
21
|
+
"src/old.ts": "export const old = 1;\n",
|
|
22
|
+
};
|
|
23
|
+
const ws = knipRoot(files, { "left-pad": "1.0.0" });
|
|
24
|
+
fs.symlinkSync(INSTALL, path.join(ws.root, "node_modules"), "junction");
|
|
25
|
+
fs.writeFileSync(path.join(ws.root, "package.json"), JSON.stringify({ ...ws.manifest, main: "src/index.ts" }));
|
|
26
|
+
const ignore = '{ file: "src/old.ts", reason: "loaded by name" }';
|
|
27
|
+
fs.writeFileSync(
|
|
28
|
+
path.join(ws.root, "workerstack.config.ts"),
|
|
29
|
+
`export default { packs: ["standards"], knip: { ignore: [${ignore}] } };\n`,
|
|
30
|
+
);
|
|
31
|
+
const out = await checkKnipAt(ws.root);
|
|
32
|
+
expect(out.report).toEqual(["knip: 1 ignore: file src/old.ts (loaded by name)"]);
|
|
33
|
+
expect(out.problems.toSorted()).toEqual([
|
|
34
|
+
'knip: package.json: unused dependency "left-pad"; remove it',
|
|
35
|
+
'knip: src/lib.ts: unused export "unused"; delete it or stop exporting it',
|
|
36
|
+
]);
|
|
37
|
+
expect(fs.readdirSync(ws.root).filter((name) => name.startsWith("knip."))).toEqual([]);
|
|
38
|
+
},
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
it(
|
|
42
|
+
"keeps knip's default entries: src/index.ts and what it imports are used with no main",
|
|
43
|
+
{ timeout: SLOW },
|
|
44
|
+
async () => {
|
|
45
|
+
const files = {
|
|
46
|
+
"src/index.ts": 'import { answer } from "./answer.ts";\nconsole.log(answer);\n',
|
|
47
|
+
"src/answer.ts": "export const answer = 42;\n",
|
|
48
|
+
};
|
|
49
|
+
const ws = knipRoot(files, {});
|
|
50
|
+
fs.symlinkSync(INSTALL, path.join(ws.root, "node_modules"), "junction");
|
|
51
|
+
fs.writeFileSync(path.join(ws.root, "workerstack.config.ts"), 'export default { packs: ["standards"] };\n');
|
|
52
|
+
const out = await checkKnipAt(ws.root);
|
|
53
|
+
expect(out.problems).toEqual([]);
|
|
54
|
+
},
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
it("does not report @workerstack/repo as unused when the config does not import it", { timeout: SLOW }, async () => {
|
|
58
|
+
const ws = knipRoot({ "src/index.ts": "export {};\n" }, {});
|
|
59
|
+
fs.symlinkSync(INSTALL, path.join(ws.root, "node_modules"), "junction");
|
|
60
|
+
const manifest = { name: "app", type: "module", devDependencies: { "@workerstack/repo": "1.0.0" } };
|
|
61
|
+
fs.writeFileSync(path.join(ws.root, "package.json"), JSON.stringify(manifest));
|
|
62
|
+
fs.writeFileSync(path.join(ws.root, "workerstack.config.ts"), 'export default { packs: ["standards"] };\n');
|
|
63
|
+
const out = await checkKnipAt(ws.root);
|
|
64
|
+
expect(out.problems).toEqual([]);
|
|
65
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** knip's exit code for a config it cannot run with. */
|
|
2
|
+
export const EXIT_BAD_CONFIG = 2;
|
|
3
|
+
/** How many of knip's last stderr lines a failure problem quotes. */
|
|
4
|
+
export const STDERR_LINES = 3;
|
|
5
|
+
|
|
6
|
+
/** How a problem line names one issue type: what is wrong, and what fixes it. */
|
|
7
|
+
export type Wording = { type: string; label: string; fix: string };
|
|
8
|
+
|
|
9
|
+
/** The issue type of a file nothing imports, which names no export. */
|
|
10
|
+
export const FILES_TYPE = "files";
|
|
11
|
+
/** The issue type of a command no manifest provides. */
|
|
12
|
+
export const BINARIES_TYPE = "binaries";
|
|
13
|
+
const DELETE_OR_UNEXPORT = "delete it or stop exporting it";
|
|
14
|
+
const REMOVE = "remove it";
|
|
15
|
+
const ADD_TO_MANIFEST = "add it to package.json";
|
|
16
|
+
|
|
17
|
+
/** Every issue type knip 6.43.0 reports, in the order a file's issues are printed. */
|
|
18
|
+
export const WORDINGS: readonly Wording[] = [
|
|
19
|
+
{ type: FILES_TYPE, label: "unused file", fix: "delete it" },
|
|
20
|
+
{ type: "dependencies", label: "unused dependency", fix: REMOVE },
|
|
21
|
+
{ type: "devDependencies", label: "unused devDependency", fix: REMOVE },
|
|
22
|
+
{ type: "optionalPeerDependencies", label: "unused optional peer dependency", fix: REMOVE },
|
|
23
|
+
{ type: "unlisted", label: "unlisted dependency", fix: ADD_TO_MANIFEST },
|
|
24
|
+
{ type: BINARIES_TYPE, label: "unlisted binary", fix: "add the package that provides it to package.json" },
|
|
25
|
+
{ type: "unresolved", label: "unresolved import", fix: "fix the path or install the package" },
|
|
26
|
+
{ type: "exports", label: "unused export", fix: DELETE_OR_UNEXPORT },
|
|
27
|
+
{ type: "nsExports", label: "unused namespace export", fix: DELETE_OR_UNEXPORT },
|
|
28
|
+
{ type: "types", label: "unused type", fix: DELETE_OR_UNEXPORT },
|
|
29
|
+
{ type: "nsTypes", label: "unused namespace type", fix: DELETE_OR_UNEXPORT },
|
|
30
|
+
{ type: "enumMembers", label: "unused enum member", fix: "delete it" },
|
|
31
|
+
{ type: "namespaceMembers", label: "unused namespace member", fix: "delete it" },
|
|
32
|
+
{ type: "duplicates", label: "duplicate export", fix: "export it under one name" },
|
|
33
|
+
{ type: "catalog", label: "unused catalog entry", fix: REMOVE },
|
|
34
|
+
{ type: "catalogReferences", label: "unresolved catalog reference", fix: "add it to the catalog" },
|
|
35
|
+
{ type: "cycles", label: "circular import", fix: "break the cycle" },
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
/** The issue types a `dependency` ignore covers. */
|
|
39
|
+
export const DEPENDENCY_TYPES: readonly string[] = [
|
|
40
|
+
"dependencies",
|
|
41
|
+
"devDependencies",
|
|
42
|
+
"optionalPeerDependencies",
|
|
43
|
+
"unlisted",
|
|
44
|
+
];
|
|
45
|
+
/** The manifest fields a dependency can be declared in. */
|
|
46
|
+
export const MANIFEST_FIELDS = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"] as const;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
4
|
+
import { answering, knipRoot, removeKnipRoots, report } from "./fixtures/knip-run.ts";
|
|
5
|
+
import { checkKnip, checkKnipAt } from "./knip-check.ts";
|
|
6
|
+
import { knipConfig } from "./sync/knip-config.ts";
|
|
7
|
+
|
|
8
|
+
afterEach(removeKnipRoots);
|
|
9
|
+
|
|
10
|
+
const run = (issues: Record<string, unknown>[]) => checkKnip(knipRoot({}), [], answering(report(...issues)));
|
|
11
|
+
const item = { name: "x", line: 1, col: 1, pos: 0 };
|
|
12
|
+
|
|
13
|
+
describe("one problem line per issue type", () => {
|
|
14
|
+
it.each([
|
|
15
|
+
["files", { name: "cli/x.ts" }, "knip: cli/x.ts: unused file; delete it"],
|
|
16
|
+
["dependencies", item, 'knip: cli/x.ts: unused dependency "x"; remove it'],
|
|
17
|
+
["devDependencies", item, 'knip: cli/x.ts: unused devDependency "x"; remove it'],
|
|
18
|
+
["optionalPeerDependencies", item, 'knip: cli/x.ts: unused optional peer dependency "x"; remove it'],
|
|
19
|
+
["unlisted", item, 'knip: cli/x.ts: unlisted dependency "x"; add it to package.json'],
|
|
20
|
+
["binaries", item, 'knip: cli/x.ts: unlisted binary "x"; add the package that provides it to package.json'],
|
|
21
|
+
["unresolved", item, 'knip: cli/x.ts: unresolved import "x"; fix the path or install the package'],
|
|
22
|
+
["exports", item, 'knip: cli/x.ts: unused export "x"; delete it or stop exporting it'],
|
|
23
|
+
["nsExports", item, 'knip: cli/x.ts: unused namespace export "x"; delete it or stop exporting it'],
|
|
24
|
+
["types", item, 'knip: cli/x.ts: unused type "x"; delete it or stop exporting it'],
|
|
25
|
+
["nsTypes", item, 'knip: cli/x.ts: unused namespace type "x"; delete it or stop exporting it'],
|
|
26
|
+
["enumMembers", { ...item, namespace: "E" }, 'knip: cli/x.ts: unused enum member "E.x"; delete it'],
|
|
27
|
+
["namespaceMembers", { ...item, namespace: "n" }, 'knip: cli/x.ts: unused namespace member "n.x"; delete it'],
|
|
28
|
+
["duplicates", item, 'knip: cli/x.ts: duplicate export "x"; export it under one name'],
|
|
29
|
+
["catalog", item, 'knip: cli/x.ts: unused catalog entry "x"; remove it'],
|
|
30
|
+
["catalogReferences", item, 'knip: cli/x.ts: unresolved catalog reference "x"; add it to the catalog'],
|
|
31
|
+
["cycles", item, 'knip: cli/x.ts: circular import "x"; break the cycle'],
|
|
32
|
+
])("%s", (type, entry, line) => {
|
|
33
|
+
expect(run([{ file: "cli/x.ts", [type]: [entry] }])).toEqual({ report: [], problems: [line] });
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("reports each issue of each file in order, and nothing for a clean run", () => {
|
|
38
|
+
const manifest = { file: "package.json", dependencies: [{ name: "left-pad" }] };
|
|
39
|
+
const source = { file: "cli/x.ts", exports: [item, { ...item, name: "y" }] };
|
|
40
|
+
expect(run([manifest, source]).problems).toEqual([
|
|
41
|
+
'knip: package.json: unused dependency "left-pad"; remove it',
|
|
42
|
+
'knip: cli/x.ts: unused export "x"; delete it or stop exporting it',
|
|
43
|
+
'knip: cli/x.ts: unused export "y"; delete it or stop exporting it',
|
|
44
|
+
]);
|
|
45
|
+
expect(run([])).toEqual({ report: [], problems: [] });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("a knip that did not report", () => {
|
|
49
|
+
const TAIL = "run `bun repo sync` and check knip.json";
|
|
50
|
+
|
|
51
|
+
it("exit 2 is one problem naming the tail of its stderr", () => {
|
|
52
|
+
const out = checkKnip(knipRoot({}), [], answering("", 2, "a\nb\nc\nd\n"));
|
|
53
|
+
expect(out).toEqual({ report: [], problems: [`knip: exited 2: b c d; ${TAIL}`] });
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("output that is not knip's json is one problem", () => {
|
|
57
|
+
const out = checkKnip(knipRoot({}), [], answering("not json", 0, "boom"));
|
|
58
|
+
expect(out).toEqual({ report: [], problems: [`knip: exited 0: boom; ${TAIL}`] });
|
|
59
|
+
expect(checkKnip(knipRoot({}), [], answering('{"issues":3}')).problems).toHaveLength(1);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("the unignored config", () => {
|
|
64
|
+
it("is written beside knip.json for the run from the workspace without ignores, and removed after", () => {
|
|
65
|
+
const ws = knipRoot({});
|
|
66
|
+
const name = `knip.unignored-${process.pid}.json`;
|
|
67
|
+
let seen = "";
|
|
68
|
+
const out = checkKnip(ws, [{ file: "gone.ts", reason: "r" }], (args, cwd) => {
|
|
69
|
+
seen = fs.readFileSync(path.join(ws.root, name), "utf8");
|
|
70
|
+
expect(args.slice(-5)).toEqual(["--config", name, "--no-exit-code", "--reporter", "json"]);
|
|
71
|
+
expect(cwd).toBe(ws.root);
|
|
72
|
+
return { code: 0, stdout: report(), stderr: "" };
|
|
73
|
+
});
|
|
74
|
+
expect(seen).toBe(knipConfig(ws, []));
|
|
75
|
+
expect(fs.existsSync(path.join(ws.root, name))).toBe(false);
|
|
76
|
+
expect(out.problems).toHaveLength(1);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("is removed when the runner throws", () => {
|
|
80
|
+
const ws = knipRoot({});
|
|
81
|
+
const boom = () => {
|
|
82
|
+
throw new Error("spawn failed");
|
|
83
|
+
};
|
|
84
|
+
expect(() => checkKnip(ws, [], boom)).toThrow("spawn failed");
|
|
85
|
+
expect(fs.readdirSync(ws.root)).toEqual(["package.json"]);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("checkKnipAt", () => {
|
|
90
|
+
const NONE = { report: [], problems: [] };
|
|
91
|
+
const pack = (root: string, body: string) =>
|
|
92
|
+
fs.writeFileSync(path.join(root, "workerstack.config.ts"), `export default ${body};\n`);
|
|
93
|
+
|
|
94
|
+
it("runs nothing without a config, without the standards pack or with an unreadable manifest", async () => {
|
|
95
|
+
const { root } = knipRoot({});
|
|
96
|
+
const runner = answering(report({ file: "a.ts", exports: [item] }));
|
|
97
|
+
expect(await checkKnipAt(root)).toEqual(NONE);
|
|
98
|
+
pack(root, '{ packs: ["docs"] }');
|
|
99
|
+
expect(await checkKnipAt(root, { run: runner })).toEqual(NONE);
|
|
100
|
+
pack(root, '{ packs: ["standards"] }');
|
|
101
|
+
fs.writeFileSync(path.join(root, "package.json"), "{");
|
|
102
|
+
expect(await checkKnipAt(root, { run: runner })).toEqual(NONE);
|
|
103
|
+
expect(runner.calls).toEqual([]);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("runs knip with the config's ignores under the standards pack", async () => {
|
|
107
|
+
const { root } = knipRoot({ "a.ts": "export {};\n" });
|
|
108
|
+
pack(root, '{ packs: ["standards"], knip: { ignore: [{ file: "a.ts", reason: "loaded by name" }] } }');
|
|
109
|
+
const runner = answering(report({ file: "a.ts", files: [{ name: "a.ts" }] }));
|
|
110
|
+
const out = await checkKnipAt(root, { run: runner });
|
|
111
|
+
expect(out).toEqual({ report: ["knip: 1 ignore: file a.ts (loaded by name)"], problems: [] });
|
|
112
|
+
expect(runner.calls).toHaveLength(1);
|
|
113
|
+
});
|
|
114
|
+
});
|