rvlib-shipkit 0.38.1 → 1.0.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 +367 -16
- package/README.md +26 -14
- package/package.json +2 -2
- package/src/assets/gen/comfyts-gen.ts +27 -10
- package/src/assets/gen/generate-roll.ts +1 -1
- package/src/assets/kit-config.ts +6 -6
- package/src/assets/run-assets.ts +14 -10
- package/src/assets/server/studio-server.ts +6 -5
- package/src/check.ts +343 -0
- package/src/{rules/SK001-package-json-shape.ts → checks/SK001-package-json-shape.check.ts} +44 -46
- package/src/checks/SK002-contracts-fulfillness.check.ts +24 -0
- package/src/checks/SK003-claude-md-shape.check.ts +66 -0
- package/src/checks/SK004-gitignore-policy.check.ts +309 -0
- package/src/{rules/SK005-rvlib-versions.ts → checks/SK005-rvlib-versions.check.ts} +46 -32
- package/src/{rules/SK006-npm-publish-safety.ts → checks/SK006-npm-publish-safety.check.ts} +25 -24
- package/src/{rules/SK007-github-visibility.ts → checks/SK007-github-visibility.check.ts} +20 -14
- package/src/{rules/SK008-reviewer-agent.ts → checks/SK008-reviewer-agent.check.ts} +29 -14
- package/src/{rules/SK009-commit-ai-tells.ts → checks/SK009-commit-ai-tells.check.ts} +24 -21
- package/src/checks/SK010-undeclared-unit.check.ts +111 -0
- package/src/{rules/SK011-sibling-unit-ranges.ts → checks/SK011-sibling-unit-ranges.check.ts} +21 -20
- package/src/{rules/SK012-config-barrel-import.ts → checks/SK012-config-barrel-import.check.ts} +18 -23
- package/src/{rules/SK013-one-way-in-policy.ts → checks/SK013-one-way-in-policy.check.ts} +58 -50
- package/src/{rules/SK014-prose-ste.ts → checks/SK014-prose-ste.check.ts} +27 -13
- package/src/{rules/SK015-nested-repo.ts → checks/SK015-nested-repo.check.ts} +15 -12
- package/src/checks/SK016-legacy-check-shapes.check.ts +182 -0
- package/src/checks/SK017-vscode-hub-task.check.ts +197 -0
- package/src/checks/SK018-test-files-run.check.ts +28 -0
- package/src/checks/SK019-tests-run-in-units.check.ts +76 -0
- package/src/checks/SK020-self-spawning-check.check.ts +147 -0
- package/src/checks/contracts.check.ts +20 -0
- package/src/cli/cmd_Asks.ts +3 -2
- package/src/cli/cmd_Assets.ts +2 -2
- package/src/cli/cmd_Bump.ts +1 -1
- package/src/cli/cmd_Check.ts +6 -6
- package/src/cli/cmd_Checks.ts +95 -0
- package/src/cli/cmd_Ci.ts +48 -13
- package/src/cli/cmd_Commands.ts +2 -6
- package/src/cli/cmd_ConfigKeys.ts +69 -0
- package/src/cli/cmd_Contracts.ts +1 -1
- package/src/cli/cmd_Fleet.ts +6 -2
- package/src/cli/cmd_Hooks.ts +1 -1
- package/src/cli/cmd_Keys.ts +43 -18
- package/src/cli/cmd_Mv.ts +2 -2
- package/src/cli/cmd_Prose.ts +10 -1
- package/src/cli/cmd_Releases.ts +2 -2
- package/src/cli/cmd_Serve.ts +1 -1
- package/src/cli/cmd_Start.ts +6 -6
- package/src/cli/cmd_Status.ts +25 -0
- package/src/cli/cmd_Tui.ts +2 -2
- package/src/cli.ts +7 -4
- package/src/collections/actions.ts +4 -4
- package/src/collections/checks.ts +258 -0
- package/src/collections/collection-types.ts +254 -1
- package/src/collections/comfy.ts +23 -0
- package/src/collections/md-body.ts +14 -0
- package/src/collections/pings.ts +38 -12
- package/src/collections/posts.ts +19 -3
- package/src/collections/providers.ts +198 -91
- package/src/collections/reflections.ts +41 -0
- package/src/collections/state-rules.ts +73 -0
- package/src/collections/test-time.ts +288 -0
- package/src/collections/tests.ts +181 -0
- package/src/collections/wikis.ts +66 -0
- package/src/comfy/comfy-panel.ts +292 -0
- package/src/contracts/apply-contracts.ts +17 -12
- package/src/contracts/contract-types.ts +16 -4
- package/src/contracts/contracts-engine.ts +78 -42
- package/src/contracts/manifests/discord.contract.ts +6 -6
- package/src/contracts/manifests/identity.contract.ts +3 -3
- package/src/contracts/manifests/itch.contract.ts +12 -15
- package/src/contracts/manifests/social.contract.ts +5 -5
- package/src/contracts/manifests/steam.contract.ts +16 -16
- package/src/contracts/run-contracts.ts +4 -3
- package/src/core/attach.ts +96 -0
- package/src/core/canonical-scripts.ts +25 -0
- package/src/core/check-ctx.ts +204 -14
- package/src/core/check-file-names.ts +112 -0
- package/src/core/check-pass.ts +84 -0
- package/src/core/check-runner.ts +796 -0
- package/src/core/config-keys.ts +256 -0
- package/src/core/config-rewrite.ts +77 -0
- package/src/core/discover-checks.ts +166 -90
- package/src/core/discover-legacy-checks.ts +26 -0
- package/src/core/doc-block.ts +13 -0
- package/src/core/floor-resolve.ts +264 -0
- package/src/core/floor.ts +20 -137
- package/src/core/git-bin.ts +24 -0
- package/src/core/git-root.ts +108 -0
- package/src/core/instance.ts +195 -0
- package/src/core/jobs.ts +118 -0
- package/src/core/layout.ts +52 -0
- package/src/core/legacy-shapes.ts +207 -0
- package/src/core/lib-root.ts +5 -0
- package/src/core/locked-write.ts +110 -39
- package/src/core/machine-commit.ts +5 -4
- package/src/core/migrate-layout.ts +557 -0
- package/src/core/motherhome.ts +2 -8
- package/src/core/nesting.ts +4 -12
- package/src/core/one-way-in-policy.ts +73 -17
- package/src/core/outcome-words.ts +45 -0
- package/src/core/package-manager.ts +86 -0
- package/src/core/parallel.ts +21 -0
- package/src/core/process-tree.ts +44 -0
- package/src/core/raw-access.ts +93 -0
- package/src/core/read-set.ts +152 -0
- package/src/core/run-check.ts +155 -213
- package/src/core/run-ci.ts +153 -189
- package/src/core/run-fix.ts +96 -0
- package/src/core/run-fixers.ts +53 -30
- package/src/core/self-update.ts +9 -3
- package/src/core/slots.ts +83 -0
- package/src/core/spawn-timeout.ts +34 -3
- package/src/core/when-block.ts +401 -0
- package/src/core/when.ts +135 -0
- package/src/core/write-status.ts +5 -2
- package/src/hooks/leak-guard.ts +10 -8
- package/src/hooks/one-way-in-guard.ts +13 -5
- package/src/hooks/root-identity.ts +2 -1
- package/src/hooks/run-hooks.ts +35 -23
- package/src/init-repo.ts +39 -23
- package/src/ledger/check-stats.ts +155 -0
- package/src/ledger/check-table.ts +179 -0
- package/src/ledger/db.ts +255 -0
- package/src/ledger/gate-checks.ts +433 -0
- package/src/ledger/git-key.ts +116 -77
- package/src/ledger/git-scan.ts +96 -35
- package/src/ledger/ledger.ts +116 -42
- package/src/ledger/live-runs.ts +111 -0
- package/src/ledger/live-timeline.ts +97 -0
- package/src/ledger/repo-view.ts +73 -0
- package/src/ledger/run-log.ts +448 -0
- package/src/ledger/run-timeline.ts +173 -0
- package/src/ledger/store.ts +23 -12
- package/src/ledger/suite-log.ts +161 -0
- package/src/ledger/toolchain-identity.ts +7 -1
- package/src/ledger/world-cache.ts +111 -0
- package/src/mv/mv-md.ts +16 -6
- package/src/problems/extract.ts +182 -0
- package/src/problems/problem-board.ts +37 -0
- package/src/problems/problem-line.ts +77 -0
- package/src/prose/prose-view.ts +90 -0
- package/src/prose/run-prose.ts +60 -19
- package/src/prose/ste.ts +140 -24
- package/src/reflections/reflections-queue.ts +81 -0
- package/src/release/git-facts.ts +15 -9
- package/src/release/run-bump.ts +6 -5
- package/src/release/run-release.ts +25 -11
- package/src/release/run-releases.ts +2 -2
- package/src/release/unit-gate.ts +7 -54
- package/src/release/unit-resolve.ts +282 -0
- package/src/release/units.ts +33 -217
- package/src/repo-config.ts +155 -85
- package/src/rules/rule.ts +36 -96
- package/src/rules/untrack-generated.ts +5 -4
- package/src/scaffold/files/vscode-tasks.json +10 -31
- package/src/scaffold/jsonc-spans.ts +113 -0
- package/src/scaffold/project-scaffold.ts +16 -43
- package/src/scan.ts +98 -0
- package/src/serve/gate-trigger.ts +148 -0
- package/src/serve/health-schedule.ts +102 -0
- package/src/serve/hub-app/chart-view.ts +179 -0
- package/src/serve/hub-app/check-view.tsx +345 -0
- package/src/serve/hub-app/check-words.ts +164 -0
- package/src/serve/hub-app/checks-svc.ts +280 -0
- package/src/serve/hub-app/clock-svc.ts +33 -0
- package/src/serve/hub-app/col-card.tsx +23 -39
- package/src/serve/hub-app/cols-svc.ts +44 -20
- package/src/serve/hub-app/comfy-svc.ts +171 -0
- package/src/serve/hub-app/comfy-view.tsx +81 -0
- package/src/serve/hub-app/dur.ts +41 -0
- package/src/serve/hub-app/hash-route.ts +121 -0
- package/src/serve/hub-app/header-bar.tsx +117 -0
- package/src/serve/hub-app/health-svc.ts +23 -28
- package/src/serve/hub-app/help.tsx +29 -0
- package/src/serve/hub-app/hub-app.tsx +122 -119
- package/src/serve/hub-app/hub-st.ts +115 -42
- package/src/serve/hub-app/hub-types.ts +117 -2
- package/src/serve/hub-app/hub.css +534 -192
- package/src/serve/hub-app/index.html +2 -0
- package/src/serve/hub-app/live-svc.ts +174 -0
- package/src/serve/hub-app/md.ts +15 -0
- package/src/serve/hub-app/media-svc.ts +4 -1
- package/src/serve/hub-app/media-view.tsx +15 -0
- package/src/serve/hub-app/next-run.ts +68 -0
- package/src/serve/hub-app/project-gutter.css +65 -0
- package/src/serve/hub-app/project-gutter.tsx +128 -0
- package/src/serve/hub-app/projects-svc.ts +226 -0
- package/src/serve/hub-app/prose-hits-st.ts +124 -0
- package/src/serve/hub-app/prose-hits-view.tsx +176 -0
- package/src/serve/hub-app/prose-hits.css +21 -0
- package/src/serve/hub-app/run-strip.ts +47 -0
- package/src/serve/hub-app/runs-tabs.ts +24 -0
- package/src/serve/hub-app/test-view.tsx +107 -0
- package/src/serve/hub-app/tests-svc.ts +62 -0
- package/src/serve/hub-app/tests-view.tsx +238 -0
- package/src/serve/hub-app/time-ago.tsx +34 -0
- package/src/serve/hub-app/timeline-chart.tsx +274 -0
- package/src/serve/hub-app/timeline-geometry.ts +314 -0
- package/src/serve/hub-app/timeline-svc.ts +62 -0
- package/src/serve/hub-app/timeline-view.tsx +245 -0
- package/src/serve/hub-app/tree-build.ts +410 -0
- package/src/serve/hub-app/tree-row.tsx +42 -0
- package/src/serve/hub-app/tree-svc.ts +174 -0
- package/src/serve/hub-app/wikis-view.tsx +53 -0
- package/src/serve/other-repos.ts +79 -0
- package/src/serve/project-gutter-route.ts +350 -0
- package/src/serve/project-gutter.ts +449 -0
- package/src/serve/run-serve.ts +245 -16
- package/src/serve/same-origin.ts +14 -0
- package/src/serve/serve-hub.ts +213 -18
- package/src/shipkit.ts +19 -12
- package/src/smoke/run-smoke.ts +3 -2
- package/src/start-repo.ts +75 -36
- package/src/suite/judge-suites.ts +209 -15
- package/src/suite/kept-timings.ts +58 -0
- package/src/suite/runner-shapes.ts +88 -0
- package/src/suite/suite-cli.ts +147 -24
- package/src/suite/suite-cpu-preload.ts +92 -0
- package/src/suite/test-units.ts +243 -0
- package/src/ts/config.ts +4 -69
- package/src/ts/jsonc.ts +69 -0
- package/src/ts/lex.ts +143 -0
- package/src/ts/rename.ts +73 -10
- package/src/ts/source-graph.ts +28 -8
- package/src/ts/tsconfig-projects.ts +6 -97
- package/src/ts/tsconfig-resolve.ts +108 -0
- package/src/tui/build-steps-panel.ts +61 -0
- package/src/tui/checks-panel.ts +24 -16
- package/src/tui/run-tui.ts +2 -2
- package/src/tui/tui-panel.ts +5 -4
- package/src/wiki/graph-colors.ts +68 -0
- package/src/wiki/graph-mount.ts +7 -9
- package/src/wiki/graph-page.ts +42 -25
- package/src/wiki/graph-positions.ts +105 -0
- package/src/wiki/run-wiki.ts +1 -1
- package/src/wiki/serve-graph.ts +33 -3
- package/src/cli/cmd_Rules.ts +0 -51
- package/src/cli/cmd_Units.ts +0 -82
- package/src/collections/graphs.ts +0 -32
- package/src/contracts/contracts-once.ts +0 -43
- package/src/core/check-suite.ts +0 -302
- package/src/core/repair-contract-dirs.ts +0 -136
- package/src/core/task-runner.ts +0 -339
- package/src/ledger/gate-units.ts +0 -138
- package/src/ledger/unit-table.ts +0 -105
- package/src/rules/SK002-contracts-fulfillness.ts +0 -24
- package/src/rules/SK003-claude-md-shape.ts +0 -70
- package/src/rules/SK004-gitignore-policy.ts +0 -238
- package/src/rules/SK010-undeclared-release-unit.ts +0 -127
- package/src/rules/rule-registry.ts +0 -64
- package/src/rules/run-fix.ts +0 -72
- package/src/tui/tasks-panel.ts +0 -60
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
// EVERY CHECK THIS REPO HAS, composed ONCE.
|
|
2
|
+
//
|
|
3
|
+
// One walk feeds three readers: the runner (core/check-runner.ts) gets bodies,
|
|
4
|
+
// the key layer gets declarations, and every surface gets rows. They cannot
|
|
5
|
+
// disagree about what exists, which is the whole reason this file is the only
|
|
6
|
+
// place that enumerates.
|
|
7
|
+
//
|
|
8
|
+
// What feeds it: the floor slots derived from package.json scripts, the
|
|
9
|
+
// repo's own `tasks: []`, every discovered `*.check.ts`, and the two legacy
|
|
10
|
+
// forms still read behind the compatibility fence (`rules: []` and a legacy
|
|
11
|
+
// check script, a `*.check.ts` with no check object). The declaration always lives WITH the check, never in a table
|
|
12
|
+
// over here: a table beside the registry is a second source of truth that
|
|
13
|
+
// drifts the first time somebody adds one.
|
|
14
|
+
import { relative, resolve } from 'path'
|
|
15
|
+
import type { CheckCtx } from '../core/check-ctx.ts'
|
|
16
|
+
import { type RepoConfig } from '../repo-config.ts'
|
|
17
|
+
import { listRepoFiles, resolveFloor, slotDescription, type FloorResolution } from '../core/floor.ts'
|
|
18
|
+
import { discoverLegacyChecks } from '../core/discover-legacy-checks.ts'
|
|
19
|
+
import { checkFileRunBy } from '../core/legacy-shapes.ts'
|
|
20
|
+
import { ruleCacheExtra } from '../rules/rule.ts'
|
|
21
|
+
import { discoverChecks } from '../core/discover-checks.ts'
|
|
22
|
+
import { checkCacheExtra, describeFirst, type TaskDef, type CheckFrom } from '../check.ts'
|
|
23
|
+
import { CHECK_TIMEOUT_MS, type RunnableCheck, type CheckBody } from '../core/check-runner.ts'
|
|
24
|
+
import { parseDuration } from '../core/wire.ts'
|
|
25
|
+
import { whenOfLegacyCheck, whenOfDisabledSlot, whenOfRule, whenOfTask, whenOfCheck, type WhenDecl } from '../core/when.ts'
|
|
26
|
+
import type { CheckDecl } from './git-key.ts'
|
|
27
|
+
import { populationOf } from '../problems/problem-line.ts'
|
|
28
|
+
import { rawAccessInClosure, rawAccessLine } from '../core/raw-access.ts'
|
|
29
|
+
import type { RvtConfig } from '../ts/config.ts'
|
|
30
|
+
import { LIB_ROOT } from '../core/lib-root.ts'
|
|
31
|
+
import type { DiscoveredCheck } from '../core/discover-checks.ts'
|
|
32
|
+
import { declaredTestUnits, inputsOutsideUnits, isTestFile } from '../suite/test-units.ts'
|
|
33
|
+
|
|
34
|
+
/** why an undeclared `run` check cannot be keyed on its reads, or null when it
|
|
35
|
+
* can. A built-in is walked inside the toolkit, a repo check inside the repo
|
|
36
|
+
* with the toolkit left out (the toolchain id covers it) */
|
|
37
|
+
function rawAccessOf(root: string, u: DiscoveredCheck, configs: Map<string, RvtConfig>): string | null {
|
|
38
|
+
try {
|
|
39
|
+
const r = u.rel == null ? rawAccessInClosure(LIB_ROOT, u.path, { configs }) : rawAccessInClosure(root, u.path, { skip: LIB_ROOT, configs })
|
|
40
|
+
return r == null ? null : rawAccessLine(r)
|
|
41
|
+
} catch (e) {
|
|
42
|
+
return `the modules ${u.rel ?? u.path} imports could not be listed (${String(e).slice(0, 120)}): declare inputs or never`
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** the env var a repo's own suite reads to widen itself. It lives HERE because
|
|
47
|
+
* it rewrites every build step's env, which is part of the key: a run that
|
|
48
|
+
* asked for the quick tier and one that asked for the full tier are different
|
|
49
|
+
* questions, and a surface that renders keys without applying it renders a
|
|
50
|
+
* tier nobody ran. */
|
|
51
|
+
export const FULL_TESTS_ENV = 'SHIPKIT_TEST_FULL'
|
|
52
|
+
|
|
53
|
+
export type TestTier = 'quick' | 'full'
|
|
54
|
+
|
|
55
|
+
/** the tier this process is asking for, absent an explicit choice */
|
|
56
|
+
export function envTier(): TestTier {
|
|
57
|
+
return process.env[FULL_TESTS_ENV] === '1' ? 'full' : 'quick'
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** a BUILD step's ceiling when it names none: a whole tool chain is not one
|
|
61
|
+
* question about the world (core/check-runner.ts owns the other default) */
|
|
62
|
+
export const BUILD_TIMEOUT_MS = 600_000
|
|
63
|
+
|
|
64
|
+
/** the floor plus the repo's own `tasks: []`, with the slots nothing could
|
|
65
|
+
* fill and the ones a config turned off. The gate prints those, and the TUI
|
|
66
|
+
* draws its rows from them before anything has run. */
|
|
67
|
+
export async function allTasks(c: RepoConfig, root: string): Promise<FloorResolution> {
|
|
68
|
+
const floor = resolveFloor(c, root)
|
|
69
|
+
return { ...floor, tasks: [...floor.tasks, ...(c.tasks ?? [])] }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** one gate check with everything a reader asks about it, not only what the
|
|
73
|
+
* key layer asks. The cache half stays in `decl` so `CheckDecl` never grows
|
|
74
|
+
* fields the key layer must then decide to ignore. */
|
|
75
|
+
export type CheckRow = {
|
|
76
|
+
decl: CheckDecl
|
|
77
|
+
/** the display name an operator reads (its title, else the id) */
|
|
78
|
+
title: string
|
|
79
|
+
/** the check's one line description (the shape: check.ts
|
|
80
|
+
* descriptionProblem). Every row carries one: a check file declares it,
|
|
81
|
+
* and a row with no file to say it in (a floor slot, a unit test check,
|
|
82
|
+
* a task, the legacy forms) gets it from what generated the row. */
|
|
83
|
+
description: string
|
|
84
|
+
/** does `shipkit ci` run it? false = PROACTIVE: `shipkit check` reports it,
|
|
85
|
+
* the gate never does, and it may sit red for months without anything
|
|
86
|
+
* being wrong with the change in front of you. */
|
|
87
|
+
gate: boolean
|
|
88
|
+
/** a BUILD STEP: the gate runs it, the health pass does not. `shipkit ci`
|
|
89
|
+
* asks "is my change good" and builds, `shipkit check` asks "is this
|
|
90
|
+
* repo healthy" and reports, and a repo's test suite has no business in
|
|
91
|
+
* the second question. DERIVED true for a floor slot and a `tasks: []`
|
|
92
|
+
* entry, which have no file to say it in, and DECLARED by a `*.check.ts`
|
|
93
|
+
* that is one (check.ts `build`). */
|
|
94
|
+
build: boolean
|
|
95
|
+
/** can a failure stop you? null = the check never said AND the repo set no
|
|
96
|
+
* `checkDefaults.blocking`, which the runner treats as fatal. */
|
|
97
|
+
blocking: boolean | null
|
|
98
|
+
/** where the answer is owned: a wiki page, a file, a command. Null when
|
|
99
|
+
* the check names none. */
|
|
100
|
+
owner: string | null
|
|
101
|
+
/** the group a check was filed under, for the few that declare one */
|
|
102
|
+
group: string | null
|
|
103
|
+
/** the wall clock ceiling it DECLARED, null when it named none and takes
|
|
104
|
+
* the default for its kind of body */
|
|
105
|
+
timeoutMs: number | null
|
|
106
|
+
/** excluded from a default run unless named with `--with` */
|
|
107
|
+
optIn: boolean
|
|
108
|
+
/** an idempotent repair exists: `shipkit check --fix <id>` applies it,
|
|
109
|
+
* then re-runs the check. `shipkit checks --fixable` is the catalog of
|
|
110
|
+
* everything in this repo that repairs itself. */
|
|
111
|
+
fixable: boolean
|
|
112
|
+
/** WHERE IT RUNS, the one reading of the `when` key, of the three
|
|
113
|
+
* spellings it replaces, and of a floor slot turned off (core/when.ts) */
|
|
114
|
+
when: WhenDecl
|
|
115
|
+
/** what it waits on, null when it waits on nothing */
|
|
116
|
+
deps: string[] | null
|
|
117
|
+
/** whose check it is: `shipkit` = it ships with the toolkit (a built-in
|
|
118
|
+
* SK check, the contracts pass, a floor slot nothing in the repo filled),
|
|
119
|
+
* `repo` = this repo declared it (a package.json script behind a floor
|
|
120
|
+
* slot, a task, a rule, a check file, a check file). */
|
|
121
|
+
from: CheckFrom
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** the one axis every check answers: who wrote the declaration */
|
|
125
|
+
export type { CheckFrom } from '../check.ts'
|
|
126
|
+
|
|
127
|
+
/** one composed check: the row every surface reads, and the runnable the
|
|
128
|
+
* runner takes. Two views of one declaration, never two enumerations. */
|
|
129
|
+
export type GateCheck = { row: CheckRow; runnable: RunnableCheck }
|
|
130
|
+
|
|
131
|
+
export type ComposeOpts = {
|
|
132
|
+
/** which tier the caller is asking for: `full` widens every build step's
|
|
133
|
+
* env, which is part of its key */
|
|
134
|
+
tier?: TestTier
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** a legacy `TaskDef` as the runner takes it. The config key still reads
|
|
138
|
+
* through the compatibility window, so the shape survives one release longer
|
|
139
|
+
* than the word for it. */
|
|
140
|
+
function taskBody(t: TaskDef): CheckBody {
|
|
141
|
+
return {
|
|
142
|
+
...(t.cmd == null ? {} : { cmd: t.cmd }),
|
|
143
|
+
...(t.logic == null ? {} : { run: async (ctx: CheckCtx) => { await t.logic!(ctx) } }),
|
|
144
|
+
...(t.env == null ? {} : { env: t.env }),
|
|
145
|
+
...(t.cwd == null ? {} : { cwd: t.cwd }),
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const ttlMsOf = (ttl: string | undefined): number => (ttl == null || ttl === '' ? 0 : parseDuration(ttl))
|
|
150
|
+
|
|
151
|
+
/** the runnable half of a row: everything the runner needs, and nothing it
|
|
152
|
+
* has to look up again */
|
|
153
|
+
function runnableOf(row: CheckRow, body: CheckBody, o: { fix?: unknown; pointer?: string | null } = {}): RunnableCheck {
|
|
154
|
+
return {
|
|
155
|
+
id: row.decl.id,
|
|
156
|
+
title: row.title,
|
|
157
|
+
owner: row.owner ?? row.decl.id,
|
|
158
|
+
blocking: row.blocking,
|
|
159
|
+
build: row.build,
|
|
160
|
+
when: row.when,
|
|
161
|
+
deps: row.deps ?? [],
|
|
162
|
+
group: row.group,
|
|
163
|
+
timeoutMs: row.timeoutMs ?? (row.build ? BUILD_TIMEOUT_MS : CHECK_TIMEOUT_MS),
|
|
164
|
+
ttlMs: ttlMsOf(row.decl.ttl),
|
|
165
|
+
rotation: row.decl.extra,
|
|
166
|
+
writes: row.decl.writes === true,
|
|
167
|
+
body,
|
|
168
|
+
fix: o.fix,
|
|
169
|
+
pointer: o.pointer ?? null,
|
|
170
|
+
population: populationOf(row.decl),
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** what the ROOT `test` check is keyed on, when the repo declared its read
|
|
175
|
+
* set (`defaults.test.inputs`): its own test files ride in as code, so the
|
|
176
|
+
* modules they import move the key, and every sub-unit dir is cut out of the
|
|
177
|
+
* declared paths, because those suites run in `test:<unit>`. A repo that
|
|
178
|
+
* declared nothing stays unkeyed, exactly as before (core/floor.ts says why) */
|
|
179
|
+
function rootTestKey(root: string, c: RepoConfig, floor: FloorResolution): { inputs: string[]; code: string[] } | null {
|
|
180
|
+
const t = floor.tasks.find((x) => x.id === 'test')
|
|
181
|
+
if (t?.inputs == null || t.cmd == null) return null
|
|
182
|
+
const files = listRepoFiles(root)
|
|
183
|
+
const subs = declaredTestUnits(c.units)
|
|
184
|
+
const own = files.filter((f) => isTestFile(f) && !subs.some((u) => f.startsWith(`${u.rel}/`)))
|
|
185
|
+
return { inputs: subs.length === 0 ? t.inputs : inputsOutsideUnits(t.inputs, subs.map((u) => u.rel), files), code: own }
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** a task's own description, else its command exiting 0 */
|
|
189
|
+
const taskDescription = (t: TaskDef): string =>
|
|
190
|
+
describeFirst(t.description, t.cmd == null ? null : `\`${t.cmd.join(' ')}\` exits 0`, `the ${t.id} task ${t.cmd == null ? 'passes' : 'exits 0'}`)
|
|
191
|
+
|
|
192
|
+
/** THE enumeration. Everything below reads it, nothing else walks. */
|
|
193
|
+
export async function composeChecks(root: string, c: RepoConfig, o: ComposeOpts = {}): Promise<GateCheck[]> {
|
|
194
|
+
const out: GateCheck[] = []
|
|
195
|
+
const checks = await discoverChecks(root)
|
|
196
|
+
const floor = resolveFloor(c, root)
|
|
197
|
+
const env = { root, config: c }
|
|
198
|
+
const full = o.tier === 'full'
|
|
199
|
+
/** a check file's own path IS its code closure, so nothing declares it */
|
|
200
|
+
const relOf = new Map(checks.filter((u) => u.rel != null).map((u) => [u.id, u.rel!]))
|
|
201
|
+
const fromOf = new Map(checks.map((u) => [u.id, u.from]))
|
|
202
|
+
// a floor slot the REPO filled (its package.json script, its config
|
|
203
|
+
// override) is the repo's; one shipkit filled with its own module or the
|
|
204
|
+
// repo's tsc is the toolkit's. A task the config declares is the repo's.
|
|
205
|
+
const taskFrom = (id: string): CheckFrom => {
|
|
206
|
+
const discovered = fromOf.get(id)
|
|
207
|
+
if (discovered != null) return discovered
|
|
208
|
+
const src = floor.sources[id]
|
|
209
|
+
if (src == null) return 'repo'
|
|
210
|
+
return src === 'package.json script' || src === 'repo.config override' || src === 'unit' ? 'repo' : 'shipkit'
|
|
211
|
+
}
|
|
212
|
+
// LEGACY: a task that runs a check file under the check's own name was
|
|
213
|
+
// that file run twice, once per engine, when tasks and checks had a
|
|
214
|
+
// namespace each. One namespace makes the pair one check: the check row
|
|
215
|
+
// runs, carrying what the task added (core/legacy-shapes.ts rewrites the
|
|
216
|
+
// pair the same way on disk)
|
|
217
|
+
const legacy = discoverLegacyChecks(root)
|
|
218
|
+
const foldedInto = new Map<string, TaskDef>()
|
|
219
|
+
for (const t of c.tasks ?? []) {
|
|
220
|
+
const d = legacy.find((x) => x.name === t.id)
|
|
221
|
+
if (d == null || t.logic != null) continue
|
|
222
|
+
const r = checkFileRunBy(t, root)
|
|
223
|
+
if (r != null && r.rest.length === 0 && r.rel === relative(root, d.path).replace(/\\/g, '/')) foldedInto.set(t.id, t)
|
|
224
|
+
}
|
|
225
|
+
// BUILD STEPS: the floor, then the repo's own `tasks: []`. The whole run
|
|
226
|
+
// carries the tier flag rather than one task id: a repo names its suites
|
|
227
|
+
// what it likes, and a gate that guesses which one is the tests will miss
|
|
228
|
+
// one.
|
|
229
|
+
const rootTest = rootTestKey(root, c, floor)
|
|
230
|
+
for (const raw of [...floor.tasks, ...(c.tasks ?? []).filter((t) => foldedInto.get(t.id) !== t)]) {
|
|
231
|
+
const t: TaskDef = full ? { ...raw, env: { ...raw.env, [FULL_TESTS_ENV]: '1' } } : raw
|
|
232
|
+
const own = t.id === 'test' && floor.tasks.includes(raw) ? rootTest : null
|
|
233
|
+
const row: CheckRow = {
|
|
234
|
+
decl: {
|
|
235
|
+
id: t.id,
|
|
236
|
+
inputs: own?.inputs ?? t.inputs,
|
|
237
|
+
ttl: t.ttl,
|
|
238
|
+
never: t.never,
|
|
239
|
+
argv: t.cmd,
|
|
240
|
+
code: relOf.has(t.id) ? [relOf.get(t.id)!] : own?.code,
|
|
241
|
+
extra: t.env == null ? undefined : JSON.stringify(Object.entries(t.env).sort()),
|
|
242
|
+
},
|
|
243
|
+
title: t.id,
|
|
244
|
+
description: taskDescription(t),
|
|
245
|
+
// an optIn task is off the default run, which is the same shape as
|
|
246
|
+
// a check that opted out of the gate
|
|
247
|
+
gate: t.optIn !== true,
|
|
248
|
+
build: true,
|
|
249
|
+
// a build step IS the build: it has no non-blocking mode
|
|
250
|
+
blocking: true,
|
|
251
|
+
owner: t.cmd == null ? null : t.cmd.join(' '),
|
|
252
|
+
group: t.group ?? null,
|
|
253
|
+
timeoutMs: t.timeoutMs ?? null,
|
|
254
|
+
optIn: t.optIn === true,
|
|
255
|
+
// a build step runs a TOOL, and repairing what a tool reports is
|
|
256
|
+
// that tool's own job (a formatter, a codemod), never the gate's
|
|
257
|
+
fixable: false,
|
|
258
|
+
when: whenOfTask(c, t),
|
|
259
|
+
deps: t.deps ?? null,
|
|
260
|
+
from: taskFrom(t.id),
|
|
261
|
+
}
|
|
262
|
+
out.push({ row, runnable: runnableOf(row, taskBody(t)) })
|
|
263
|
+
}
|
|
264
|
+
// a slot `defaults: { <id>: false }` turned off never resolves to a task,
|
|
265
|
+
// so without this it had no row at all: no `runs in` value, no reason, and
|
|
266
|
+
// a count of off checks that read zero while a gate step sat dark. It has
|
|
267
|
+
// no command, which is why it declares `never` rather than joining the
|
|
268
|
+
// burn-down of checks nobody has keyed yet.
|
|
269
|
+
for (const id of floor.disabled) {
|
|
270
|
+
const row: CheckRow = {
|
|
271
|
+
decl: { id, never: `turned off by defaults.${id}: false, so there is no command to key` },
|
|
272
|
+
title: id,
|
|
273
|
+
description: slotDescription(id, declaredTestUnits(c.units).length > 0),
|
|
274
|
+
gate: false,
|
|
275
|
+
build: true,
|
|
276
|
+
blocking: true,
|
|
277
|
+
owner: null,
|
|
278
|
+
group: null,
|
|
279
|
+
timeoutMs: null,
|
|
280
|
+
optIn: false,
|
|
281
|
+
fixable: false,
|
|
282
|
+
when: whenOfDisabledSlot(id),
|
|
283
|
+
deps: null,
|
|
284
|
+
// the repo wrote the line that turned it off
|
|
285
|
+
from: 'repo',
|
|
286
|
+
}
|
|
287
|
+
// it is OFF, so the body is never reached: the runner reports the
|
|
288
|
+
// reason rather than running anything
|
|
289
|
+
out.push({ row, runnable: runnableOf(row, { run: () => {} }) })
|
|
290
|
+
}
|
|
291
|
+
// every `*.check.ts` this repo and the toolkit declare
|
|
292
|
+
const configs = new Map<string, RvtConfig>()
|
|
293
|
+
for (const u of checks) {
|
|
294
|
+
const w = whenOfCheck(c, u)
|
|
295
|
+
// an in process body declaring nothing is keyed on what it reads
|
|
296
|
+
// through its context (git-key.ts READS). The tier is part of that
|
|
297
|
+
// question: a body can read the env, and a quick answer must never
|
|
298
|
+
// stand in for a full one
|
|
299
|
+
const undeclared = u.def.run != null && u.def.inputs == null && u.def.ttl == null && u.def.never == null
|
|
300
|
+
// conservative: any module its body can reach that touches the disk or
|
|
301
|
+
// a process around `c` keeps it off its reads (core/raw-access.ts)
|
|
302
|
+
const raw = undeclared ? rawAccessOf(root, u, configs) : null
|
|
303
|
+
const reads = undeclared && raw == null
|
|
304
|
+
const extra = checkCacheExtra({ id: u.id, cacheKey: u.def.cacheKey }, env, w)
|
|
305
|
+
const row: CheckRow = {
|
|
306
|
+
decl: {
|
|
307
|
+
id: u.id,
|
|
308
|
+
inputs: u.def.inputs,
|
|
309
|
+
code: u.rel == null ? undefined : [u.rel],
|
|
310
|
+
ttl: u.def.ttl,
|
|
311
|
+
never: u.def.never,
|
|
312
|
+
argv: u.def.cmd,
|
|
313
|
+
extra: reads ? `${extra ?? ''}|tier=${full ? 'full' : 'quick'}` : extra,
|
|
314
|
+
writes: u.def.writes,
|
|
315
|
+
...(reads ? { reads: true } : {}),
|
|
316
|
+
...(raw != null ? { uncacheable: raw } : {}),
|
|
317
|
+
},
|
|
318
|
+
title: u.title,
|
|
319
|
+
description: u.description,
|
|
320
|
+
gate: w.at !== 'health',
|
|
321
|
+
build: u.def.build === true,
|
|
322
|
+
blocking: u.def.blocking,
|
|
323
|
+
owner: u.owner,
|
|
324
|
+
group: u.def.group ?? null,
|
|
325
|
+
timeoutMs: u.def.timeoutMs ?? null,
|
|
326
|
+
optIn: false,
|
|
327
|
+
fixable: u.def.fix != null,
|
|
328
|
+
when: w,
|
|
329
|
+
deps: u.def.deps ?? null,
|
|
330
|
+
from: u.from,
|
|
331
|
+
}
|
|
332
|
+
const body: CheckBody = {
|
|
333
|
+
...(u.def.cmd == null ? {} : { cmd: u.def.cmd }),
|
|
334
|
+
...(u.def.run == null ? {} : { run: u.def.run }),
|
|
335
|
+
...(u.def.env == null ? {} : { env: u.def.env }),
|
|
336
|
+
// relative to the root, and an absolute one stands as written
|
|
337
|
+
...(u.def.cwd == null ? {} : { cwd: resolve(root, u.def.cwd) }),
|
|
338
|
+
}
|
|
339
|
+
out.push({ row, runnable: runnableOf(row, body, { fix: u.def.fix, pointer: u.def.cmd?.join(' ') ?? null }) })
|
|
340
|
+
}
|
|
341
|
+
// LEGACY: `rules: []`, read as a check whose body is its lint. Dies with
|
|
342
|
+
// that config key once `shipkit fleet` shows no repo below 0.40.
|
|
343
|
+
for (const r of c.rules ?? []) {
|
|
344
|
+
const row: CheckRow = {
|
|
345
|
+
decl: {
|
|
346
|
+
id: r.id,
|
|
347
|
+
inputs: r.inputs,
|
|
348
|
+
code: r.code,
|
|
349
|
+
ttl: r.ttl,
|
|
350
|
+
never: r.never,
|
|
351
|
+
extra: ruleCacheExtra(r, env),
|
|
352
|
+
},
|
|
353
|
+
title: r.title,
|
|
354
|
+
description: describeFirst(r.title, `rule ${r.id} holds`),
|
|
355
|
+
gate: true,
|
|
356
|
+
build: false,
|
|
357
|
+
blocking: r.blocking,
|
|
358
|
+
owner: r.owner,
|
|
359
|
+
group: null,
|
|
360
|
+
timeoutMs: null,
|
|
361
|
+
optIn: false,
|
|
362
|
+
fixable: r.fix != null,
|
|
363
|
+
when: whenOfRule(c, r.id),
|
|
364
|
+
deps: null,
|
|
365
|
+
from: 'repo',
|
|
366
|
+
}
|
|
367
|
+
out.push({ row, runnable: runnableOf(row, { run: (ctx) => r.lint(ctx, { root: ctx.root, config: ctx.config }) }, { fix: r.fix }) })
|
|
368
|
+
}
|
|
369
|
+
// LEGACY: a check script with no check object, read as a `cmd` check
|
|
370
|
+
// running itself. The reader is a source scanner rather than an import,
|
|
371
|
+
// which is why it cannot carry a fixer or a typed declaration
|
|
372
|
+
// (core/discover-legacy-checks.ts).
|
|
373
|
+
for (const found of legacy) {
|
|
374
|
+
const rel = relative(root, found.path).replace(/\\/g, '/')
|
|
375
|
+
const task = foldedInto.get(found.name)
|
|
376
|
+
// a task that is not optIn put an off gate file in the gate, and the
|
|
377
|
+
// file's own cache declaration wins over the task's
|
|
378
|
+
const d = task == null ? found : { ...found, gate: found.gate || task.optIn !== true }
|
|
379
|
+
const cached = d.inputs != null || d.ttl != null || d.never != null
|
|
380
|
+
const row: CheckRow = {
|
|
381
|
+
decl: {
|
|
382
|
+
id: d.name,
|
|
383
|
+
inputs: d.inputs ?? (cached ? undefined : task?.inputs),
|
|
384
|
+
ttl: d.ttl ?? (cached ? undefined : task?.ttl),
|
|
385
|
+
never: d.never ?? (cached ? undefined : task?.never),
|
|
386
|
+
code: [rel],
|
|
387
|
+
argv: ['bun', rel],
|
|
388
|
+
// no `extra`, so WHERE it runs stays out of a legacy check's
|
|
389
|
+
// key, deliberately: the field is read by a source scanner
|
|
390
|
+
// that cannot carry a `cacheKey` either, and adding the
|
|
391
|
+
// rotation now would bust every field repo's TTL cache once
|
|
392
|
+
// for a file form that is refused after 0.40 anyway
|
|
393
|
+
},
|
|
394
|
+
title: d.name,
|
|
395
|
+
description: describeFirst(`\`bun ${rel}\` exits 0`, `${d.name} exits 0`),
|
|
396
|
+
gate: d.gate,
|
|
397
|
+
build: false,
|
|
398
|
+
// the file wins, then the repo default, then nothing: an
|
|
399
|
+
// unclassified check is an error, never a guess
|
|
400
|
+
blocking: d.blocking ?? c.checkDefaults?.blocking ?? null,
|
|
401
|
+
owner: rel,
|
|
402
|
+
group: task?.group ?? null,
|
|
403
|
+
timeoutMs: task?.timeoutMs ?? null,
|
|
404
|
+
optIn: false,
|
|
405
|
+
// the reader is a source scanner, so it cannot carry a function
|
|
406
|
+
fixable: false,
|
|
407
|
+
when: whenOfLegacyCheck(c, d),
|
|
408
|
+
deps: task?.deps ?? null,
|
|
409
|
+
from: 'repo',
|
|
410
|
+
}
|
|
411
|
+
// the pointer names the file the way a reader types it, never the
|
|
412
|
+
// absolute path discovery happened to hand the spawner
|
|
413
|
+
const body: CheckBody = { cmd: ['bun', d.path], ...(task?.env == null ? {} : { env: task.env }), ...(task?.cwd == null ? {} : { cwd: resolve(root, task.cwd) }) }
|
|
414
|
+
out.push({ row, runnable: runnableOf(row, body, { pointer: `bun ${rel}` }) })
|
|
415
|
+
}
|
|
416
|
+
return out
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/** every check the gate knows, with its metadata. */
|
|
420
|
+
export async function gateInventory(root: string, c: RepoConfig, o: ComposeOpts = {}): Promise<CheckRow[]> {
|
|
421
|
+
return (await composeChecks(root, c, o)).map((g) => g.row)
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/** the same list, as the runner takes it */
|
|
425
|
+
export async function gateRunnables(root: string, c: RepoConfig, o: ComposeOpts = {}): Promise<RunnableCheck[]> {
|
|
426
|
+
return (await composeChecks(root, c, o)).map((g) => g.runnable)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/** the inventory narrowed to the cache declaration, which is what the key
|
|
430
|
+
* layer keys. Same walk, so a surface can never enumerate a different set. */
|
|
431
|
+
export async function gateChecks(root: string, c: RepoConfig, o: ComposeOpts = {}): Promise<CheckDecl[]> {
|
|
432
|
+
return (await composeChecks(root, c, o)).map((g) => g.row.decl)
|
|
433
|
+
}
|