@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
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The files `repo turn` gates: the index (`--staged`), the working tree against HEAD with the untracked files git
|
|
3
|
+
* does not ignore, or every file of one package. Each file carries the workspace package that owns it.
|
|
4
|
+
*/
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
|
|
9
|
+
/** Which files a turn covers; `rel` is root-relative POSIX, `""` for the root. */
|
|
10
|
+
export type Scope = { kind: "staged" } | { kind: "tree" } | { kind: "package"; rel: string };
|
|
11
|
+
/** One changed file; `owner` is the dir of the package owning it, `""` for the root. */
|
|
12
|
+
export type Change = { rel: string; deleted: boolean; owner: string };
|
|
13
|
+
export type ChangeSetResult = { ok: true; changes: Change[] } | { ok: false; errors: string[] };
|
|
14
|
+
|
|
15
|
+
/** git's empty tree: what a repo with no commit yet diffs against. */
|
|
16
|
+
const EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
17
|
+
|
|
18
|
+
/** git in the root: its stdout, and whether it exited 0. */
|
|
19
|
+
type Git = (args: string[]) => { ok: boolean; out: string };
|
|
20
|
+
type Entry = { rel: string; deleted: boolean };
|
|
21
|
+
type Entries = { ok: true; entries: Entry[] } | { ok: false; errors: string[] };
|
|
22
|
+
|
|
23
|
+
function gitIn(root: string): Git {
|
|
24
|
+
return (args) => {
|
|
25
|
+
const run = spawnSync("git", args, { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
26
|
+
return { ok: run.status === 0, out: run.stdout };
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** The deepest package dir holding `rel`, or `""` for the root. */
|
|
31
|
+
export function ownerOf(rel: string, packages: readonly string[]): string {
|
|
32
|
+
const holding = packages.filter((dir) => rel === dir || rel.startsWith(`${dir}/`));
|
|
33
|
+
return holding.sort((a, b) => a.length - b.length).at(-1) ?? "";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** `--name-status -z` output as path and whether git saw it deleted. */
|
|
37
|
+
function statuses(out: string): Entry[] {
|
|
38
|
+
const parts = out.split("\0").filter((part) => part !== "");
|
|
39
|
+
const entries: Entry[] = [];
|
|
40
|
+
for (let i = 0; i + 1 < parts.length; i += 2)
|
|
41
|
+
entries.push({ rel: parts[i + 1] as string, deleted: parts[i] === "D" });
|
|
42
|
+
return entries;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const names = (out: string): string[] => out.split("\0").filter((rel) => rel !== "");
|
|
46
|
+
|
|
47
|
+
/** The base the working tree is compared to: HEAD, or the empty tree before the first commit. */
|
|
48
|
+
function base(git: Git): string {
|
|
49
|
+
return git(["rev-parse", "--verify", "-q", "HEAD"]).ok ? "HEAD" : EMPTY_TREE;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const DIFF = ["--name-status", "-z", "--no-renames"];
|
|
53
|
+
const UNTRACKED = ["ls-files", "--others", "--exclude-standard", "-z"];
|
|
54
|
+
|
|
55
|
+
function staged(git: Git): Entries {
|
|
56
|
+
const index = statuses(git(["diff", "--cached", ...DIFF]).out);
|
|
57
|
+
const unstaged = new Set(names(git(["diff", "--name-only", "-z"]).out));
|
|
58
|
+
const partial = index.filter((entry) => unstaged.has(entry.rel));
|
|
59
|
+
if (partial.length === 0) return { ok: true, entries: index };
|
|
60
|
+
return {
|
|
61
|
+
ok: false,
|
|
62
|
+
errors: partial.map(
|
|
63
|
+
({ rel }) => `${rel} has staged and unstaged changes; stage the rest (git add ${rel}) or undo it`,
|
|
64
|
+
),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function tree(git: Git): Entries {
|
|
69
|
+
const tracked = statuses(git(["diff", base(git), ...DIFF]).out);
|
|
70
|
+
const untracked = names(git(UNTRACKED).out).map((rel) => ({ rel, deleted: false }));
|
|
71
|
+
return { ok: true, entries: [...tracked, ...untracked] };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Every file under `dir`, tracked or untracked; a tracked file missing from disk is a deletion. */
|
|
75
|
+
function whole(git: Git, root: string, dir: string): Entries {
|
|
76
|
+
const listed = names(git(["ls-files", "--cached", "--others", "--exclude-standard", "-z", "--", dir || "."]).out);
|
|
77
|
+
// An unmerged path is listed once per stage.
|
|
78
|
+
const entries = [...new Set(listed)].map((rel) => ({ rel, deleted: !fs.existsSync(path.join(root, rel)) }));
|
|
79
|
+
return { ok: true, entries };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** A submodule or a nested checkout is a directory entry: listed by git, gated by its own repo. */
|
|
83
|
+
function isDirectory(root: string, rel: string): boolean {
|
|
84
|
+
return rel.endsWith("/") || fs.lstatSync(path.join(root, rel), { throwIfNoEntry: false })?.isDirectory() === true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function listOf({ git, root }: { git: Git; root: string }, scope: Scope, owner: string | null): Entries {
|
|
88
|
+
if (scope.kind === "staged") return staged(git);
|
|
89
|
+
return owner === null ? tree(git) : whole(git, root, owner);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** The change set of `scope` under the git repo at `root`, sorted, each file with its owning package. */
|
|
93
|
+
export function changeSet(root: string, scope: Scope, packages: readonly string[]): ChangeSetResult {
|
|
94
|
+
const git = gitIn(root);
|
|
95
|
+
if (!git(["rev-parse", "--git-dir"]).ok) return { ok: false, errors: [`${root} is not in a git repo`] };
|
|
96
|
+
const scopeOwner = scope.kind === "package" ? ownerOf(scope.rel, packages) : null;
|
|
97
|
+
const listed = listOf({ git, root }, scope, scopeOwner);
|
|
98
|
+
if (!listed.ok) return listed;
|
|
99
|
+
const changes = listed.entries
|
|
100
|
+
.filter((entry) => !isDirectory(root, entry.rel))
|
|
101
|
+
.map((entry) => ({ ...entry, owner: ownerOf(entry.rel, packages) }))
|
|
102
|
+
.filter((change) => scopeOwner === null || change.owner === scopeOwner)
|
|
103
|
+
.sort((a, b) => Number(a.rel > b.rel) - Number(a.rel < b.rel));
|
|
104
|
+
return { ok: true, changes };
|
|
105
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assert every payload leaf in `agents/global-settings/` is present in its
|
|
3
|
+
* $HOME dest (generic subset check, skips keys matching `skipKeyPattern`), and
|
|
4
|
+
* that the project `.grok/config.toml` sets no [features] or [agent].
|
|
5
|
+
*/
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { isDeepStrictEqual } from "node:util";
|
|
9
|
+
import { parse as parseToml } from "smol-toml";
|
|
10
|
+
import { isRecord } from "./hook-input.ts";
|
|
11
|
+
import {
|
|
12
|
+
type JsonObject,
|
|
13
|
+
hostIdsOf,
|
|
14
|
+
loadManifest,
|
|
15
|
+
readDest,
|
|
16
|
+
readPayload,
|
|
17
|
+
resolveDest,
|
|
18
|
+
shouldSkip,
|
|
19
|
+
} from "./sync-global-settings.ts";
|
|
20
|
+
import { readEnv } from "../env.ts";
|
|
21
|
+
|
|
22
|
+
const APPLY_HINT = "run `bun repo yolo`";
|
|
23
|
+
const PROJECT_REL = ".grok/config.toml";
|
|
24
|
+
|
|
25
|
+
/** One settings key whose payload value is missing or different in the dest. */
|
|
26
|
+
export interface DriftEntry {
|
|
27
|
+
host: string;
|
|
28
|
+
dest: string;
|
|
29
|
+
key: string;
|
|
30
|
+
kind: "missing" | "differs";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** One host's check outcome: `ok`, drift (`missing`/`differs` entries), or an `unreadable` dest. */
|
|
34
|
+
export interface HostStatus {
|
|
35
|
+
id: string;
|
|
36
|
+
dest: string;
|
|
37
|
+
state: "ok" | "drift" | "unreadable";
|
|
38
|
+
drift: DriftEntry[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface CheckResult {
|
|
42
|
+
ok: boolean;
|
|
43
|
+
drift?: DriftEntry[];
|
|
44
|
+
error?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
type ProjectGrok = { error: string; value?: undefined } | { value: JsonObject; error?: undefined };
|
|
48
|
+
|
|
49
|
+
function isPlainObject(v: unknown): v is JsonObject {
|
|
50
|
+
return v != null && typeof v === "object" && !Array.isArray(v) && !(v instanceof Date);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Leaf `[path, value]` pairs of a payload; arrays and dates are leaves. */
|
|
54
|
+
function leaves(obj: JsonObject, skipRe: RegExp, prefix: string[] = []): [string[], unknown][] {
|
|
55
|
+
const out: [string[], unknown][] = [];
|
|
56
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
57
|
+
if (shouldSkip(k, skipRe)) continue;
|
|
58
|
+
const next = [...prefix, k];
|
|
59
|
+
if (isPlainObject(v)) out.push(...leaves(v, skipRe, next));
|
|
60
|
+
else out.push([next, v]);
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function valueAt(obj: unknown, keyPath: string[]): { found: false } | { found: true; value: unknown } {
|
|
66
|
+
let cur = obj;
|
|
67
|
+
for (const k of keyPath) {
|
|
68
|
+
if (!isPlainObject(cur) || !Object.prototype.hasOwnProperty.call(cur, k)) {
|
|
69
|
+
return { found: false };
|
|
70
|
+
}
|
|
71
|
+
cur = cur[k];
|
|
72
|
+
}
|
|
73
|
+
return { found: true, value: cur };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function driftLine({ host, dest, key }: DriftEntry): string {
|
|
77
|
+
return `drift: ${host} ${dest} ${key}; ${APPLY_HINT}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function readProjectGrok(abs: string): ProjectGrok {
|
|
81
|
+
let raw: string;
|
|
82
|
+
try {
|
|
83
|
+
raw = fs.readFileSync(abs, "utf8");
|
|
84
|
+
} catch (err) {
|
|
85
|
+
if (isRecord(err) && err.code === "ENOENT") return { error: "missing project .grok/config.toml" };
|
|
86
|
+
return { error: "unreadable project .grok/config.toml" };
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
return { value: parseToml(raw) };
|
|
90
|
+
} catch {
|
|
91
|
+
return { error: "unreadable project .grok/config.toml" };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Per-host status for `host` ("all" or one id); an unreadable dest marks only its host. Throws on manifest or payload errors. */
|
|
96
|
+
export function checkHosts({
|
|
97
|
+
home,
|
|
98
|
+
workspaceRoot,
|
|
99
|
+
sourceRoot,
|
|
100
|
+
consumerRoot,
|
|
101
|
+
host = "all",
|
|
102
|
+
}: {
|
|
103
|
+
home: string;
|
|
104
|
+
workspaceRoot: string;
|
|
105
|
+
sourceRoot?: string | undefined;
|
|
106
|
+
consumerRoot?: string | undefined;
|
|
107
|
+
host?: string;
|
|
108
|
+
}): HostStatus[] {
|
|
109
|
+
const src = sourceRoot || workspaceRoot;
|
|
110
|
+
const manifest = loadManifest(src);
|
|
111
|
+
if (!hostIdsOf(manifest).has(host)) throw new Error(`unknown host: ${host}`);
|
|
112
|
+
const skipRe = new RegExp(manifest.skipKeyPattern, "i");
|
|
113
|
+
const out: HostStatus[] = [];
|
|
114
|
+
for (const h of manifest.hosts.filter((x) => host === "all" || x.id === host)) {
|
|
115
|
+
const payload = readPayload(src, h, consumerRoot || workspaceRoot);
|
|
116
|
+
let destValue: JsonObject;
|
|
117
|
+
try {
|
|
118
|
+
destValue = readDest(resolveDest(home, h.dest), h.format, h.dest);
|
|
119
|
+
} catch {
|
|
120
|
+
out.push({ id: h.id, dest: h.dest, state: "unreadable", drift: [] });
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const drift: DriftEntry[] = [];
|
|
124
|
+
for (const [keyPath, want] of leaves(payload, skipRe)) {
|
|
125
|
+
const have = valueAt(destValue, keyPath);
|
|
126
|
+
if (!have.found || !isDeepStrictEqual(have.value, want)) {
|
|
127
|
+
drift.push({ host: h.id, dest: h.dest, key: keyPath.join("."), kind: have.found ? "differs" : "missing" });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
out.push({ id: h.id, dest: h.dest, state: drift.length ? "drift" : "ok", drift });
|
|
131
|
+
}
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** `grok ok · claude missing a,b differs c · …`: hosts in manifest order. */
|
|
136
|
+
export function checkLine(statuses: HostStatus[]): string {
|
|
137
|
+
return statuses
|
|
138
|
+
.map((s) => {
|
|
139
|
+
if (s.state !== "drift") return `${s.id} ${s.state}`;
|
|
140
|
+
const parts = (["missing", "differs"] as const).flatMap((kind) => {
|
|
141
|
+
const keys = s.drift.filter((d) => d.kind === kind).map((d) => d.key);
|
|
142
|
+
return keys.length ? [`${kind} ${keys.join(",")}`] : [];
|
|
143
|
+
});
|
|
144
|
+
return `${s.id} ${parts.join(" ")}`;
|
|
145
|
+
})
|
|
146
|
+
.join(" · ");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** The apply hint, shown only when some host has drifted. */
|
|
150
|
+
export function checkHint(statuses: HostStatus[]): string | null {
|
|
151
|
+
return statuses.some((s) => s.state === "drift") ? APPLY_HINT : null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** The error for a project `.grok/config.toml` that is missing, unreadable or sets [features]/[agent]; null when fine or the same file as the home one. */
|
|
155
|
+
export function projectConfigError(workspaceRoot: string, home: string): string | null {
|
|
156
|
+
const projectConfig = path.resolve(workspaceRoot, PROJECT_REL);
|
|
157
|
+
if (projectConfig === path.resolve(home, PROJECT_REL)) return null;
|
|
158
|
+
const project = readProjectGrok(projectConfig);
|
|
159
|
+
if (project.error !== undefined) return project.error;
|
|
160
|
+
if (
|
|
161
|
+
Object.prototype.hasOwnProperty.call(project.value, "features") ||
|
|
162
|
+
Object.prototype.hasOwnProperty.call(project.value, "agent")
|
|
163
|
+
) {
|
|
164
|
+
return "project .grok/config.toml must not set [features] or [agent]";
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Manifest and payloads come from `sourceRoot` (default `workspaceRoot`), each replaced by a file of its name under
|
|
171
|
+
* `consumerRoot`'s `.repo/global-settings/` (default `workspaceRoot`); the project `.grok/config.toml` from `workspaceRoot`.
|
|
172
|
+
*/
|
|
173
|
+
export function checkGlobals({
|
|
174
|
+
home,
|
|
175
|
+
workspaceRoot,
|
|
176
|
+
sourceRoot,
|
|
177
|
+
consumerRoot,
|
|
178
|
+
}: {
|
|
179
|
+
home?: string;
|
|
180
|
+
workspaceRoot?: string;
|
|
181
|
+
sourceRoot?: string | undefined;
|
|
182
|
+
consumerRoot?: string | undefined;
|
|
183
|
+
} = {}): CheckResult {
|
|
184
|
+
const homeDir = home || readEnv().HOME;
|
|
185
|
+
const ws = workspaceRoot || process.cwd();
|
|
186
|
+
if (!homeDir) return { ok: false, error: `HOME required; ${APPLY_HINT}` };
|
|
187
|
+
let statuses: HostStatus[];
|
|
188
|
+
try {
|
|
189
|
+
statuses = checkHosts({ home: homeDir, workspaceRoot: ws, sourceRoot, consumerRoot });
|
|
190
|
+
} catch (err) {
|
|
191
|
+
return { ok: false, error: (err /* checkHosts throws only Error values */ as Error).message };
|
|
192
|
+
}
|
|
193
|
+
const unreadable = statuses.find((s) => s.state === "unreadable");
|
|
194
|
+
if (unreadable) return { ok: false, error: `unreadable dest: ${unreadable.dest}; ${APPLY_HINT}` };
|
|
195
|
+
const drift = statuses.flatMap((s) => s.drift);
|
|
196
|
+
if (drift.length) return { ok: false, drift };
|
|
197
|
+
const error = projectConfigError(ws, homeDir);
|
|
198
|
+
return error === null ? { ok: true, drift } : { ok: false, error };
|
|
199
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Vitest reporters for a gate run that also writes the JSON the skip report is read from. */
|
|
2
|
+
export const REPORT_REPORTERS: readonly string[] = ["--reporter=default", "--reporter=json"];
|
|
3
|
+
/** The flag that sends the JSON reporter's output to a file; the file follows. */
|
|
4
|
+
export const REPORT_OUTPUT_FLAG = "--outputFile.json=";
|
|
5
|
+
/** The file name of the JSON report inside the temp directory. */
|
|
6
|
+
export const REPORT_JSON_FILE = "vitest.json";
|
|
7
|
+
/** The prefix of the temp directory the JSON report is written to. */
|
|
8
|
+
export const REPORT_TEMP_PREFIX = "workerstack-ci-report-";
|
|
9
|
+
/** Test statuses that mean the test did not run. */
|
|
10
|
+
export const SKIPPED_STATUSES: readonly string[] = ["skipped", "pending", "todo"];
|
|
11
|
+
/** Separates a test file from the full test name in a skip id. */
|
|
12
|
+
export const ID_SEPARATOR = " > ";
|
|
13
|
+
/** A report file a test job uploads. */
|
|
14
|
+
export const REPORT_FILE: RegExp = /^ci-report-.*\.json$/;
|
|
15
|
+
/** Reports needed before a test can be said to be skipped on every OS. */
|
|
16
|
+
export const MIN_REPORTS = 2;
|
|
17
|
+
export const REPORT_SHAPE = "expected { os: string, skipped: string[] }";
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The skip report of `repo ci --report <file>` and the check of `repo ci skips <dir>`. Each OS's test job writes the
|
|
3
|
+
* ids of the tests that did not run; a test skipped in every report runs in no CI job, which the check refuses.
|
|
4
|
+
*/
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { ID_SEPARATOR, MIN_REPORTS, REPORT_FILE, REPORT_SHAPE, SKIPPED_STATUSES } from "./ci-skips.constants.ts";
|
|
9
|
+
|
|
10
|
+
/** Where relative paths start, and where lines go. */
|
|
11
|
+
export type SkipsIo = { cwd: string; stdout: (s: string) => void; stderr: (s: string) => void };
|
|
12
|
+
type Report = { os: string; skipped: string[] };
|
|
13
|
+
|
|
14
|
+
const vitestReport = () =>
|
|
15
|
+
z.object({
|
|
16
|
+
testResults: z.array(
|
|
17
|
+
z.object({
|
|
18
|
+
name: z.string(),
|
|
19
|
+
assertionResults: z.array(z.object({ fullName: z.string(), status: z.string() })),
|
|
20
|
+
}),
|
|
21
|
+
),
|
|
22
|
+
});
|
|
23
|
+
const ciReport = () => z.object({ os: z.string().min(1), skipped: z.array(z.string()) });
|
|
24
|
+
|
|
25
|
+
const posix = (file: string) => file.replaceAll("\\", "/");
|
|
26
|
+
|
|
27
|
+
/** The ids of the tests vitest's JSON report says did not run, sorted and unique; the paths are root-relative posix. */
|
|
28
|
+
export function skippedIds(json: unknown, root: string): string[] {
|
|
29
|
+
const parsed = vitestReport().safeParse(json);
|
|
30
|
+
if (!parsed.success) return [];
|
|
31
|
+
const ids = parsed.data.testResults.flatMap((file) =>
|
|
32
|
+
file.assertionResults
|
|
33
|
+
.filter((test) => SKIPPED_STATUSES.includes(test.status))
|
|
34
|
+
.map((test) => `${path.posix.relative(posix(root), posix(file.name))}${ID_SEPARATOR}${test.fullName}`),
|
|
35
|
+
);
|
|
36
|
+
return [...new Set(ids)].sort();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Writes `file` from the JSON report at `jsonFile`; a report that was not written or read has no skips. */
|
|
40
|
+
export function writeReport(file: string, jsonFile: string, root: string): void {
|
|
41
|
+
let json: unknown = null;
|
|
42
|
+
try {
|
|
43
|
+
json = JSON.parse(fs.readFileSync(jsonFile, "utf8"));
|
|
44
|
+
} catch {
|
|
45
|
+
json = null;
|
|
46
|
+
}
|
|
47
|
+
const report: Report = { os: process.platform, skipped: skippedIds(json, root) };
|
|
48
|
+
fs.writeFileSync(file, `${JSON.stringify(report)}\n`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The `ci-report-*.json` files of `dir` and of its subdirectories, as paths relative to `dir`. */
|
|
52
|
+
function reportFiles(dir: string): string[] {
|
|
53
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
54
|
+
const nested = entries
|
|
55
|
+
.filter((entry) => entry.isDirectory())
|
|
56
|
+
.flatMap((entry) =>
|
|
57
|
+
fs
|
|
58
|
+
.readdirSync(path.join(dir, entry.name), { withFileTypes: true })
|
|
59
|
+
.filter((inner) => inner.isFile() && REPORT_FILE.test(inner.name))
|
|
60
|
+
.map((inner) => `${entry.name}/${inner.name}`),
|
|
61
|
+
);
|
|
62
|
+
const top = entries.filter((entry) => entry.isFile() && REPORT_FILE.test(entry.name)).map((entry) => entry.name);
|
|
63
|
+
return [...top, ...nested].sort();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The report in `file`, or null with why it is not one. */
|
|
67
|
+
function readReport(file: string): { report: Report | null; reason: string } {
|
|
68
|
+
let json: unknown;
|
|
69
|
+
try {
|
|
70
|
+
json = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
71
|
+
} catch {
|
|
72
|
+
return { report: null, reason: "not valid JSON" };
|
|
73
|
+
}
|
|
74
|
+
const parsed = ciReport().safeParse(json);
|
|
75
|
+
return { report: parsed.success ? parsed.data : null, reason: REPORT_SHAPE };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Reads every report in `dir`; failures are the lines to print, one per problem. */
|
|
79
|
+
function readAll(dir: string, cwd: string): { reports: Report[] } | { failures: string[] } {
|
|
80
|
+
const at = path.resolve(cwd, dir);
|
|
81
|
+
let found: string[];
|
|
82
|
+
try {
|
|
83
|
+
found = reportFiles(at);
|
|
84
|
+
} catch {
|
|
85
|
+
return { failures: [`${dir} is not a directory; download the test jobs' reports into it first`] };
|
|
86
|
+
}
|
|
87
|
+
if (found.length === 0)
|
|
88
|
+
return { failures: [`${dir} has no ci-report-*.json; download the test jobs' reports first`] };
|
|
89
|
+
const read = found.map((rel) => ({ shown: `${dir}/${rel}`, got: readReport(path.join(at, rel)) }));
|
|
90
|
+
const failures = read
|
|
91
|
+
.filter(({ got }) => got.report === null)
|
|
92
|
+
.map(({ shown, got }) => `${shown} is not a ci report: ${got.reason}`);
|
|
93
|
+
if (failures.length > 0) return { failures };
|
|
94
|
+
return { reports: read.map(({ got }) => got.report).filter((report): report is Report => report !== null) };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** `repo ci skips <dir>`: fails each test skipped in every report; `dir` is shown as given. */
|
|
98
|
+
export function skipsMain(dir: string, io: SkipsIo): number {
|
|
99
|
+
const read = readAll(dir, io.cwd);
|
|
100
|
+
const fail = (lines: string[]) => {
|
|
101
|
+
for (const line of lines) io.stderr(`repo ci skips: ${line}\n`);
|
|
102
|
+
return 1;
|
|
103
|
+
};
|
|
104
|
+
if ("failures" in read) return fail(read.failures);
|
|
105
|
+
const oses = read.reports.map((report) => report.os).sort();
|
|
106
|
+
if (read.reports.length < MIN_REPORTS) {
|
|
107
|
+
return fail([`only ${read.reports.length} report (${oses.join(", ")}); every OS's test job must upload one`]);
|
|
108
|
+
}
|
|
109
|
+
const every = [...new Set(read.reports.flatMap((report) => report.skipped))];
|
|
110
|
+
const shared = every.filter((id) => read.reports.every((report) => report.skipped.includes(id)));
|
|
111
|
+
if (shared.length > 0) {
|
|
112
|
+
return fail(
|
|
113
|
+
shared.sort().map((id) => `${id} is skipped on every OS (${oses.join(", ")}); make it run on one or delete it`),
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
io.stdout(`ci skips: ${read.reports.length} reports (${oses.join(", ")}), no test skipped on all of them\n`);
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** The real git and gh runners of `repo ci wait`, and its real sleep. */
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { childEnv } from "../env.ts";
|
|
4
|
+
import { toolEnv } from "./lint.ts";
|
|
5
|
+
import type { Captured } from "./lint.ts";
|
|
6
|
+
|
|
7
|
+
/** Runs a tool with `args` in a directory, its output captured. */
|
|
8
|
+
export type Runner = (args: string[], cwd: string) => Captured;
|
|
9
|
+
|
|
10
|
+
/** A runner of `bin`; a tool that cannot start is a failed run with the reason as its stderr. */
|
|
11
|
+
export const spawnRunner =
|
|
12
|
+
(bin: string): Runner =>
|
|
13
|
+
(args, cwd) => {
|
|
14
|
+
const result = spawnSync(bin, args, { cwd, encoding: "utf8", env: toolEnv(childEnv()) });
|
|
15
|
+
if (result.error) return { code: 1, stdout: "", stderr: result.error.message };
|
|
16
|
+
return { code: result.status ?? 1, stdout: result.stdout, stderr: result.stderr };
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/** A runner of `bin` that shares this process's terminal, so the tool can prompt and its output is not captured. */
|
|
20
|
+
export const inheritRunner =
|
|
21
|
+
(bin: string): Runner =>
|
|
22
|
+
(args, cwd) => {
|
|
23
|
+
const result = spawnSync(bin, args, { cwd, stdio: "inherit", env: toolEnv(childEnv()) });
|
|
24
|
+
if (result.error) return { code: 1, stdout: "", stderr: result.error.message };
|
|
25
|
+
return { code: result.status ?? 1, stdout: "", stderr: "" };
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const realSleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** Milliseconds between looks for the run, and how long to look before giving up. */
|
|
2
|
+
export const POLL_MS = 10_000;
|
|
3
|
+
export const GIVE_UP_MS = 120_000;
|
|
4
|
+
export const MS_PER_SECOND = 1000;
|
|
5
|
+
/** Characters of a sha shown. */
|
|
6
|
+
export const SHA_SHOWN = 7;
|
|
7
|
+
const WORKFLOW = "ci.yml";
|
|
8
|
+
export const EXIT_PASSED = 0;
|
|
9
|
+
export const EXIT_FAILED = 1;
|
|
10
|
+
export const EXIT_SUPERSEDED = 2;
|
|
11
|
+
/** Job conclusions that are not a failure. */
|
|
12
|
+
export const NOT_FAILED: readonly string[] = ["success", "skipped", "neutral"];
|
|
13
|
+
export const CONCLUSION_SUCCESS = "success";
|
|
14
|
+
export const CONCLUSION_CANCELLED = "cancelled";
|
|
15
|
+
export const GIT_HEAD: readonly string[] = ["rev-parse", "HEAD"];
|
|
16
|
+
export const GIT_BRANCH: readonly string[] = ["rev-parse", "--abbrev-ref", "HEAD"];
|
|
17
|
+
export const GIT_REMOTES = (sha: string): string[] => ["branch", "-r", "--contains", sha];
|
|
18
|
+
export const GH_AUTH: readonly string[] = ["auth", "status"];
|
|
19
|
+
const GH_LIST_FIELDS = "databaseId,status,conclusion,event,headBranch,createdAt";
|
|
20
|
+
const GH_BRANCH_FIELDS = "databaseId,createdAt";
|
|
21
|
+
const GH_VIEW_FIELDS = "conclusion,jobs";
|
|
22
|
+
export const GH_LIST_COMMIT = (sha: string): string[] => [
|
|
23
|
+
"run",
|
|
24
|
+
"list",
|
|
25
|
+
"--commit",
|
|
26
|
+
sha,
|
|
27
|
+
"--workflow",
|
|
28
|
+
WORKFLOW,
|
|
29
|
+
"--json",
|
|
30
|
+
GH_LIST_FIELDS,
|
|
31
|
+
];
|
|
32
|
+
export const GH_LIST_BRANCH = (branch: string): string[] => [
|
|
33
|
+
"run",
|
|
34
|
+
"list",
|
|
35
|
+
"--branch",
|
|
36
|
+
branch,
|
|
37
|
+
"--workflow",
|
|
38
|
+
WORKFLOW,
|
|
39
|
+
"--json",
|
|
40
|
+
GH_BRANCH_FIELDS,
|
|
41
|
+
];
|
|
42
|
+
export const GH_WATCH = (id: number): string[] => ["run", "watch", String(id), "--exit-status"];
|
|
43
|
+
export const GH_VIEW = (id: number): string[] => ["run", "view", String(id), "--json", GH_VIEW_FIELDS];
|
|
44
|
+
export const GH_LOGS = (id: number): string[] => ["run", "view", String(id), "--log-failed"];
|