@intentius/chant 0.124.33 → 0.124.34
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/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/promote.js +261 -73
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +18 -1
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +7 -0
- package/dist/cli/registry.d.ts +3 -1
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/components/promote-rounds.d.ts +102 -0
- package/dist/components/promote-rounds.d.ts.map +1 -0
- package/dist/components/promote-rounds.js +174 -0
- package/dist/components/release-gate.d.ts +30 -0
- package/dist/components/release-gate.d.ts.map +1 -1
- package/dist/components/release-gate.js +92 -42
- package/dist/graph-declared.d.ts +4 -0
- package/dist/graph-declared.d.ts.map +1 -1
- package/dist/graph-declared.js +11 -1
- package/dist/wave-runtime.mjs +39 -39
- package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/contract-repo.js +13 -2
- package/dist/workspace/check.schema.json +20 -2
- package/dist/workspace/checks/ci-workflow.d.ts +17 -0
- package/dist/workspace/checks/ci-workflow.d.ts.map +1 -0
- package/dist/workspace/checks/ci-workflow.js +123 -0
- package/dist/workspace/checks/security.d.ts +16 -1
- package/dist/workspace/checks/security.d.ts.map +1 -1
- package/dist/workspace/checks/security.js +4 -4
- package/dist/workspace/checks.d.ts +1 -1
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/checks.js +4 -1
- package/dist/workspace/ci-init.d.ts +100 -0
- package/dist/workspace/ci-init.d.ts.map +1 -0
- package/dist/workspace/ci-init.js +434 -0
- package/dist/workspace/ci-workflow.d.ts +91 -0
- package/dist/workspace/ci-workflow.d.ts.map +1 -0
- package/dist/workspace/ci-workflow.js +33 -0
- package/dist/workspace/declaration.d.ts +18 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +9 -2
- package/dist/workspace/declaration.schema.json +28 -0
- package/dist/workspace/init.js +2 -2
- package/dist/workspace/reason-codes.d.ts +9 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +10 -0
- package/dist/workspace/release-workflow.d.ts +31 -0
- package/dist/workspace/release-workflow.d.ts.map +1 -0
- package/dist/workspace/release-workflow.js +150 -0
- package/package.json +1 -1
- package/src/cli/handlers/promote.ts +280 -83
- package/src/cli/main.ts +18 -1
- package/src/cli/parse-args.ts +5 -0
- package/src/cli/registry.ts +3 -1
- package/src/components/promote-rounds.test.ts +220 -0
- package/src/components/promote-rounds.ts +232 -0
- package/src/components/release-gate.test.ts +47 -0
- package/src/components/release-gate.ts +111 -46
- package/src/graph-declared-exports.test.ts +40 -0
- package/src/graph-declared.ts +9 -1
- package/src/workspace/__fixtures__/contract-repo.ts +11 -2
- package/src/workspace/check-contract.test.ts +3 -2
- package/src/workspace/check.schema.json +20 -2
- package/src/workspace/checks/ci-workflow.test.ts +150 -0
- package/src/workspace/checks/ci-workflow.ts +129 -0
- package/src/workspace/checks/security.ts +4 -4
- package/src/workspace/checks.ts +4 -1
- package/src/workspace/ci-init.test.ts +159 -0
- package/src/workspace/ci-init.ts +445 -0
- package/src/workspace/ci-report-url.test.ts +6 -1
- package/src/workspace/ci-workflow.ts +71 -0
- package/src/workspace/declaration.schema.json +28 -0
- package/src/workspace/declaration.ts +32 -3
- package/src/workspace/init.ts +2 -2
- package/src/workspace/reason-codes.test.ts +2 -0
- package/src/workspace/reason-codes.ts +10 -0
- package/src/workspace/release-workflow.ts +167 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A promote under a release that plans again once an upstream applied, and a
|
|
3
|
+
* gate per wave (#4168 step 4). Merging deploys nothing since step 1, so what
|
|
4
|
+
* `pr-apply` did on merge happens here, at promote:
|
|
5
|
+
*
|
|
6
|
+
* - **Rounds** (#3999's rule, `../pr-loop.ts`). The release gate approves a
|
|
7
|
+
* change set in which each dependent was planned against its upstream's
|
|
8
|
+
* planned outputs (`planReleaseSet` in ./release-gate.ts). The promote runs
|
|
9
|
+
* a wave at a time. Before a wave runs, a component whose plan read an
|
|
10
|
+
* upstream output that the run left as another value
|
|
11
|
+
* (`movedWiringReferences`) is planned again against what stands. When the
|
|
12
|
+
* approved change set covers the new plan (`waveNotCovered`, the test a
|
|
13
|
+
* pull request's resume uses), it applies under the approval it has.
|
|
14
|
+
* Otherwise its gates are decided as `<gate>-round-<n>` (`prRoundGate`),
|
|
15
|
+
* bound to the new plan's digest.
|
|
16
|
+
* - **A gate per wave** (`--wave-gate <gate>`, #4157's rule). Every wave
|
|
17
|
+
* after the first is planned again against what the waves before it left,
|
|
18
|
+
* and a wave the approved change set does not cover waits at
|
|
19
|
+
* `<gate>-wave-<k>` (`waveGateName`), one gate for the whole wave, bound to
|
|
20
|
+
* that wave's digest.
|
|
21
|
+
*
|
|
22
|
+
* A stage whose components declare no gate has nowhere to wait, so the
|
|
23
|
+
* promote stops there with nothing more deployed.
|
|
24
|
+
*
|
|
25
|
+
* A promote that stops at a round's or a wave's gate writes a resume record
|
|
26
|
+
* ({@link PromoteResume}): the components that applied and the outputs they
|
|
27
|
+
* left. The same promote run again with it skips what applied and carries on
|
|
28
|
+
* from the wave it stopped at, so an approval of the round or wave it waits on
|
|
29
|
+
* is decided against the same plan.
|
|
30
|
+
*/
|
|
31
|
+
import { type DriverComponent, type DriverRunResult, type ReleaseGateBinding } from "./driver.js";
|
|
32
|
+
import type { ChangeSetDocument } from "../change-set.js";
|
|
33
|
+
import type { ReleasePlan } from "./release-gate.js";
|
|
34
|
+
export declare const PROMOTE_RESUME_SCHEMA = "chant-promote-resume/v1";
|
|
35
|
+
/** What a promote that stopped at a round's or a wave's gate leaves for the next attempt. */
|
|
36
|
+
export interface PromoteResume {
|
|
37
|
+
schema: typeof PROMOTE_RESUME_SCHEMA;
|
|
38
|
+
env: string;
|
|
39
|
+
releaseDigest: string;
|
|
40
|
+
/** The change set the release gate approved before anything applied. */
|
|
41
|
+
approvedDigest: string;
|
|
42
|
+
/** Components that applied, in run order. */
|
|
43
|
+
completed: string[];
|
|
44
|
+
/** The outputs standing after them. */
|
|
45
|
+
outputs: Record<string, Record<string, unknown>>;
|
|
46
|
+
/** ISO 8601. */
|
|
47
|
+
at: string;
|
|
48
|
+
}
|
|
49
|
+
/** One round or wave that needed its own approval. */
|
|
50
|
+
export interface PromoteStage {
|
|
51
|
+
kind: "round" | "wave";
|
|
52
|
+
/** The round number (2 and up) or the wave number (1-based). */
|
|
53
|
+
number: number;
|
|
54
|
+
components: string[];
|
|
55
|
+
/** The plan the stage's gates bind. */
|
|
56
|
+
digest: string;
|
|
57
|
+
/** Why the approved change set did not cover it, one line per member. */
|
|
58
|
+
notCovered: string[];
|
|
59
|
+
status: "applied" | "waiting" | "failed";
|
|
60
|
+
/** With `failed` before anything ran: why. */
|
|
61
|
+
message?: string;
|
|
62
|
+
}
|
|
63
|
+
export interface PromoteRoundsOptions {
|
|
64
|
+
/** The promoted compositions, in no particular order. */
|
|
65
|
+
components: DriverComponent[];
|
|
66
|
+
/** Outputs standing before the run: the seeds, and on a resume the record's. */
|
|
67
|
+
outputs: Record<string, Record<string, unknown>>;
|
|
68
|
+
/** The release gate the change set was approved under. */
|
|
69
|
+
binding: ReleaseGateBinding;
|
|
70
|
+
/** The approved change set, and what each component's plan read. */
|
|
71
|
+
approved: ChangeSetDocument;
|
|
72
|
+
reads: ReleasePlan["reads"];
|
|
73
|
+
/** Components an earlier attempt applied. Skipped. */
|
|
74
|
+
completed?: readonly string[];
|
|
75
|
+
/** `--wave-gate <gate>`: check every wave after the first, not only the components whose inputs moved, and name a wave's gate `<gate>-wave-<k>`. */
|
|
76
|
+
waveGate?: string;
|
|
77
|
+
/** Run one subset of the components with `outputs` standing (`runPromotion` with the binding). */
|
|
78
|
+
run: (components: DriverComponent[], outputs: Record<string, Record<string, unknown>>, binding: ReleaseGateBinding) => Promise<DriverRunResult>;
|
|
79
|
+
/** Plan `only` again with `outputs` standing (`planReleaseSet`). */
|
|
80
|
+
replan: (only: ReadonlySet<string>, outputs: Record<string, Record<string, unknown>>) => Promise<ChangeSetDocument>;
|
|
81
|
+
/** Store a stage's plan and bind its gates (empty resolutions included). */
|
|
82
|
+
bind: (doc: ChangeSetDocument, gates: string[]) => Promise<ReleaseGateBinding>;
|
|
83
|
+
}
|
|
84
|
+
export interface PromoteRoundsResult {
|
|
85
|
+
run: DriverRunResult;
|
|
86
|
+
/** The rounds and waves that needed their own approval. */
|
|
87
|
+
stages: PromoteStage[];
|
|
88
|
+
/** Every component that applied, earlier attempts included, in run order. */
|
|
89
|
+
completed: string[];
|
|
90
|
+
outputs: Record<string, Record<string, unknown>>;
|
|
91
|
+
}
|
|
92
|
+
/** `component` with each gate renamed by `rename`, nested phases included. */
|
|
93
|
+
export declare function renameGates(component: DriverComponent, rename: (gate: string) => string): DriverComponent;
|
|
94
|
+
/** The references `component`'s steps make that resolve to another value now than when its plan read them. */
|
|
95
|
+
export declare function movedInputs(component: DriverComponent, read: Record<string, Record<string, unknown>> | undefined, now: Record<string, Record<string, unknown>>): string[];
|
|
96
|
+
/**
|
|
97
|
+
* Run the promote a wave at a time, planning again where an upstream applied
|
|
98
|
+
* as another value, and with `waveGate`, checking each wave. Stops at the
|
|
99
|
+
* first wave that fails or waits on a gate.
|
|
100
|
+
*/
|
|
101
|
+
export declare function runPromotionInRounds(options: PromoteRoundsOptions): Promise<PromoteRoundsResult>;
|
|
102
|
+
//# sourceMappingURL=promote-rounds.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"promote-rounds.d.ts","sourceRoot":"","sources":["../../src/components/promote-rounds.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A promote under a release that plans again once an upstream applied, and a
|
|
3
|
+
* gate per wave (#4168 step 4). Merging deploys nothing since step 1, so what
|
|
4
|
+
* `pr-apply` did on merge happens here, at promote:
|
|
5
|
+
*
|
|
6
|
+
* - **Rounds** (#3999's rule, `../pr-loop.ts`). The release gate approves a
|
|
7
|
+
* change set in which each dependent was planned against its upstream's
|
|
8
|
+
* planned outputs (`planReleaseSet` in ./release-gate.ts). The promote runs
|
|
9
|
+
* a wave at a time. Before a wave runs, a component whose plan read an
|
|
10
|
+
* upstream output that the run left as another value
|
|
11
|
+
* (`movedWiringReferences`) is planned again against what stands. When the
|
|
12
|
+
* approved change set covers the new plan (`waveNotCovered`, the test a
|
|
13
|
+
* pull request's resume uses), it applies under the approval it has.
|
|
14
|
+
* Otherwise its gates are decided as `<gate>-round-<n>` (`prRoundGate`),
|
|
15
|
+
* bound to the new plan's digest.
|
|
16
|
+
* - **A gate per wave** (`--wave-gate <gate>`, #4157's rule). Every wave
|
|
17
|
+
* after the first is planned again against what the waves before it left,
|
|
18
|
+
* and a wave the approved change set does not cover waits at
|
|
19
|
+
* `<gate>-wave-<k>` (`waveGateName`), one gate for the whole wave, bound to
|
|
20
|
+
* that wave's digest.
|
|
21
|
+
*
|
|
22
|
+
* A stage whose components declare no gate has nowhere to wait, so the
|
|
23
|
+
* promote stops there with nothing more deployed.
|
|
24
|
+
*
|
|
25
|
+
* A promote that stops at a round's or a wave's gate writes a resume record
|
|
26
|
+
* ({@link PromoteResume}): the components that applied and the outputs they
|
|
27
|
+
* left. The same promote run again with it skips what applied and carries on
|
|
28
|
+
* from the wave it stopped at, so an approval of the round or wave it waits on
|
|
29
|
+
* is decided against the same plan.
|
|
30
|
+
*/
|
|
31
|
+
import { movedWiringReferences } from "./driver.js";
|
|
32
|
+
import { resolveComponentGraph } from "./driver.js";
|
|
33
|
+
import { waveNotCovered } from "./pr-run.js";
|
|
34
|
+
import { prRoundGate } from "../pr-loop.js";
|
|
35
|
+
import { waveGateName } from "../gated-waves.js";
|
|
36
|
+
import { capabilitySteps } from "./wave-plan.js";
|
|
37
|
+
export const PROMOTE_RESUME_SCHEMA = "chant-promote-resume/v1";
|
|
38
|
+
function isGate(step) {
|
|
39
|
+
return step.kind === "gate";
|
|
40
|
+
}
|
|
41
|
+
function isPhase(step) {
|
|
42
|
+
return typeof step.phase === "string" && Array.isArray(step.steps);
|
|
43
|
+
}
|
|
44
|
+
/** `component` with each gate renamed by `rename`, nested phases included. */
|
|
45
|
+
export function renameGates(component, rename) {
|
|
46
|
+
const walk = (phase) => ({
|
|
47
|
+
...phase,
|
|
48
|
+
steps: phase.steps.map((step) => {
|
|
49
|
+
if (isPhase(step))
|
|
50
|
+
return walk(step);
|
|
51
|
+
if (!isGate(step))
|
|
52
|
+
return step;
|
|
53
|
+
const { signalName: _old, ...rest } = step;
|
|
54
|
+
return { ...rest, gate: rename(step.gate ?? step.signalName ?? "") };
|
|
55
|
+
}),
|
|
56
|
+
});
|
|
57
|
+
return { ...component, deploy: component.deploy.map(walk) };
|
|
58
|
+
}
|
|
59
|
+
/** The gate names `components` declare, sorted and distinct. */
|
|
60
|
+
function gatesOf(components) {
|
|
61
|
+
const names = new Set();
|
|
62
|
+
const walk = (steps) => {
|
|
63
|
+
for (const step of steps) {
|
|
64
|
+
if (isPhase(step))
|
|
65
|
+
walk(step.steps);
|
|
66
|
+
else if (isGate(step))
|
|
67
|
+
names.add(step.gate ?? step.signalName ?? "");
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
for (const c of components)
|
|
71
|
+
for (const phase of c.deploy)
|
|
72
|
+
walk(phase.steps);
|
|
73
|
+
return [...names].sort();
|
|
74
|
+
}
|
|
75
|
+
/** The references `component`'s steps make that resolve to another value now than when its plan read them. */
|
|
76
|
+
export function movedInputs(component, read, now) {
|
|
77
|
+
if (!read)
|
|
78
|
+
return [];
|
|
79
|
+
const moved = new Set();
|
|
80
|
+
for (const step of capabilitySteps(component.deploy)) {
|
|
81
|
+
const { kind: _kind, ...rest } = step;
|
|
82
|
+
for (const r of movedWiringReferences(rest, read, now))
|
|
83
|
+
if (!r.publish)
|
|
84
|
+
moved.add(r.ref);
|
|
85
|
+
}
|
|
86
|
+
return [...moved].sort();
|
|
87
|
+
}
|
|
88
|
+
function joinRuns(runs, outputs) {
|
|
89
|
+
const last = runs[runs.length - 1];
|
|
90
|
+
const base = {
|
|
91
|
+
order: runs.flatMap((r) => r.order),
|
|
92
|
+
waves: runs.flatMap((r) => r.waves),
|
|
93
|
+
results: runs.flatMap((r) => r.results),
|
|
94
|
+
ok: runs.every((r) => r.ok),
|
|
95
|
+
status: "ok",
|
|
96
|
+
componentOutputs: outputs,
|
|
97
|
+
};
|
|
98
|
+
if (!last || last.status === "ok")
|
|
99
|
+
return base;
|
|
100
|
+
const { order: _o, waves: _w, results: _r, ok: _ok, componentOutputs: _c, ...stop } = last;
|
|
101
|
+
return { ...base, ...stop, ok: false };
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Run the promote a wave at a time, planning again where an upstream applied
|
|
105
|
+
* as another value, and with `waveGate`, checking each wave. Stops at the
|
|
106
|
+
* first wave that fails or waits on a gate.
|
|
107
|
+
*/
|
|
108
|
+
export async function runPromotionInRounds(options) {
|
|
109
|
+
const byName = new Map(options.components.map((c) => [c.name, c]));
|
|
110
|
+
const { waves } = resolveComponentGraph(options.components.map((c) => ({ ...c, dependsOn: (c.dependsOn ?? []).filter((d) => byName.has(d)) })));
|
|
111
|
+
const completed = [...(options.completed ?? [])];
|
|
112
|
+
const done = new Set(completed);
|
|
113
|
+
let outputs = { ...options.outputs };
|
|
114
|
+
const runs = [];
|
|
115
|
+
const stages = [];
|
|
116
|
+
let round = 1;
|
|
117
|
+
for (const [index, wave] of waves.entries()) {
|
|
118
|
+
const waveNumber = index + 1;
|
|
119
|
+
const pending = wave.filter((name) => !done.has(name));
|
|
120
|
+
if (pending.length === 0)
|
|
121
|
+
continue;
|
|
122
|
+
const check = options.waveGate !== undefined && waveNumber > 1
|
|
123
|
+
? pending
|
|
124
|
+
: pending.filter((name) => movedInputs(byName.get(name), options.reads[name], outputs).length > 0);
|
|
125
|
+
const batches = [];
|
|
126
|
+
const rest = pending.filter((name) => !check.includes(name));
|
|
127
|
+
if (check.length > 0) {
|
|
128
|
+
const fresh = await options.replan(new Set(check), outputs);
|
|
129
|
+
const notCovered = waveNotCovered(options.approved, fresh);
|
|
130
|
+
if (notCovered.length === 0) {
|
|
131
|
+
rest.push(...check);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
const kind = options.waveGate !== undefined ? "wave" : "round";
|
|
135
|
+
if (kind === "round")
|
|
136
|
+
round += 1;
|
|
137
|
+
const number = kind === "wave" ? waveNumber : round;
|
|
138
|
+
const base = options.waveGate;
|
|
139
|
+
const rename = (g) => (base !== undefined ? waveGateName(base, number) : prRoundGate(g, number));
|
|
140
|
+
const renamed = check.map((name) => renameGates(byName.get(name), rename));
|
|
141
|
+
const gates = gatesOf(renamed);
|
|
142
|
+
const stage = { kind, number, components: check, digest: fresh.digest, notCovered, status: "waiting" };
|
|
143
|
+
stages.push(stage);
|
|
144
|
+
if (gates.length === 0) {
|
|
145
|
+
stage.status = "failed";
|
|
146
|
+
stage.message = `${check.join(", ")} planned again past the approved change set and declare${check.length === 1 ? "s" : ""} no gate to approve the new plan on`;
|
|
147
|
+
const stop = { order: [], waves: [], results: [], ok: false, status: "fail", failedComponent: check[0], componentOutputs: outputs };
|
|
148
|
+
return { run: joinRuns([...runs, stop], outputs), stages, completed, outputs };
|
|
149
|
+
}
|
|
150
|
+
const binding = await options.bind(fresh, gates);
|
|
151
|
+
batches.push({ names: check, binding, stage, rename });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (rest.length > 0)
|
|
155
|
+
batches.unshift({ names: pending.filter((n) => rest.includes(n)), binding: options.binding });
|
|
156
|
+
for (const batch of batches) {
|
|
157
|
+
const components = batch.names.map((name) => (batch.rename ? renameGates(byName.get(name), batch.rename) : byName.get(name)));
|
|
158
|
+
const run = await options.run(components, outputs, batch.binding);
|
|
159
|
+
runs.push(run);
|
|
160
|
+
outputs = { ...outputs, ...run.componentOutputs };
|
|
161
|
+
for (const r of run.results) {
|
|
162
|
+
if (r.status === "ok" && !done.has(r.component)) {
|
|
163
|
+
done.add(r.component);
|
|
164
|
+
completed.push(r.component);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (batch.stage)
|
|
168
|
+
batch.stage.status = run.status === "ok" ? "applied" : run.status === "gated" ? "waiting" : "failed";
|
|
169
|
+
if (run.status !== "ok")
|
|
170
|
+
return { run: joinRuns(runs, outputs), stages, completed, outputs };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return { run: joinRuns(runs, outputs), stages, completed, outputs };
|
|
174
|
+
}
|
|
@@ -39,6 +39,14 @@
|
|
|
39
39
|
* planned by that capability, as a pull request's plan is. A plan that
|
|
40
40
|
* returns no change-set part is a hole, since nothing says what it changes.
|
|
41
41
|
*
|
|
42
|
+
* A dependency the promote also deploys is read from its plan, not from the
|
|
43
|
+
* estate (#4168 step 4, the rule #3999 gave a pull request): a dependent is
|
|
44
|
+
* planned against the outputs its upstream's plan says it will expose, and a
|
|
45
|
+
* reference the plan can't say is listed in `unknownInputs`. Any other
|
|
46
|
+
* dependency is read as it stands. What each component was planned against is
|
|
47
|
+
* kept in `reads`, so the promote can hold it back when an upstream applies
|
|
48
|
+
* as another value (`./promote-rounds.ts`).
|
|
49
|
+
*
|
|
42
50
|
* Any other component is a `chant` member compared with what the
|
|
43
51
|
* environment runs now: its latest deploy record and the release that record
|
|
44
52
|
* names. The artifact digest, each template the release's target records for
|
|
@@ -91,6 +99,22 @@ export interface ReleaseChangeSetInput {
|
|
|
91
99
|
seededOutputs?: Record<string, Record<string, unknown>>;
|
|
92
100
|
/** The chant version to record in the document. */
|
|
93
101
|
chant?: string;
|
|
102
|
+
/**
|
|
103
|
+
* Plan only these components (a later round or wave, ./promote-rounds.ts).
|
|
104
|
+
* The others are not planned; a dependency among them is read from
|
|
105
|
+
* `seededOutputs`, which then holds what the run left. Default: every one.
|
|
106
|
+
*/
|
|
107
|
+
only?: ReadonlySet<string>;
|
|
108
|
+
}
|
|
109
|
+
/** A release's change set for an environment, and what each component was planned against. */
|
|
110
|
+
export interface ReleasePlan {
|
|
111
|
+
doc: ChangeSetDocument;
|
|
112
|
+
/** Per planned component, the outputs of its dependencies as its plan read them. */
|
|
113
|
+
reads: Record<string, Record<string, Record<string, unknown>>>;
|
|
114
|
+
/** Per dependency read from its plan, the names of the outputs the plan knows, sorted. Absent when none. */
|
|
115
|
+
plannedOutputs?: Record<string, string[]>;
|
|
116
|
+
/** Per component, the references to a promoted upstream that no value stands for until it applies, sorted. Absent when none. */
|
|
117
|
+
unknownInputs?: Record<string, string[]>;
|
|
94
118
|
}
|
|
95
119
|
/**
|
|
96
120
|
* A component no capability plans, as a `chant` member: what the release
|
|
@@ -112,6 +136,12 @@ export declare function chantComponentPart(input: {
|
|
|
112
136
|
* that fails to plan: it becomes a failed member.
|
|
113
137
|
*/
|
|
114
138
|
export declare function planReleaseChangeSet(input: ReleaseChangeSetInput): Promise<ChangeSetDocument>;
|
|
139
|
+
/**
|
|
140
|
+
* {@link planReleaseChangeSet}, with what each component read. Components are
|
|
141
|
+
* planned a wave at a time, so a dependency the promote deploys has planned
|
|
142
|
+
* before its dependents read its outputs.
|
|
143
|
+
*/
|
|
144
|
+
export declare function planReleaseSet(input: ReleaseChangeSetInput): Promise<ReleasePlan>;
|
|
115
145
|
/** The members a release gate refuses: provisional ones, which no approval of the document covers (`change-set.mdx`). */
|
|
116
146
|
export declare function provisionalMembers(doc: ChangeSetDocument): string[];
|
|
117
147
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"release-gate.d.ts","sourceRoot":"","sources":["../../src/components/release-gate.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"release-gate.d.ts","sourceRoot":"","sources":["../../src/components/release-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAkB,MAAM,cAAc,CAAC;AACvE,OAAO,EAAsF,KAAK,eAAe,EAAsD,MAAM,UAAU,CAAC;AAExL,OAAO,EAAY,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAE5D,OAAO,EAIL,KAAK,iBAAiB,EACtB,KAAK,aAAa,EACnB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAEzE,4GAA4G;AAC5G,OAAO,EAAE,eAAe,EAAE,CAAC;AAE3B,6DAA6D;AAC7D,eAAO,MAAM,4BAA4B,oBAAoB,CAAC;AAU9D,oGAAoG;AACpG,wBAAgB,WAAW,CAAC,UAAU,EAAE,SAAS,eAAe,EAAE,GAAG,MAAM,EAAE,CAU5E;AAED,sIAAsI;AACtI,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,mCAAmC;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,iGAAiG;IACjG,UAAU,EAAE,SAAS,eAAe,EAAE,CAAC;IACvC,yDAAyD;IACzD,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,kGAAkG;IAClG,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC5C;;;;OAIG;IACH,SAAS,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,yDAAyD;IACzD,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACxD,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,IAAI,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CAC5B;AAED,8FAA8F;AAC9F,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,iBAAiB,CAAC;IACvB,oFAAoF;IACpF,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAC/D,4GAA4G;IAC5G,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC1C,gIAAgI;IAChI,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CAC1C;AA0BD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACxC,SAAS,EAAE,eAAe,CAAC;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,aAAa,GAAG,SAAS,CAAC;IACnC,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,aAAa,CAsEhB;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAEnG;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,WAAW,CAAC,CA2FvF;AAED,yHAAyH;AACzH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,iBAAiB,GAAG,MAAM,EAAE,CAEnE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,mBAAmB,CAAC,EACtD,OAAO,EAAE;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GACtD,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAUtC;AAED,mGAAmG;AACnG,wBAAgB,eAAe,CAAC,KAAK,EAAE;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,iBAAiB,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,mBAAmB,CAkBtB"}
|
|
@@ -39,6 +39,14 @@
|
|
|
39
39
|
* planned by that capability, as a pull request's plan is. A plan that
|
|
40
40
|
* returns no change-set part is a hole, since nothing says what it changes.
|
|
41
41
|
*
|
|
42
|
+
* A dependency the promote also deploys is read from its plan, not from the
|
|
43
|
+
* estate (#4168 step 4, the rule #3999 gave a pull request): a dependent is
|
|
44
|
+
* planned against the outputs its upstream's plan says it will expose, and a
|
|
45
|
+
* reference the plan can't say is listed in `unknownInputs`. Any other
|
|
46
|
+
* dependency is read as it stands. What each component was planned against is
|
|
47
|
+
* kept in `reads`, so the promote can hold it back when an upstream applies
|
|
48
|
+
* as another value (`./promote-rounds.ts`).
|
|
49
|
+
*
|
|
42
50
|
* Any other component is a `chant` member compared with what the
|
|
43
51
|
* environment runs now: its latest deploy record and the release that record
|
|
44
52
|
* names. The artifact digest, each template the release's target records for
|
|
@@ -51,7 +59,7 @@
|
|
|
51
59
|
* component a promote, rollback or redeploy left failed or gated with nothing
|
|
52
60
|
* recorded since (#4035): its record names what ran before that run.
|
|
53
61
|
*/
|
|
54
|
-
import { deployContext, resolveComponentGraph, resolveStepInput } from "./driver.js";
|
|
62
|
+
import { deployContext, resolveComponentGraph, resolveStepInput, unresolvedWiringReferences } from "./driver.js";
|
|
55
63
|
import { componentPlanDigest } from "./gate-plan.js";
|
|
56
64
|
import { gateName, RELEASE_GATE_OP } from "../op/gate-name.js";
|
|
57
65
|
import { capabilitySteps, planContext, readComponentOutputs } from "./wave-plan.js";
|
|
@@ -187,66 +195,108 @@ export function chantComponentPart(input) {
|
|
|
187
195
|
* that fails to plan: it becomes a failed member.
|
|
188
196
|
*/
|
|
189
197
|
export async function planReleaseChangeSet(input) {
|
|
198
|
+
return (await planReleaseSet(input)).doc;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* {@link planReleaseChangeSet}, with what each component read. Components are
|
|
202
|
+
* planned a wave at a time, so a dependency the promote deploys has planned
|
|
203
|
+
* before its dependents read its outputs.
|
|
204
|
+
*/
|
|
205
|
+
export async function planReleaseSet(input) {
|
|
190
206
|
const { release, env, registry } = input;
|
|
191
207
|
const components = [...input.components];
|
|
192
208
|
const byName = new Map(components.map((c) => [c.name, c]));
|
|
193
209
|
const outputs = { ...(input.seededOutputs ?? {}) };
|
|
194
210
|
const read = new Set(Object.keys(outputs));
|
|
195
211
|
const parts = [];
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
212
|
+
const plannedBy = new Map();
|
|
213
|
+
const planned = {};
|
|
214
|
+
const unknownInputs = {};
|
|
215
|
+
const reads = {};
|
|
216
|
+
const inPlan = (name) => input.only === undefined || input.only.has(name);
|
|
217
|
+
const isPlannable = (c) => capabilitySteps(c.deploy).some((s) => registry.has(s.kind) && registry.resolve(s.kind).plan !== undefined);
|
|
218
|
+
const { waves } = resolveComponentGraph(components.map((c) => ({ ...c, dependsOn: (c.dependsOn ?? []).filter((d) => byName.has(d)) })));
|
|
219
|
+
for (const wave of waves) {
|
|
220
|
+
const planning = wave.filter(inPlan);
|
|
221
|
+
for (const name of planning) {
|
|
222
|
+
const component = byName.get(name);
|
|
223
|
+
const digest = input.digests.get(name) ?? "";
|
|
224
|
+
const plannable = capabilitySteps(component.deploy).filter((s) => registry.has(s.kind) && registry.resolve(s.kind).plan !== undefined);
|
|
225
|
+
if (plannable.length === 0) {
|
|
226
|
+
parts.push(chantComponentPart({ component, release, env, digest, current: input.current.get(name), unsettled: input.unsettled?.get(name) }));
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
const ctx = deployContext({ env }, name);
|
|
230
|
+
try {
|
|
231
|
+
for (const dep of component.dependsOn ?? []) {
|
|
232
|
+
if (read.has(dep) || !byName.has(dep))
|
|
233
|
+
continue;
|
|
234
|
+
read.add(dep);
|
|
235
|
+
// A dependency this promote plans exposes what its plan says (#3999).
|
|
236
|
+
if (plannedBy.has(dep) && isPlannable(byName.get(dep))) {
|
|
237
|
+
const known = plannedBy.get(dep)?.known ?? {};
|
|
238
|
+
outputs[dep] = { ...known };
|
|
239
|
+
planned[dep] = Object.keys(known).sort();
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
const found = await readComponentOutputs(byName.get(dep), registry, deployContext({ env }, dep), outputs);
|
|
243
|
+
if (found)
|
|
244
|
+
outputs[dep] = found;
|
|
245
|
+
}
|
|
246
|
+
reads[name] = Object.fromEntries((component.dependsOn ?? []).filter((d) => d in outputs).map((d) => [d, structuredClone(outputs[d])]));
|
|
247
|
+
const unknown = new Set();
|
|
248
|
+
let exposes;
|
|
249
|
+
for (const step of plannable) {
|
|
250
|
+
const { kind, ...rest } = step;
|
|
251
|
+
for (const r of unresolvedWiringReferences(rest, outputs))
|
|
252
|
+
if (plannedBy.has(r.source) && !r.publish)
|
|
253
|
+
unknown.add(r.ref);
|
|
254
|
+
const result = await registry.resolve(kind).plan(planContext(ctx, rest, outputs), resolveStepInput(rest, {}, outputs));
|
|
255
|
+
if (result.outputs)
|
|
256
|
+
exposes = { known: { ...(exposes?.known ?? {}), ...result.outputs.known }, unknown: [...new Set([...(exposes?.unknown ?? []), ...result.outputs.unknown])].sort() };
|
|
257
|
+
parts.push(result.changeSet ?? {
|
|
258
|
+
member: {
|
|
259
|
+
member: result.member,
|
|
260
|
+
lexicon: "chant",
|
|
261
|
+
planner: "chant",
|
|
262
|
+
status: "planned",
|
|
263
|
+
planDigest: result.planDigest,
|
|
264
|
+
holes: [{ address: result.member, reason: `its ${kind} plan describes no changes, so what it changes is unknown` }],
|
|
265
|
+
},
|
|
266
|
+
entries: [],
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
plannedBy.set(name, exposes);
|
|
270
|
+
if (unknown.size > 0)
|
|
271
|
+
unknownInputs[name] = [...unknown].sort();
|
|
214
272
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
parts.push(planned.changeSet ?? {
|
|
273
|
+
catch (err) {
|
|
274
|
+
plannedBy.set(name, undefined);
|
|
275
|
+
parts.push({
|
|
219
276
|
member: {
|
|
220
|
-
member:
|
|
277
|
+
member: name,
|
|
221
278
|
lexicon: "chant",
|
|
222
279
|
planner: "chant",
|
|
223
|
-
status: "
|
|
224
|
-
|
|
225
|
-
|
|
280
|
+
status: "failed",
|
|
281
|
+
error: err instanceof Error ? err.message : String(err),
|
|
282
|
+
planDigest: null,
|
|
283
|
+
holes: [],
|
|
226
284
|
},
|
|
227
285
|
entries: [],
|
|
228
286
|
});
|
|
229
287
|
}
|
|
230
288
|
}
|
|
231
|
-
catch (err) {
|
|
232
|
-
parts.push({
|
|
233
|
-
member: {
|
|
234
|
-
member: name,
|
|
235
|
-
lexicon: "chant",
|
|
236
|
-
planner: "chant",
|
|
237
|
-
status: "failed",
|
|
238
|
-
error: err instanceof Error ? err.message : String(err),
|
|
239
|
-
planDigest: null,
|
|
240
|
-
holes: [],
|
|
241
|
-
},
|
|
242
|
-
entries: [],
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
289
|
}
|
|
246
|
-
|
|
290
|
+
const doc = composeChangeSet(parts, {
|
|
247
291
|
...(input.chant !== undefined ? { chant: input.chant } : {}),
|
|
248
292
|
planned: { releaseDigest: release.releaseDigest, environment: env },
|
|
249
293
|
});
|
|
294
|
+
return {
|
|
295
|
+
doc,
|
|
296
|
+
reads,
|
|
297
|
+
...(Object.keys(planned).length > 0 ? { plannedOutputs: planned } : {}),
|
|
298
|
+
...(Object.keys(unknownInputs).length > 0 ? { unknownInputs } : {}),
|
|
299
|
+
};
|
|
250
300
|
}
|
|
251
301
|
/** The members a release gate refuses: provisional ones, which no approval of the document covers (`change-set.mdx`). */
|
|
252
302
|
export function provisionalMembers(doc) {
|
package/dist/graph-declared.d.ts
CHANGED
|
@@ -10,6 +10,10 @@ import { type GraphIR } from "./graph-ir.js";
|
|
|
10
10
|
* that live side, build each stack's `src` in isolation and qualify its node ids
|
|
11
11
|
* and edge endpoints the same way. Stacks are merged into one graph.
|
|
12
12
|
*
|
|
13
|
+
* Each stack's exports and imports come along with their `node` qualified the
|
|
14
|
+
* same way (#4202), so a workspace viewer can still match an import in another
|
|
15
|
+
* member to an export here once an environment is picked.
|
|
16
|
+
*
|
|
13
17
|
* A stack without `src` contributes nothing here — it has no declared source to
|
|
14
18
|
* scope to (its live nodes still show as foreign in the overlay).
|
|
15
19
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-declared.d.ts","sourceRoot":"","sources":["../src/graph-declared.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,OAAO,EAAe,MAAM,YAAY,CAAC;AAErE
|
|
1
|
+
{"version":3,"file":"graph-declared.d.ts","sourceRoot":"","sources":["../src/graph-declared.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,OAAO,EAAe,MAAM,YAAY,CAAC;AAErE;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,EAC7C,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,OAAO,CAAC,CA2BlB"}
|
package/dist/graph-declared.js
CHANGED
|
@@ -12,6 +12,10 @@ import { buildGraphIr } from "./graph-ir.js";
|
|
|
12
12
|
* that live side, build each stack's `src` in isolation and qualify its node ids
|
|
13
13
|
* and edge endpoints the same way. Stacks are merged into one graph.
|
|
14
14
|
*
|
|
15
|
+
* Each stack's exports and imports come along with their `node` qualified the
|
|
16
|
+
* same way (#4202), so a workspace viewer can still match an import in another
|
|
17
|
+
* member to an export here once an environment is picked.
|
|
18
|
+
*
|
|
15
19
|
* A stack without `src` contributes nothing here — it has no declared source to
|
|
16
20
|
* scope to (its live nodes still show as foreign in the overlay).
|
|
17
21
|
*/
|
|
@@ -19,6 +23,8 @@ export async function buildDeclaredPerStack(stacks, projectPath) {
|
|
|
19
23
|
var _a;
|
|
20
24
|
const nodes = [];
|
|
21
25
|
const edges = [];
|
|
26
|
+
const exports = [];
|
|
27
|
+
const imports = [];
|
|
22
28
|
// Which nodes belong to which stack (#1433). This is not derived or guessed:
|
|
23
29
|
// the stack is declared in config and every node here is being renamed with
|
|
24
30
|
// its name on the line below. The membership was always known and thrown
|
|
@@ -37,9 +43,13 @@ export async function buildDeclaredPerStack(stacks, projectPath) {
|
|
|
37
43
|
}
|
|
38
44
|
for (const e of g.edges)
|
|
39
45
|
edges.push({ ...e, from: q(e.from), to: q(e.to) });
|
|
46
|
+
for (const e of g.exports ?? [])
|
|
47
|
+
exports.push({ ...e, ...(e.node ? { node: q(e.node) } : {}) });
|
|
48
|
+
for (const i of g.imports ?? [])
|
|
49
|
+
imports.push({ ...i, node: q(i.node) });
|
|
40
50
|
}
|
|
41
51
|
for (const ids of Object.values(byStack))
|
|
42
52
|
ids.sort();
|
|
43
53
|
const groups = Object.keys(byStack).length ? { byStack } : {};
|
|
44
|
-
return { nodes, edges, groups };
|
|
54
|
+
return { nodes, edges, groups, ...(exports.length ? { exports } : {}), ...(imports.length ? { imports } : {}) };
|
|
45
55
|
}
|