@intentius/chant 0.124.31 → 0.124.32
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/bin/chant +7 -2
- package/dist/cli/commands/check-lexicon-plugin.d.ts +7 -4
- package/dist/cli/commands/check-lexicon-plugin.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-plugin.js +10 -7
- package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon.js +18 -12
- package/dist/cli/commands/import.d.ts +10 -0
- package/dist/cli/commands/import.d.ts.map +1 -1
- package/dist/cli/commands/import.js +37 -0
- package/dist/cli/commands/init-lexicon/templates/project.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon/templates/project.js +4 -3
- package/dist/cli/commands/onboard.d.ts.map +1 -1
- package/dist/cli/commands/onboard.js +8 -4
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +2 -1
- package/dist/cli/dependencies.d.ts +18 -0
- package/dist/cli/dependencies.d.ts.map +1 -0
- package/dist/cli/dependencies.js +120 -0
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.js +23 -4
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.js +12 -4
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/graph.js +16 -6
- package/dist/cli/handlers/lint.d.ts.map +1 -1
- package/dist/cli/handlers/lint.js +6 -0
- package/dist/cli/handlers/operator.d.ts +2 -2
- package/dist/cli/handlers/operator.js +3 -3
- package/dist/cli/handlers/pr.d.ts +14 -0
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +488 -32
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/promote.js +3 -1
- package/dist/cli/handlers/run.d.ts +13 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/run.js +38 -15
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +44 -16
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +15 -0
- package/dist/cli/registry.d.ts +13 -4
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/cli/registry.js +6 -2
- package/dist/codegen/generate-post-synth-barrel.js +2 -2
- package/dist/codegen/lexicon-regen.d.ts +4 -0
- package/dist/codegen/lexicon-regen.d.ts.map +1 -1
- package/dist/codegen/lexicon-regen.js +1 -1
- package/dist/codegen/pinned-upgrade.js +2 -2
- package/dist/components/capability.d.ts +44 -3
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/capability.js +21 -0
- package/dist/components/changed-files.d.ts +7 -1
- package/dist/components/changed-files.d.ts.map +1 -1
- package/dist/components/changed-files.js +9 -1
- package/dist/components/driver.d.ts +38 -0
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/driver.js +47 -6
- package/dist/components/fan-out-record.d.ts +6 -1
- package/dist/components/fan-out-record.d.ts.map +1 -1
- package/dist/components/fan-out-record.js +24 -0
- package/dist/components/fan-out-run.d.ts +20 -0
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-run.js +21 -1
- package/dist/components/pr-pipeline.d.ts +65 -2
- package/dist/components/pr-pipeline.d.ts.map +1 -1
- package/dist/components/pr-pipeline.js +107 -15
- package/dist/components/pr-run.d.ts +127 -29
- package/dist/components/pr-run.d.ts.map +1 -1
- package/dist/components/pr-run.js +290 -70
- package/dist/components/wave-plan.d.ts +5 -2
- package/dist/components/wave-plan.d.ts.map +1 -1
- package/dist/components/wave-plan.js +12 -1
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/fold-import.js +16 -6
- package/dist/discovery/sandbox/bundle.d.ts.map +1 -1
- package/dist/discovery/sandbox/bundle.js +6 -0
- package/dist/discovery/specifier-resolve.d.ts.map +1 -1
- package/dist/discovery/specifier-resolve.js +8 -2
- package/dist/fold/fold.d.ts.map +1 -1
- package/dist/fold/fold.js +68 -1
- package/dist/fold/subset.d.ts +37 -0
- package/dist/fold/subset.d.ts.map +1 -1
- package/dist/fold/subset.js +88 -0
- package/dist/lexicon-module.d.ts +10 -0
- package/dist/lexicon-module.d.ts.map +1 -1
- package/dist/lexicon-module.js +53 -8
- package/dist/lexicon-plugin-helpers.d.ts.map +1 -1
- package/dist/lexicon-plugin-helpers.js +6 -4
- package/dist/lexicon.d.ts +2 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/affected.d.ts.map +1 -1
- package/dist/lifecycle/affected.js +7 -1
- package/dist/lifecycle/release-ledger.d.ts +21 -0
- package/dist/lifecycle/release-ledger.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.js +24 -0
- package/dist/lifecycle/work-dir.d.ts +45 -0
- package/dist/lifecycle/work-dir.d.ts.map +1 -0
- package/dist/lifecycle/work-dir.js +78 -0
- package/dist/lint/discover.d.ts +17 -9
- package/dist/lint/discover.d.ts.map +1 -1
- package/dist/lint/discover.js +62 -54
- package/dist/module-source.d.ts +57 -0
- package/dist/module-source.d.ts.map +1 -0
- package/dist/module-source.js +104 -0
- package/dist/op/activities/factory.js +1 -1
- package/dist/op/discover.d.ts.map +1 -1
- package/dist/op/discover.js +13 -3
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/gate.js +3 -0
- package/dist/op/op-waves-run.d.ts.map +1 -1
- package/dist/op/op-waves-run.js +20 -5
- package/dist/op/operator.d.ts +2 -2
- package/dist/op/operator.js +2 -2
- package/dist/op/steward.d.ts +6 -4
- package/dist/op/steward.d.ts.map +1 -1
- package/dist/op/steward.js +5 -3
- package/dist/op/work-lease-run.d.ts +20 -7
- package/dist/op/work-lease-run.d.ts.map +1 -1
- package/dist/op/work-lease-run.js +56 -12
- package/dist/pr-forge.d.ts +10 -0
- package/dist/pr-forge.d.ts.map +1 -1
- package/dist/pr-forge.js +39 -0
- package/dist/pr-loop.d.ts +141 -4
- package/dist/pr-loop.d.ts.map +1 -1
- package/dist/pr-loop.js +122 -6
- package/dist/pr-report-url.d.ts +2 -2
- package/dist/pr-report-url.d.ts.map +1 -1
- package/dist/pr-report-url.js +5 -5
- package/dist/ts-loader.d.ts +38 -4
- package/dist/ts-loader.d.ts.map +1 -1
- package/dist/ts-loader.js +190 -16
- package/dist/wave-runtime.mjs +208 -208
- package/dist/workspace/chant-migrations/fountain-retired-kinds.d.ts +34 -0
- package/dist/workspace/chant-migrations/fountain-retired-kinds.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/fountain-retired-kinds.js +176 -0
- package/dist/workspace/chant-migrations.d.ts.map +1 -1
- package/dist/workspace/chant-migrations.js +2 -0
- package/dist/workspace/checks/boxes.d.ts +6 -6
- package/dist/workspace/checks/boxes.js +9 -9
- package/dist/workspace/ci-green-forge.d.ts +6 -4
- package/dist/workspace/ci-green-forge.d.ts.map +1 -1
- package/dist/workspace/ci-green.d.ts +28 -12
- package/dist/workspace/ci-green.d.ts.map +1 -1
- package/dist/workspace/ci-green.js +49 -21
- package/dist/workspace/ci-list.schema.json +12 -6
- package/dist/workspace/ci-report-url.d.ts +18 -0
- package/dist/workspace/ci-report-url.d.ts.map +1 -0
- package/dist/workspace/ci-report-url.js +33 -0
- package/dist/workspace/ci-run-index.d.ts +12 -0
- package/dist/workspace/ci-run-index.d.ts.map +1 -1
- package/dist/workspace/ci-run-index.js +6 -4
- package/dist/workspace/ci-run-index.schema.json +5 -3
- package/dist/workspace/ci-run.d.ts +75 -2
- package/dist/workspace/ci-run.d.ts.map +1 -1
- package/dist/workspace/ci-run.js +195 -53
- package/dist/workspace/ci-runner-key.d.ts +16 -3
- package/dist/workspace/ci-runner-key.d.ts.map +1 -1
- package/dist/workspace/ci-runner-key.js +30 -10
- package/dist/workspace/ci-serve.d.ts +54 -4
- package/dist/workspace/ci-serve.d.ts.map +1 -1
- package/dist/workspace/ci-serve.js +175 -26
- package/dist/workspace/declaration.d.ts +8 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +11 -2
- package/dist/workspace/declaration.schema.json +7 -2
- package/dist/workspace/export.d.ts +1 -1
- package/dist/workspace/export.d.ts.map +1 -1
- package/dist/workspace/export.js +7 -3
- package/dist/workspace/ls.d.ts +6 -1
- package/dist/workspace/ls.d.ts.map +1 -1
- package/dist/workspace/ls.js +1 -0
- package/dist/workspace/ls.schema.json +7 -1
- package/dist/workspace/pin-integrity.d.ts.map +1 -1
- package/dist/workspace/pin-integrity.js +3 -2
- package/dist/workspace/pr-report.schema.json +74 -7
- package/dist/workspace/reason-codes.d.ts +1 -1
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +2 -2
- package/dist/workspace/run-event.schema.json +3 -1
- package/dist/workspace/status.schema.json +3 -1
- package/dist/workspace/write-scope.d.ts +9 -0
- package/dist/workspace/write-scope.d.ts.map +1 -1
- package/dist/workspace/write-scope.js +11 -1
- package/package.json +1 -1
- package/src/__fixtures__/pr-loop/pr-apply-wave-waiting.note.golden.md +48 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +3 -3
- package/src/cli/commands/audit.test.ts +2 -2
- package/src/cli/commands/check-lexicon-plugin.test.ts +9 -0
- package/src/cli/commands/check-lexicon-plugin.ts +11 -8
- package/src/cli/commands/check-lexicon.test.ts +1 -1
- package/src/cli/commands/check-lexicon.ts +16 -11
- package/src/cli/commands/import.ts +40 -0
- package/src/cli/commands/init-lexicon/templates/project.ts +4 -3
- package/src/cli/commands/init-lexicon.test.ts +8 -5
- package/src/cli/commands/onboard.test.ts +45 -0
- package/src/cli/commands/onboard.ts +8 -4
- package/src/cli/commands/update.test.ts +6 -0
- package/src/cli/commands/update.ts +4 -1
- package/src/cli/compiled-cli.e2e.test.ts +76 -11
- package/src/cli/dependencies.e2e.test.ts +101 -0
- package/src/cli/dependencies.test.ts +93 -0
- package/src/cli/dependencies.ts +122 -0
- package/src/cli/handlers/build.test.ts +69 -0
- package/src/cli/handlers/build.ts +20 -4
- package/src/cli/handlers/fan-out.test.ts +73 -0
- package/src/cli/handlers/fan-out.ts +11 -4
- package/src/cli/handlers/graph.test.ts +76 -0
- package/src/cli/handlers/graph.ts +19 -12
- package/src/cli/handlers/lint.test.ts +42 -0
- package/src/cli/handlers/lint.ts +6 -0
- package/src/cli/handlers/operator.ts +3 -3
- package/src/cli/handlers/pr-waves.test.ts +449 -0
- package/src/cli/handlers/pr.test.ts +134 -0
- package/src/cli/handlers/pr.ts +523 -31
- package/src/cli/handlers/promote.test.ts +26 -1
- package/src/cli/handlers/promote.ts +3 -2
- package/src/cli/handlers/run-components-exit.test.ts +99 -0
- package/src/cli/handlers/run-signer.test.ts +79 -0
- package/src/cli/handlers/run.ts +34 -16
- package/src/cli/main.test.ts +18 -0
- package/src/cli/main.ts +43 -16
- package/src/cli/parse-args.ts +12 -0
- package/src/cli/registry.ts +19 -6
- package/src/codegen/generate-post-synth-barrel.ts +2 -2
- package/src/codegen/lexicon-regen.ts +1 -1
- package/src/codegen/pinned-upgrade.ts +2 -2
- package/src/codegen/post-synth-barrel-guards.test.ts +6 -11
- package/src/components/capability.ts +51 -3
- package/src/components/changed-files.test.ts +16 -1
- package/src/components/changed-files.ts +9 -1
- package/src/components/driver.test.ts +36 -1
- package/src/components/driver.ts +76 -4
- package/src/components/fan-out-record.ts +29 -1
- package/src/components/fan-out-run.test.ts +35 -0
- package/src/components/fan-out-run.ts +40 -1
- package/src/components/pr-pipeline.test.ts +100 -0
- package/src/components/pr-pipeline.ts +132 -17
- package/src/components/pr-run.test.ts +323 -18
- package/src/components/pr-run.ts +354 -75
- package/src/components/wave-plan.ts +16 -4
- package/src/discovery/fold-host-method.test.ts +87 -0
- package/src/discovery/fold-import.ts +15 -5
- package/src/discovery/fold-package-composite.test.ts +27 -0
- package/src/discovery/fold-specifiers.test.ts +8 -0
- package/src/discovery/sandbox/bundle.ts +6 -0
- package/src/discovery/specifier-resolve.ts +8 -2
- package/src/fold/fold.test.ts +72 -0
- package/src/fold/fold.ts +71 -0
- package/src/fold/subset.test.ts +31 -0
- package/src/fold/subset.ts +99 -0
- package/src/lexicon-dist.e2e.test.ts +254 -0
- package/src/lexicon-module.test.ts +47 -0
- package/src/lexicon-module.ts +49 -7
- package/src/lexicon-plugin-helpers.ts +6 -4
- package/src/lexicon.ts +2 -0
- package/src/lifecycle/affected.test.ts +7 -0
- package/src/lifecycle/affected.ts +6 -1
- package/src/lifecycle/release-ledger.ts +34 -0
- package/src/lifecycle/work-dir.test.ts +77 -0
- package/src/lifecycle/work-dir.ts +82 -0
- package/src/lint/discover.test.ts +46 -2
- package/src/lint/discover.ts +55 -49
- package/src/module-source.test.ts +85 -0
- package/src/module-source.ts +112 -0
- package/src/op/activities/factory.ts +1 -1
- package/src/op/discover-root.test.ts +31 -2
- package/src/op/discover.ts +14 -4
- package/src/op/gate.test.ts +30 -0
- package/src/op/gate.ts +3 -0
- package/src/op/op-waves-run.ts +20 -5
- package/src/op/op-waves.test.ts +49 -0
- package/src/op/operator.ts +2 -2
- package/src/op/steward.ts +6 -4
- package/src/op/work-lease-run.test.ts +88 -2
- package/src/op/work-lease-run.ts +52 -12
- package/src/pr-forge-bundle.test.ts +2 -2
- package/src/pr-forge.test.ts +42 -0
- package/src/pr-forge.ts +40 -0
- package/src/pr-loop-bundle.test.ts +2 -1
- package/src/pr-loop.test.ts +117 -1
- package/src/pr-loop.ts +252 -10
- package/src/pr-report-url.ts +5 -5
- package/src/terraform/__fixtures__/advise.test.ts +21 -2
- package/src/ts-loader.test.ts +89 -1
- package/src/ts-loader.ts +180 -13
- package/src/workspace/changes.test.ts +1 -1
- package/src/workspace/chant-migrations/fountain-retired-kinds.test.ts +33 -0
- package/src/workspace/chant-migrations/fountain-retired-kinds.ts +190 -0
- package/src/workspace/chant-migrations.e2e.test.ts +5 -0
- package/src/workspace/chant-migrations.ts +2 -0
- package/src/workspace/checks/boxes.ts +9 -9
- package/src/workspace/ci-green-forge.ts +6 -4
- package/src/workspace/ci-green-runs.test.ts +53 -11
- package/src/workspace/ci-green.test.ts +1 -1
- package/src/workspace/ci-green.ts +50 -22
- package/src/workspace/ci-list.schema.json +12 -6
- package/src/workspace/ci-report-url.test.ts +220 -0
- package/src/workspace/ci-report-url.ts +33 -0
- package/src/workspace/ci-run-index.schema.json +5 -3
- package/src/workspace/ci-run-index.test.ts +42 -3
- package/src/workspace/ci-run-index.ts +18 -4
- package/src/workspace/ci-run.test.ts +185 -7
- package/src/workspace/ci-run.ts +224 -54
- package/src/workspace/ci-runner-key.test.ts +55 -17
- package/src/workspace/ci-runner-key.ts +36 -11
- package/src/workspace/ci-serve.test.ts +150 -6
- package/src/workspace/ci-serve.ts +169 -25
- package/src/workspace/declaration.schema.json +7 -2
- package/src/workspace/declaration.test.ts +23 -0
- package/src/workspace/declaration.ts +19 -3
- package/src/workspace/export.ts +6 -3
- package/src/workspace/ls.schema.json +7 -1
- package/src/workspace/ls.ts +7 -2
- package/src/workspace/pin-integrity.test.ts +5 -0
- package/src/workspace/pin-integrity.ts +3 -2
- package/src/workspace/pr-report.schema.json +74 -7
- package/src/workspace/reason-codes.ts +2 -2
- package/src/workspace/run-event.schema.json +3 -1
- package/src/workspace/status.schema.json +3 -1
- package/src/workspace/write-scope.test.ts +29 -1
- package/src/workspace/write-scope.ts +12 -1
package/dist/pr-loop.js
CHANGED
|
@@ -28,6 +28,16 @@
|
|
|
28
28
|
* and decides the gate against the new digest. The same plan applies; a moved
|
|
29
29
|
* plan stops with both digests named and nothing applied.
|
|
30
30
|
*
|
|
31
|
+
* ## A gate per wave on merge
|
|
32
|
+
*
|
|
33
|
+
* With `--wave-gate <gate>` (#4003) the apply runs a wave at a time, and
|
|
34
|
+
* plans each wave after the one before it applied. Wave 1 applies under the
|
|
35
|
+
* pull request's approval. A later wave whose new plan the approved change
|
|
36
|
+
* set covers (`planCoveredBy`) applies under that approval too; one whose
|
|
37
|
+
* plan moved waits at `<gate>-wave-<k>`, under the same op, for an approval
|
|
38
|
+
* of its new plan. The report's `waves` says which wave applied under which
|
|
39
|
+
* approval.
|
|
40
|
+
*
|
|
31
41
|
* ## Inside a workspace member
|
|
32
42
|
*
|
|
33
43
|
* Each workspace member that generates the pipeline (#3465) runs its own
|
|
@@ -43,6 +53,14 @@ export const PR_REPORT_SCHEMA_ID = "https://intentius.io/chant/schemas/workspace
|
|
|
43
53
|
export const PR_REPORT_CONTRACT = 1;
|
|
44
54
|
/** The first chant whose report can carry `deployedOutputs` (#4104). */
|
|
45
55
|
export const PR_REPORT_DEPLOYED_OUTPUTS_FLOOR = "0.124.30";
|
|
56
|
+
/** The first chant whose report can carry `waveGate` and `waves` (#4003). */
|
|
57
|
+
export const PR_REPORT_WAVES_FLOOR = "0.124.32";
|
|
58
|
+
/**
|
|
59
|
+
* The first chant whose report can carry `plannedOutputs`, `rounds`, a
|
|
60
|
+
* member's `unknownInputs`, `buildInputs`, `round` and `held`, and the `waiting` statuses
|
|
61
|
+
* (#3999).
|
|
62
|
+
*/
|
|
63
|
+
export const PR_REPORT_PLANNED_OUTPUTS_FLOOR = "0.124.32";
|
|
46
64
|
/** The gate a pull request's apply waits on, unless the pipeline names another. */
|
|
47
65
|
export const PR_APPLY_GATE = "pr-apply";
|
|
48
66
|
/**
|
|
@@ -52,6 +70,15 @@ export const PR_APPLY_GATE = "pr-apply";
|
|
|
52
70
|
export function prOp(pr, member) {
|
|
53
71
|
return member ? `pr-${pr}-${member}` : `pr-${pr}`;
|
|
54
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* The gate a round of an apply waits on (#3999): the pull request's own
|
|
75
|
+
* gate for the first, and `<gate>-round-<n>` for each one planned after an
|
|
76
|
+
* upstream applied. A workspace's `identity.gates` entry for `gate` covers
|
|
77
|
+
* every round.
|
|
78
|
+
*/
|
|
79
|
+
export function prRoundGate(gate, round) {
|
|
80
|
+
return round <= 1 ? gate : `${gate}-round-${round}`;
|
|
81
|
+
}
|
|
55
82
|
/** Whether `op` is a pull request's gate op, with or without a member. Member names follow the declaration's name pattern. */
|
|
56
83
|
export const PR_GATE_OP = /^pr-\d+(?:-[a-z0-9][a-z0-9-]*)?$/;
|
|
57
84
|
/** The hidden line that marks the pull request's note, one per environment and, in a workspace, per member. */
|
|
@@ -101,6 +128,7 @@ const STATUS_WORDS = {
|
|
|
101
128
|
applied: "applied",
|
|
102
129
|
failed: "apply failed",
|
|
103
130
|
blocked: "blocked",
|
|
131
|
+
waiting: "waiting on its new plan",
|
|
104
132
|
};
|
|
105
133
|
function headline(report) {
|
|
106
134
|
const n = report.members.length;
|
|
@@ -116,10 +144,47 @@ function headline(report) {
|
|
|
116
144
|
return `Applied ${members}.`;
|
|
117
145
|
case "failed":
|
|
118
146
|
return `The apply failed: ${report.members.filter((m) => m.status === "failed").length} failed, ${report.members.filter((m) => m.status === "applied").length} applied.`;
|
|
119
|
-
case "
|
|
120
|
-
|
|
147
|
+
case "wave-applied": {
|
|
148
|
+
const applied = (report.waves ?? []).filter((w) => w.status === "applied").map((w) => w.wave);
|
|
149
|
+
const total = report.selection.waves.length;
|
|
150
|
+
return `Applied wave ${applied.at(-1) ?? "?"} of ${total}. The next wave runs in its own job.`;
|
|
151
|
+
}
|
|
152
|
+
case "refused": {
|
|
153
|
+
const waiting = report.waves?.find((w) => w.status === "waiting");
|
|
154
|
+
return waiting ? `Wave ${waiting.wave} waits at its gate ${code(waiting.gate)}; nothing in it applied.` : "The apply refused.";
|
|
155
|
+
}
|
|
156
|
+
case "waiting":
|
|
157
|
+
return `Applied ${report.members.filter((m) => m.status === "applied").length} of ${members}; ${report.members.filter((m) => m.status === "waiting").length} wait on an approval of the plan made after their upstream applied.`;
|
|
121
158
|
}
|
|
122
159
|
}
|
|
160
|
+
/** Who a wave applied or waits under, as a table cell. */
|
|
161
|
+
function waveApproval(w) {
|
|
162
|
+
const whose = w.approval === "pull-request" ? "the pull request's" : "its own";
|
|
163
|
+
const by = w.approvedBy?.length ? ` by ${w.approvedBy.map(code).join(", ")}` : w.status === "waiting" ? ", not approved yet" : "";
|
|
164
|
+
return `${whose} ${code(w.gate)}${by}`;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* The waves of an apply with `--wave-gate` (#4003): a row per wave reached,
|
|
168
|
+
* saying which approval it applied under, then for a wave that waits the
|
|
169
|
+
* reason its plan moved and the approve command for its new plan.
|
|
170
|
+
*/
|
|
171
|
+
function waveLines(report, approver) {
|
|
172
|
+
const waves = report.waves ?? [];
|
|
173
|
+
if (waves.length === 0)
|
|
174
|
+
return [];
|
|
175
|
+
const out = ["", "| Wave | Components | Status | Approval |", "|---:|---|---|---|"];
|
|
176
|
+
for (const w of waves)
|
|
177
|
+
out.push(`| ${w.wave} | ${w.components.map(code).join(", ")} | ${w.status} | ${waveApproval(w)} |`);
|
|
178
|
+
for (const w of waves.filter((x) => x.status === "waiting")) {
|
|
179
|
+
out.push("", `Wave ${w.wave} was planned after the waves before it applied, and the approved plan does not cover it:`, "");
|
|
180
|
+
for (const why of w.moved ?? [])
|
|
181
|
+
out.push(`- ${why}`);
|
|
182
|
+
if (w.approved)
|
|
183
|
+
out.push("", `An approval of ${code(w.gate)} stands for ${code(w.approved)}, not for the plan now.`);
|
|
184
|
+
out.push("", `Read wave ${w.wave}'s plan, ${code(w.digest)}, and approve it with:`, "", "```", prApproveCommand({ op: report.op, gate: w.gate, digest: w.digest }, approver), "```", "", "Then run the wave's apply job again.");
|
|
185
|
+
}
|
|
186
|
+
return out;
|
|
187
|
+
}
|
|
123
188
|
function approvalLines(report, approver) {
|
|
124
189
|
const a = report.approval;
|
|
125
190
|
if (report.status === "nothing")
|
|
@@ -170,6 +235,7 @@ export function renderPrNote(report, options = {}) {
|
|
|
170
235
|
out.push("", "A member whose inputs moved applied the plan it was approved with. The next run plans it against the new values.");
|
|
171
236
|
}
|
|
172
237
|
}
|
|
238
|
+
out.push(...waveLines(report, options.approver ?? "<you>"));
|
|
173
239
|
if (report.selection.unclaimed.length > 0) {
|
|
174
240
|
out.push("", `Changed, and deployed by no component: ${report.selection.unclaimed.map(code).join(", ")}.`);
|
|
175
241
|
}
|
|
@@ -178,9 +244,39 @@ export function renderPrNote(report, options = {}) {
|
|
|
178
244
|
const each = deployed.map(([dep, names]) => `${code(dep)} (${names.length > 0 ? names.map(code).join(", ") : "none yet"})`);
|
|
179
245
|
out.push("", `Planned against outputs read from the deployed estate: ${each.join("; ")}.`);
|
|
180
246
|
}
|
|
247
|
+
const planned = Object.entries(report.plannedOutputs ?? {});
|
|
248
|
+
if (planned.length > 0) {
|
|
249
|
+
const each = planned.map(([dep, names]) => `${code(dep)} (${names.length > 0 ? names.map(code).join(", ") : "none known"})`);
|
|
250
|
+
out.push("", `Planned against the outputs this change's plans say its upstreams will have: ${each.join("; ")}.`);
|
|
251
|
+
}
|
|
252
|
+
const unknown = [...new Set(report.members.flatMap((m) => (m.unknownInputs ?? []).map((ref) => `${code(m.component)} reads ${code(ref)}`)))];
|
|
253
|
+
if (unknown.length > 0) {
|
|
254
|
+
out.push("", `Known only after apply: ${unknown.join("; ")}. This approval does not cover what those members do with them. ` +
|
|
255
|
+
"The apply plans them again once their upstream applied and asks for an approval of that plan.");
|
|
256
|
+
}
|
|
257
|
+
const fromBuild = [...new Set(report.members.flatMap((m) => (m.buildInputs ?? []).map((ref) => `${code(m.component)} reads ${code(ref)}`)))];
|
|
258
|
+
if (fromBuild.length > 0) {
|
|
259
|
+
out.push("", `From this pull request's build: ${fromBuild.join("; ")}. The image digest comes from this pull request's build, whose provenance binds it, ` +
|
|
260
|
+
"so those members apply with it once the build ran, without a second approval.");
|
|
261
|
+
}
|
|
262
|
+
for (const r of report.rounds ?? []) {
|
|
263
|
+
const what = `Round ${r.round} planned ${r.components.map(code).join(", ")} again after their upstream applied, digest ${code(r.digest)}`;
|
|
264
|
+
if (r.status === "waiting") {
|
|
265
|
+
if (r.message)
|
|
266
|
+
out.push("", `> ${r.message}`);
|
|
267
|
+
out.push("", `${what}. Not approved yet. Approve it, then run the apply again with the same \`--resume\` record:`, "", "```", prApproveCommand({ op: report.op, gate: r.gate, digest: r.digest }, options.approver ?? "<you>"), "```");
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
out.push("", `${what}: ${r.status === "applied" ? `applied, approved by ${(r.approval.approvedBy ?? []).map(code).join(", ")}` : r.status === "plan-failed" ? "a member failed to plan" : "the apply failed"}.`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
181
273
|
if (report.status !== "nothing") {
|
|
182
274
|
out.push("", `Plan digest: ${code(report.digest)}`, "", ...approvalLines(report, options.approver ?? "<you>"));
|
|
183
275
|
}
|
|
276
|
+
// #4003: with a gate per wave, a later wave's plan on merge may differ from this one.
|
|
277
|
+
if (report.stage === "plan" && report.status === "planned" && report.waveGate) {
|
|
278
|
+
out.push("", `On merge each wave after the first is planned again once the waves before it apply, and a wave whose plan moved stops at its own gate, ${code(`${report.waveGate}-wave-<k>`)}, for approval.`);
|
|
279
|
+
}
|
|
184
280
|
const head = out.join("\n") + "\n";
|
|
185
281
|
if (report.status === "nothing" || report.changeSet.members.length === 0)
|
|
186
282
|
return head;
|
|
@@ -210,20 +306,40 @@ export function prStatusDescription(report) {
|
|
|
210
306
|
case "applied":
|
|
211
307
|
text = `applied ${n} member${n === 1 ? "" : "s"}${digest}`;
|
|
212
308
|
break;
|
|
309
|
+
case "wave-applied": {
|
|
310
|
+
const last = (report.waves ?? []).filter((w) => w.status === "applied").at(-1);
|
|
311
|
+
text = `applied wave ${last?.wave ?? "?"} of ${report.selection.waves.length}${last ? ` under ${last.approval === "pull-request" ? "the pull request's approval" : `${last.gate}`}` : ""}`;
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
213
314
|
case "failed":
|
|
214
315
|
text = `apply failed for ${report.members.filter((m) => m.status === "failed").map((m) => m.member).join(", ")}`;
|
|
215
316
|
break;
|
|
216
|
-
case "
|
|
217
|
-
text = `
|
|
317
|
+
case "waiting":
|
|
318
|
+
text = `waiting: ${report.members.filter((m) => m.status === "waiting").map((m) => m.member).join(", ")} planned again after an upstream applied, not approved yet`;
|
|
218
319
|
break;
|
|
320
|
+
case "refused": {
|
|
321
|
+
const waiting = report.waves?.find((w) => w.status === "waiting");
|
|
322
|
+
if (waiting && (report.refusal === "wave-not-approved" || report.refusal === "wave-plan-changed")) {
|
|
323
|
+
text = `wave ${waiting.wave} waits at ${waiting.gate}: ${report.refusal === "wave-plan-changed" ? "its plan changed after its approval" : "its plan moved, approve it"}`;
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
if (report.refusal === "earlier-wave") {
|
|
327
|
+
text = "refused: an earlier wave has not applied";
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
text = `refused: ${report.refusal === "plan-changed" ? "the plan changed after review" : report.refusal === "not-a-reviewer" ? "the approver has not approved the pull request" : report.refusal === "author-approved" ? "the approver wrote the change" : report.refusal === "no-pull-request" ? "no pull request merged this commit" : "the plan is not approved"}`;
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
219
333
|
}
|
|
220
334
|
return text.length > 140 ? text.slice(0, 137) + "..." : text;
|
|
221
335
|
}
|
|
222
336
|
/** The commit status state for a report: success, failure, or pending while it waits on approval. */
|
|
223
337
|
export function prStatusState(report) {
|
|
224
|
-
if (report.status === "planned" || report.status === "applied" || report.status === "nothing")
|
|
338
|
+
if (report.status === "planned" || report.status === "applied" || report.status === "wave-applied" || report.status === "nothing")
|
|
225
339
|
return "success";
|
|
226
|
-
if (report.status === "refused" && report.refusal === "not-approved")
|
|
340
|
+
if (report.status === "refused" && (report.refusal === "not-approved" || report.refusal === "wave-not-approved"))
|
|
341
|
+
return "pending";
|
|
342
|
+
if (report.status === "waiting")
|
|
227
343
|
return "pending";
|
|
228
344
|
return "failure";
|
|
229
345
|
}
|
package/dist/pr-report-url.d.ts
CHANGED
|
@@ -44,8 +44,8 @@ export type ReportTarget = {
|
|
|
44
44
|
bucket: string;
|
|
45
45
|
prefix: string;
|
|
46
46
|
};
|
|
47
|
-
/** `--report-url` read, or why it can't be. */
|
|
48
|
-
export declare function parseReportUrl(raw: string): ReportTarget | {
|
|
47
|
+
/** `--report-url` read, or why it can't be. `name` is what the error calls the value: the flag, or `ci.reportUrl` (#4160). */
|
|
48
|
+
export declare function parseReportUrl(raw: string, name?: string): ReportTarget | {
|
|
49
49
|
error: string;
|
|
50
50
|
};
|
|
51
51
|
/** The key a stage's files are kept under, below the URL: `<pr>/<commit>[/<member>]`. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pr-report-url.d.ts","sourceRoot":"","sources":["../src/pr-report-url.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAOH,mCAAmC;AACnC,MAAM,MAAM,YAAY,GACpB;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC5C;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAElE
|
|
1
|
+
{"version":3,"file":"pr-report-url.d.ts","sourceRoot":"","sources":["../src/pr-report-url.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAOH,mCAAmC;AACnC,MAAM,MAAM,YAAY,GACpB;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC5C;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAElE,8HAA8H;AAC9H,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,SAAiB,GAAG,YAAY,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAkBnG;AAED,yFAAyF;AACzF,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAEpF;AAED,kCAAkC;AAClC,eAAO,MAAM,gBAAgB,UAAW,MAAM,GAAG,OAAO,KAAG,MAAoC,CAAC;AAEhG,mFAAmF;AACnF,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAE1F,kGAAkG;AAClG,MAAM,MAAM,MAAM,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAItE,2BAA2B;AAC3B,eAAO,MAAM,UAAU,EAAE,MAmBrB,CAAC;AAEL,gEAAgE;AAChE,wBAAgB,SAAS,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,GAAE,MAAmB,GAAG,SAAS,CAcnF;AAED,6JAA6J;AAC7J,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AAEhE,8GAA8G;AAC9G,MAAM,MAAM,OAAO,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAE1D,4EAA4E;AAC5E,eAAO,MAAM,WAAW,EAAE,OAiBtB,CAAC;AAKL,gEAAgE;AAChE,wBAAgB,SAAS,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,GAAE,OAAqB,GAAG,SAAS,CAoBrF;AAED,kFAAkF;AAClF,MAAM,WAAW,cAAc;IAC7B,yDAAyD;IACzD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,oGAAoG;IACpG,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,8EAA8E;IAC9E,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,+FAA+F;IAC/F,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAQD,mFAAmF;AACnF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACxC,MAAM,EAAE,YAAY,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;IACxB,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,GAAG,CAAC,EAAE,SAAS,CAAC;CACjB,GAAG,cAAc,CA6DjB"}
|
package/dist/pr-report-url.js
CHANGED
|
@@ -37,8 +37,8 @@ import { spawn } from "node:child_process";
|
|
|
37
37
|
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
38
38
|
import { dirname, isAbsolute, join } from "node:path";
|
|
39
39
|
import { fileURLToPath } from "node:url";
|
|
40
|
-
/** `--report-url` read, or why it can't be. */
|
|
41
|
-
export function parseReportUrl(raw) {
|
|
40
|
+
/** `--report-url` read, or why it can't be. `name` is what the error calls the value: the flag, or `ci.reportUrl` (#4160). */
|
|
41
|
+
export function parseReportUrl(raw, name = "--report-url") {
|
|
42
42
|
const url = raw.trim();
|
|
43
43
|
if (url.startsWith("file://")) {
|
|
44
44
|
let dir;
|
|
@@ -46,10 +46,10 @@ export function parseReportUrl(raw) {
|
|
|
46
46
|
dir = fileURLToPath(url);
|
|
47
47
|
}
|
|
48
48
|
catch (err) {
|
|
49
|
-
return { error:
|
|
49
|
+
return { error: `${name} ${url}: ${err instanceof Error ? err.message : String(err)}` };
|
|
50
50
|
}
|
|
51
51
|
if (!isAbsolute(dir))
|
|
52
|
-
return { error:
|
|
52
|
+
return { error: `${name} ${url}: a file URL names an absolute directory` };
|
|
53
53
|
return { scheme: "file", url: url.replace(/\/+$/, ""), dir };
|
|
54
54
|
}
|
|
55
55
|
const s3 = /^s3:\/\/([^/]+)\/?(.*)$/.exec(url);
|
|
@@ -57,7 +57,7 @@ export function parseReportUrl(raw) {
|
|
|
57
57
|
const prefix = s3[2].replace(/\/+$/, "");
|
|
58
58
|
return { scheme: "s3", url: `s3://${s3[1]}${prefix ? `/${prefix}` : ""}`, bucket: s3[1], prefix };
|
|
59
59
|
}
|
|
60
|
-
return { error:
|
|
60
|
+
return { error: `${name} ${url}: takes an s3://<bucket>/<prefix> or file://<directory> URL` };
|
|
61
61
|
}
|
|
62
62
|
/** The key a stage's files are kept under, below the URL: `<pr>/<commit>[/<member>]`. */
|
|
63
63
|
export function reportKey(pr, commit, member) {
|
package/dist/ts-loader.d.ts
CHANGED
|
@@ -18,14 +18,31 @@
|
|
|
18
18
|
*
|
|
19
19
|
* Run from source (the repository's `bin/chant` through tsx, vitest), this
|
|
20
20
|
* module is itself TypeScript, the TypeScript loader is already there, and
|
|
21
|
-
* both functions do nothing
|
|
21
|
+
* both functions do nothing, except that {@link installCompiledCli} resolves
|
|
22
|
+
* chant's packages to their source (#3981, {@link installSourceResolution}).
|
|
22
23
|
*/
|
|
23
24
|
/**
|
|
24
25
|
* Called once by the CLI's `main()`. Compiled, it records that TypeScript
|
|
25
|
-
* loads lazily and installs the `src/*.ts` to `dist/*.js` redirect
|
|
26
|
-
* source it
|
|
26
|
+
* loads lazily and installs the `src/*.ts` to `dist/*.js` redirect. From
|
|
27
|
+
* source it installs {@link installSourceResolution}.
|
|
27
28
|
*/
|
|
28
29
|
export declare function installCompiledCli(): void;
|
|
30
|
+
/**
|
|
31
|
+
* #3981 — the compiled module for a lexicon's `src/*.ts`: `<lexicon>/dist/*.js`
|
|
32
|
+
* when it exists, as a URL, else `undefined`. A lexicon ships both, and its
|
|
33
|
+
* exports name the compiled one; fold reads a composite from the source, and
|
|
34
|
+
* an import that source makes must land on the module the plugin and the
|
|
35
|
+
* project already loaded, with no TypeScript loader.
|
|
36
|
+
*/
|
|
37
|
+
export declare function lexiconCompiledTwin(url: string): string | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* #3981 — the `development` target a chant package's `exports` give
|
|
40
|
+
* `specifier`, found from `parentPath`'s directory up through each
|
|
41
|
+
* `node_modules`, or `undefined` when there is none or its file does not
|
|
42
|
+
* exist. Reads exact keys and single-`*` patterns, the shapes chant's
|
|
43
|
+
* packages use; anything else is left to Node.
|
|
44
|
+
*/
|
|
45
|
+
export declare function chantPackageSourceTarget(specifier: string, parentPath: string): string | undefined;
|
|
29
46
|
/**
|
|
30
47
|
* Register tsx for the ESM and CommonJS loaders, once, before importing
|
|
31
48
|
* anything that may be TypeScript. A no-op from source, where tsx (or vitest)
|
|
@@ -34,13 +51,30 @@ export declare function installCompiledCli(): void;
|
|
|
34
51
|
* CommonJS hook from source knows not to add a second one.
|
|
35
52
|
*/
|
|
36
53
|
export declare function ensureTypeScriptLoader(): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* #3981 — register tsx only when the module about to be imported is not
|
|
56
|
+
* JavaScript. `pathOrUrl` is the resolved module, a path or a file URL. A
|
|
57
|
+
* compiled lexicon (`dist/*.js`) imports with plain node, so a command that
|
|
58
|
+
* loads only compiled modules never pays for tsx's hooks thread. A `.ts`
|
|
59
|
+
* module, or one with no extension, registers tsx as before. Returns what
|
|
60
|
+
* {@link ensureTypeScriptLoader} does, or whether tsx is already registered
|
|
61
|
+
* when the module is JavaScript.
|
|
62
|
+
*/
|
|
63
|
+
export declare function ensureTypeScriptLoaderFor(pathOrUrl: string): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* #3981 — whether a caller about to import a module of unknown kind should
|
|
66
|
+
* resolve it first: true only in the compiled CLI before tsx is registered.
|
|
67
|
+
* From source, and once tsx is in, every module loads either way.
|
|
68
|
+
*/
|
|
69
|
+
export declare function typeScriptLoaderPending(): boolean;
|
|
37
70
|
/**
|
|
38
71
|
* The source directory for a module of chant's own: `dirname` of `moduleUrl`
|
|
39
72
|
* from source, and the matching directory under `<package>/src` when the
|
|
40
73
|
* module runs from `<package>/dist`. The sandbox drivers hand chant's own
|
|
41
74
|
* modules to esbuild by path, and must name the TypeScript source the
|
|
42
75
|
* project's own `@intentius/chant/*` imports resolve to. String arithmetic
|
|
43
|
-
* only: no filesystem access, so it is safe inside a sandboxed child.
|
|
76
|
+
* only: no filesystem access, so it is safe inside a sandboxed child. The
|
|
77
|
+
* same mapping as {@link moduleSourceDir}, which lexicons use.
|
|
44
78
|
*/
|
|
45
79
|
export declare function chantSourceDir(moduleUrl: string): string;
|
|
46
80
|
//# sourceMappingURL=ts-loader.d.ts.map
|
package/dist/ts-loader.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ts-loader.d.ts","sourceRoot":"","sources":["../src/ts-loader.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"ts-loader.d.ts","sourceRoot":"","sources":["../src/ts-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAqCH;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CA+BzC;AAkCD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAUnE;AAID;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CA0ClG;AAoCD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAShD;AAED;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAGpE;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,IAAI,OAAO,CAGjD;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAExD"}
|
package/dist/ts-loader.js
CHANGED
|
@@ -18,12 +18,14 @@
|
|
|
18
18
|
*
|
|
19
19
|
* Run from source (the repository's `bin/chant` through tsx, vitest), this
|
|
20
20
|
* module is itself TypeScript, the TypeScript loader is already there, and
|
|
21
|
-
* both functions do nothing
|
|
21
|
+
* both functions do nothing, except that {@link installCompiledCli} resolves
|
|
22
|
+
* chant's packages to their source (#3981, {@link installSourceResolution}).
|
|
22
23
|
*/
|
|
23
|
-
import { existsSync } from "node:fs";
|
|
24
|
+
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
24
25
|
import * as nodeModule from "node:module";
|
|
25
26
|
import { dirname, join, sep } from "node:path";
|
|
26
27
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
28
|
+
import { isJavaScriptModule, moduleSourceDir } from "./module-source.js";
|
|
27
29
|
const { createRequire, registerHooks } = nodeModule;
|
|
28
30
|
const STATE = Symbol.for("chant.compiledCli");
|
|
29
31
|
function compiledState() {
|
|
@@ -35,11 +37,15 @@ function runsCompiled() {
|
|
|
35
37
|
}
|
|
36
38
|
/**
|
|
37
39
|
* Called once by the CLI's `main()`. Compiled, it records that TypeScript
|
|
38
|
-
* loads lazily and installs the `src/*.ts` to `dist/*.js` redirect
|
|
39
|
-
* source it
|
|
40
|
+
* loads lazily and installs the `src/*.ts` to `dist/*.js` redirect. From
|
|
41
|
+
* source it installs {@link installSourceResolution}.
|
|
40
42
|
*/
|
|
41
43
|
export function installCompiledCli() {
|
|
42
|
-
if (!runsCompiled()
|
|
44
|
+
if (!runsCompiled()) {
|
|
45
|
+
installSourceResolution();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (compiledState())
|
|
43
49
|
return;
|
|
44
50
|
globalThis[STATE] = { tsx: false };
|
|
45
51
|
const distDir = dirname(fileURLToPath(import.meta.url));
|
|
@@ -49,8 +55,10 @@ export function installCompiledCli() {
|
|
|
49
55
|
registerHooks({
|
|
50
56
|
resolve(specifier, context, nextResolve) {
|
|
51
57
|
const result = nextResolve(specifier, context);
|
|
52
|
-
if (!result.url.startsWith(srcUrl))
|
|
53
|
-
|
|
58
|
+
if (!result.url.startsWith(srcUrl)) {
|
|
59
|
+
const twin = lexiconCompiledTwin(result.url);
|
|
60
|
+
return twin === undefined ? result : { ...result, url: twin, format: "module" };
|
|
61
|
+
}
|
|
54
62
|
const path = result.url.slice(srcUrl.length).replace(/[?#].*$/, "");
|
|
55
63
|
if (!path.endsWith(".ts") || path.endsWith(".d.ts"))
|
|
56
64
|
return result;
|
|
@@ -64,6 +72,155 @@ export function installCompiledCli() {
|
|
|
64
72
|
},
|
|
65
73
|
});
|
|
66
74
|
}
|
|
75
|
+
/** Package directories by the directory a module sits in: a lexicon package's root, or null for any other package. */
|
|
76
|
+
const lexiconRoots = new Map();
|
|
77
|
+
/** The lexicon package `dir` belongs to: the nearest directory up with a package.json, when it names a chant lexicon. */
|
|
78
|
+
function lexiconRootOf(dir) {
|
|
79
|
+
const seen = [];
|
|
80
|
+
let root = null;
|
|
81
|
+
for (let at = dir;;) {
|
|
82
|
+
const cached = lexiconRoots.get(at);
|
|
83
|
+
if (cached !== undefined) {
|
|
84
|
+
root = cached;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
seen.push(at);
|
|
88
|
+
const manifest = join(at, "package.json");
|
|
89
|
+
if (existsSync(manifest)) {
|
|
90
|
+
try {
|
|
91
|
+
const name = JSON.parse(readFileSync(manifest, "utf-8")).name;
|
|
92
|
+
root = typeof name === "string" && name.startsWith("@intentius/chant-lexicon-") ? at : null;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
root = null;
|
|
96
|
+
}
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
const parent = dirname(at);
|
|
100
|
+
if (parent === at)
|
|
101
|
+
break;
|
|
102
|
+
at = parent;
|
|
103
|
+
}
|
|
104
|
+
for (const at of seen)
|
|
105
|
+
lexiconRoots.set(at, root);
|
|
106
|
+
return root ?? undefined;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* #3981 — the compiled module for a lexicon's `src/*.ts`: `<lexicon>/dist/*.js`
|
|
110
|
+
* when it exists, as a URL, else `undefined`. A lexicon ships both, and its
|
|
111
|
+
* exports name the compiled one; fold reads a composite from the source, and
|
|
112
|
+
* an import that source makes must land on the module the plugin and the
|
|
113
|
+
* project already loaded, with no TypeScript loader.
|
|
114
|
+
*/
|
|
115
|
+
export function lexiconCompiledTwin(url) {
|
|
116
|
+
if (!url.startsWith("file:"))
|
|
117
|
+
return undefined;
|
|
118
|
+
const path = fileURLToPath(url.replace(/[?#].*$/, ""));
|
|
119
|
+
if (!/\.[cm]?ts$/.test(path) || /\.d\.[cm]?ts$/.test(path) || !path.includes(`${sep}src${sep}`))
|
|
120
|
+
return undefined;
|
|
121
|
+
const root = lexiconRootOf(dirname(path));
|
|
122
|
+
if (root === undefined)
|
|
123
|
+
return undefined;
|
|
124
|
+
const srcDir = join(root, "src") + sep;
|
|
125
|
+
if (!path.startsWith(srcDir))
|
|
126
|
+
return undefined;
|
|
127
|
+
const js = join(root, "dist", path.slice(srcDir.length)).replace(/\.([cm]?)ts$/, ".$1js");
|
|
128
|
+
return existsSync(js) ? pathToFileURL(js).href : undefined;
|
|
129
|
+
}
|
|
130
|
+
const SOURCE_RESOLUTION = Symbol.for("chant.sourceResolution");
|
|
131
|
+
/**
|
|
132
|
+
* #3981 — the `development` target a chant package's `exports` give
|
|
133
|
+
* `specifier`, found from `parentPath`'s directory up through each
|
|
134
|
+
* `node_modules`, or `undefined` when there is none or its file does not
|
|
135
|
+
* exist. Reads exact keys and single-`*` patterns, the shapes chant's
|
|
136
|
+
* packages use; anything else is left to Node.
|
|
137
|
+
*/
|
|
138
|
+
export function chantPackageSourceTarget(specifier, parentPath) {
|
|
139
|
+
const match = /^(@intentius\/chant(?:-[a-z0-9-]+)?)(\/.*)?$/.exec(specifier);
|
|
140
|
+
if (!match)
|
|
141
|
+
return undefined;
|
|
142
|
+
const subpath = match[2] ? `.${match[2]}` : ".";
|
|
143
|
+
let dir = dirname(parentPath);
|
|
144
|
+
for (;;) {
|
|
145
|
+
const pkgDir = join(dir, "node_modules", match[1]);
|
|
146
|
+
const manifest = join(pkgDir, "package.json");
|
|
147
|
+
if (existsSync(manifest)) {
|
|
148
|
+
let exportsField;
|
|
149
|
+
try {
|
|
150
|
+
exportsField = JSON.parse(readFileSync(manifest, "utf-8")).exports;
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
if (!exportsField || typeof exportsField !== "object")
|
|
156
|
+
return undefined;
|
|
157
|
+
const map = exportsField;
|
|
158
|
+
let entry = map[subpath];
|
|
159
|
+
let star;
|
|
160
|
+
if (entry === undefined) {
|
|
161
|
+
let best = "";
|
|
162
|
+
for (const key of Object.keys(map)) {
|
|
163
|
+
const at = key.indexOf("*");
|
|
164
|
+
if (at < 0 || key.indexOf("*", at + 1) >= 0)
|
|
165
|
+
continue;
|
|
166
|
+
const prefix = key.slice(0, at);
|
|
167
|
+
const suffix = key.slice(at + 1);
|
|
168
|
+
if (!subpath.startsWith(prefix) || !subpath.endsWith(suffix) || subpath.length < key.length || prefix.length <= best.length)
|
|
169
|
+
continue;
|
|
170
|
+
best = prefix;
|
|
171
|
+
entry = map[key];
|
|
172
|
+
star = subpath.slice(prefix.length, subpath.length - suffix.length);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const development = entry && typeof entry === "object" ? entry.development : undefined;
|
|
176
|
+
if (typeof development !== "string")
|
|
177
|
+
return undefined;
|
|
178
|
+
const target = join(pkgDir, star === undefined ? development : development.split("*").join(star));
|
|
179
|
+
// Node keys a module by its real path, so a symlinked package must too.
|
|
180
|
+
return existsSync(target) ? realpathSync(target) : undefined;
|
|
181
|
+
}
|
|
182
|
+
const parent = dirname(dir);
|
|
183
|
+
if (parent === dir)
|
|
184
|
+
return undefined;
|
|
185
|
+
dir = parent;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* #3981 — from source, resolve chant's own packages through their
|
|
190
|
+
* `development` export condition, as `node --conditions=development` (which
|
|
191
|
+
* the repository's `bin/chant` passes) would. A lexicon's `default` export is
|
|
192
|
+
* its compiled `dist/*.js`, which a checkout may not have built, or built from
|
|
193
|
+
* older source; `development` is its `src/*.ts`, which vitest, a project's
|
|
194
|
+
* tsconfig `paths` and fold's own resolution (`SOURCE_EXPORT_CONDITIONS`)
|
|
195
|
+
* pick. Without this, a CLI run from source by `node --import tsx` would load
|
|
196
|
+
* a plugin from `dist/` and fold the project against `src/`: two copies of the
|
|
197
|
+
* lexicon with their own classes.
|
|
198
|
+
*
|
|
199
|
+
* The hook resolves the specifier itself and short-circuits: conditions added
|
|
200
|
+
* to the context here do not reach tsx's resolver. Nothing is installed under
|
|
201
|
+
* vitest, whose resolver already adds the condition, or on a node without
|
|
202
|
+
* `module.registerHooks`.
|
|
203
|
+
*/
|
|
204
|
+
function installSourceResolution() {
|
|
205
|
+
// In a vitest worker (not a CLI it spawns, which inherits its environment), vite resolves.
|
|
206
|
+
if ("__vitest_worker__" in globalThis || typeof registerHooks !== "function")
|
|
207
|
+
return;
|
|
208
|
+
const flags = globalThis;
|
|
209
|
+
if (flags[SOURCE_RESOLUTION])
|
|
210
|
+
return;
|
|
211
|
+
flags[SOURCE_RESOLUTION] = true;
|
|
212
|
+
registerHooks({
|
|
213
|
+
resolve(specifier, context, nextResolve) {
|
|
214
|
+
const parentURL = context?.parentURL;
|
|
215
|
+
if (specifier.startsWith("@intentius/chant") && parentURL?.startsWith("file:")) {
|
|
216
|
+
const target = chantPackageSourceTarget(specifier, fileURLToPath(parentURL));
|
|
217
|
+
if (target !== undefined)
|
|
218
|
+
return { url: pathToFileURL(target).href, shortCircuit: true };
|
|
219
|
+
}
|
|
220
|
+
return nextResolve(specifier, context);
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
}
|
|
67
224
|
/**
|
|
68
225
|
* Register tsx for the ESM and CommonJS loaders, once, before importing
|
|
69
226
|
* anything that may be TypeScript. A no-op from source, where tsx (or vitest)
|
|
@@ -83,21 +240,38 @@ export function ensureTypeScriptLoader() {
|
|
|
83
240
|
require("tsx/esm/api").register();
|
|
84
241
|
return true;
|
|
85
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* #3981 — register tsx only when the module about to be imported is not
|
|
245
|
+
* JavaScript. `pathOrUrl` is the resolved module, a path or a file URL. A
|
|
246
|
+
* compiled lexicon (`dist/*.js`) imports with plain node, so a command that
|
|
247
|
+
* loads only compiled modules never pays for tsx's hooks thread. A `.ts`
|
|
248
|
+
* module, or one with no extension, registers tsx as before. Returns what
|
|
249
|
+
* {@link ensureTypeScriptLoader} does, or whether tsx is already registered
|
|
250
|
+
* when the module is JavaScript.
|
|
251
|
+
*/
|
|
252
|
+
export function ensureTypeScriptLoaderFor(pathOrUrl) {
|
|
253
|
+
if (!isJavaScriptModule(pathOrUrl))
|
|
254
|
+
return ensureTypeScriptLoader();
|
|
255
|
+
return compiledState()?.tsx ?? false;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* #3981 — whether a caller about to import a module of unknown kind should
|
|
259
|
+
* resolve it first: true only in the compiled CLI before tsx is registered.
|
|
260
|
+
* From source, and once tsx is in, every module loads either way.
|
|
261
|
+
*/
|
|
262
|
+
export function typeScriptLoaderPending() {
|
|
263
|
+
const state = compiledState();
|
|
264
|
+
return state !== undefined && !state.tsx;
|
|
265
|
+
}
|
|
86
266
|
/**
|
|
87
267
|
* The source directory for a module of chant's own: `dirname` of `moduleUrl`
|
|
88
268
|
* from source, and the matching directory under `<package>/src` when the
|
|
89
269
|
* module runs from `<package>/dist`. The sandbox drivers hand chant's own
|
|
90
270
|
* modules to esbuild by path, and must name the TypeScript source the
|
|
91
271
|
* project's own `@intentius/chant/*` imports resolve to. String arithmetic
|
|
92
|
-
* only: no filesystem access, so it is safe inside a sandboxed child.
|
|
272
|
+
* only: no filesystem access, so it is safe inside a sandboxed child. The
|
|
273
|
+
* same mapping as {@link moduleSourceDir}, which lexicons use.
|
|
93
274
|
*/
|
|
94
275
|
export function chantSourceDir(moduleUrl) {
|
|
95
|
-
|
|
96
|
-
if (!moduleUrl.endsWith(".js"))
|
|
97
|
-
return dir;
|
|
98
|
-
const marker = `${sep}dist${sep}`;
|
|
99
|
-
const at = (dir + sep).lastIndexOf(marker);
|
|
100
|
-
if (at < 0)
|
|
101
|
-
return dir;
|
|
102
|
-
return dir.slice(0, at) + `${sep}src` + dir.slice(at + marker.length - 1);
|
|
276
|
+
return moduleSourceDir(moduleUrl);
|
|
103
277
|
}
|