@intentius/chant 0.124.29 → 0.124.31
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/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.js +4 -1
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.js +27 -5
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +64 -0
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +99 -0
- package/dist/cli/registry.d.ts +35 -1
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/components/pr-pipeline.d.ts +24 -0
- package/dist/components/pr-pipeline.d.ts.map +1 -1
- package/dist/components/pr-pipeline.js +16 -0
- package/dist/components/pr-run.d.ts +20 -2
- package/dist/components/pr-run.d.ts.map +1 -1
- package/dist/components/pr-run.js +26 -13
- package/dist/components/wave-plan.d.ts +11 -0
- package/dist/components/wave-plan.d.ts.map +1 -1
- package/dist/components/wave-plan.js +26 -0
- package/dist/lifecycle/release-ledger.d.ts +8 -3
- package/dist/lifecycle/release-ledger.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.js +2 -0
- package/dist/lifecycle/run-ledger.d.ts +1 -1
- package/dist/lifecycle/run-ledger.d.ts.map +1 -1
- package/dist/lifecycle/run-ledger.js +9 -0
- package/dist/op/activities/activity-contracts.d.ts +1 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.js +1 -0
- package/dist/op/activities/shell.d.ts +6 -0
- package/dist/op/activities/shell.d.ts.map +1 -1
- package/dist/op/activities/shell.js +1 -1
- package/dist/op/local-executor.d.ts +35 -1
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-executor.js +31 -6
- package/dist/op/redact.d.ts +34 -0
- package/dist/op/redact.d.ts.map +1 -0
- package/dist/op/redact.js +94 -0
- package/dist/op/runtime.d.ts +27 -0
- package/dist/op/runtime.d.ts.map +1 -1
- package/dist/op/runtime.js +20 -0
- package/dist/op/step-console.d.ts +2 -0
- package/dist/op/step-console.d.ts.map +1 -1
- package/dist/op/step-console.js +5 -2
- package/dist/pr-forge.d.ts +40 -0
- package/dist/pr-forge.d.ts.map +1 -1
- package/dist/pr-forge.js +56 -6
- package/dist/pr-loop.d.ts +10 -0
- package/dist/pr-loop.d.ts.map +1 -1
- package/dist/pr-loop.js +7 -0
- package/dist/pr-report-url.d.ts +8 -0
- package/dist/pr-report-url.d.ts.map +1 -1
- package/dist/pr-report-url.js +47 -1
- package/dist/run-event-stream.d.ts +2 -2
- package/dist/run-event-stream.d.ts.map +1 -1
- package/dist/run-event-stream.js +2 -2
- package/dist/run-event.d.ts +15 -0
- package/dist/run-event.d.ts.map +1 -1
- package/dist/run-event.js +2 -0
- package/dist/wave-runtime.mjs +171 -171
- package/dist/workspace/ci-green-forge.d.ts +2 -0
- package/dist/workspace/ci-green-forge.d.ts.map +1 -1
- package/dist/workspace/ci-green.d.ts +2 -0
- package/dist/workspace/ci-green.d.ts.map +1 -1
- package/dist/workspace/ci-green.js +4 -2
- package/dist/workspace/ci-run-index.d.ts +96 -0
- package/dist/workspace/ci-run-index.d.ts.map +1 -0
- package/dist/workspace/ci-run-index.js +162 -0
- package/dist/workspace/ci-run-index.schema.json +50 -0
- package/dist/workspace/ci-run.d.ts +299 -0
- package/dist/workspace/ci-run.d.ts.map +1 -0
- package/dist/workspace/ci-run.js +691 -0
- package/dist/workspace/ci-runner-key.d.ts +120 -0
- package/dist/workspace/ci-runner-key.d.ts.map +1 -0
- package/dist/workspace/ci-runner-key.js +217 -0
- package/dist/workspace/ci-secrets.d.ts +65 -0
- package/dist/workspace/ci-secrets.d.ts.map +1 -0
- package/dist/workspace/ci-secrets.js +211 -0
- package/dist/workspace/ci-serve.d.ts +227 -0
- package/dist/workspace/ci-serve.d.ts.map +1 -0
- package/dist/workspace/ci-serve.js +468 -0
- package/dist/workspace/pr-report.schema.json +5 -0
- package/dist/workspace/reason-codes.d.ts +6 -2
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +7 -2
- package/dist/workspace/run-event.schema.json +14 -3
- package/dist/workspace/status-stewards.d.ts +2 -0
- package/dist/workspace/status-stewards.d.ts.map +1 -1
- package/dist/workspace/status-stewards.js +1 -0
- package/dist/workspace/status.schema.json +12 -1
- package/dist/workspace/trust/trust-write.d.ts +109 -0
- package/dist/workspace/trust/trust-write.d.ts.map +1 -0
- package/dist/workspace/trust/trust-write.js +320 -0
- package/dist/workspace/trust-write.schema.json +265 -0
- package/package.json +1 -1
- package/src/cli/handlers/build.test.ts +15 -0
- package/src/cli/handlers/build.ts +4 -1
- package/src/cli/handlers/fan-out.test.ts +82 -0
- package/src/cli/handlers/fan-out.ts +34 -5
- package/src/cli/handlers/pr.ts +5 -0
- package/src/cli/main.test.ts +1 -0
- package/src/cli/main.ts +64 -0
- package/src/cli/parse-args.ts +71 -0
- package/src/cli/registry.ts +35 -1
- package/src/components/pr-pipeline.ts +32 -0
- package/src/components/pr-run.test.ts +127 -0
- package/src/components/pr-run.ts +32 -12
- package/src/components/wave-plan.ts +30 -0
- package/src/lifecycle/release-ledger.ts +9 -3
- package/src/lifecycle/run-ledger.ts +7 -1
- package/src/op/activities/activity-contracts.ts +1 -0
- package/src/op/activities/shell.ts +7 -1
- package/src/op/local-executor.ts +58 -7
- package/src/op/redact.ts +93 -0
- package/src/op/runtime.ts +42 -0
- package/src/op/step-console.ts +7 -2
- package/src/pr-forge.test.ts +54 -0
- package/src/pr-forge.ts +92 -6
- package/src/pr-loop.test.ts +13 -1
- package/src/pr-loop.ts +16 -0
- package/src/pr-report-url.ts +53 -1
- package/src/run-event-stream.ts +4 -3
- package/src/run-event.ts +17 -0
- package/src/workspace/ci-green-forge.ts +2 -0
- package/src/workspace/ci-green.ts +6 -2
- package/src/workspace/ci-run-index.schema.json +50 -0
- package/src/workspace/ci-run-index.test.ts +353 -0
- package/src/workspace/ci-run-index.ts +192 -0
- package/src/workspace/ci-run.test.ts +716 -0
- package/src/workspace/ci-run.ts +833 -0
- package/src/workspace/ci-runner-key.test.ts +166 -0
- package/src/workspace/ci-runner-key.ts +250 -0
- package/src/workspace/ci-secrets.test.ts +106 -0
- package/src/workspace/ci-secrets.ts +213 -0
- package/src/workspace/ci-serve.test.ts +367 -0
- package/src/workspace/ci-serve.ts +574 -0
- package/src/workspace/pr-report.schema.json +5 -0
- package/src/workspace/reason-codes.test.ts +2 -0
- package/src/workspace/reason-codes.ts +7 -2
- package/src/workspace/run-event.schema.json +14 -3
- package/src/workspace/status-stewards.ts +3 -0
- package/src/workspace/status.schema.json +12 -1
- package/src/workspace/trust/trust-write.test.ts +179 -0
- package/src/workspace/trust/trust-write.ts +377 -0
- package/src/workspace/trust-write.schema.json +265 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://intentius.io/chant/schemas/workspace/trust-write/v1/trust-write.schema.json",
|
|
4
|
+
"title": "chant workspace trust runner add|remove output",
|
|
5
|
+
"description": "What `chant workspace trust runner add <principal> --key <key>` and `trust runner remove <principal>` print (#3510): the runner entry before and after the write, the digest of .chant/trust.json after it, or the reason it wrote nothing. Each write edits .chant/trust.json in the working tree in place and never commits. Readers ignore fields they do not know; a field is only ever added within a version. The error codes are a closed list, each in the one closed list of `reason-codes.ts`. Contract version 1 of this document is written by chant 0.124.31 and newer.",
|
|
6
|
+
"oneOf": [
|
|
7
|
+
{
|
|
8
|
+
"$ref": "#/$defs/result"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"$ref": "#/$defs/failure"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"$defs": {
|
|
15
|
+
"runner": {
|
|
16
|
+
"type": "object",
|
|
17
|
+
"required": [
|
|
18
|
+
"principal",
|
|
19
|
+
"class",
|
|
20
|
+
"key"
|
|
21
|
+
],
|
|
22
|
+
"additionalProperties": false,
|
|
23
|
+
"properties": {
|
|
24
|
+
"principal": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"minLength": 1
|
|
27
|
+
},
|
|
28
|
+
"class": {
|
|
29
|
+
"enum": [
|
|
30
|
+
"runner",
|
|
31
|
+
"service"
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"key": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"pattern": "^ssh-ed25519 [A-Za-z0-9+/]+={0,2}$",
|
|
37
|
+
"description": "The runner's public key, as an ssh-ed25519 line with no comment."
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"digest": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"pattern": "^[0-9a-f]{64}$",
|
|
44
|
+
"description": "A SHA-256 of the file's bytes, in lowercase hex. A file that is not there has the digest of empty text."
|
|
45
|
+
},
|
|
46
|
+
"result": {
|
|
47
|
+
"description": "The entry was written, or would be with --dry-run, or the file already held it (changed false). Exit code 0.",
|
|
48
|
+
"type": "object",
|
|
49
|
+
"required": [
|
|
50
|
+
"$schema",
|
|
51
|
+
"contract",
|
|
52
|
+
"chant",
|
|
53
|
+
"action",
|
|
54
|
+
"name",
|
|
55
|
+
"path",
|
|
56
|
+
"digest",
|
|
57
|
+
"previousDigest",
|
|
58
|
+
"paths",
|
|
59
|
+
"changed",
|
|
60
|
+
"dryRun",
|
|
61
|
+
"previous",
|
|
62
|
+
"entry",
|
|
63
|
+
"runnerRole"
|
|
64
|
+
],
|
|
65
|
+
"properties": {
|
|
66
|
+
"$schema": {
|
|
67
|
+
"const": "https://intentius.io/chant/schemas/workspace/trust-write/v1/trust-write.schema.json"
|
|
68
|
+
},
|
|
69
|
+
"contract": {
|
|
70
|
+
"const": 1
|
|
71
|
+
},
|
|
72
|
+
"chant": {
|
|
73
|
+
"type": "string",
|
|
74
|
+
"description": "The chant that wrote it."
|
|
75
|
+
},
|
|
76
|
+
"action": {
|
|
77
|
+
"enum": [
|
|
78
|
+
"runner add",
|
|
79
|
+
"runner remove"
|
|
80
|
+
]
|
|
81
|
+
},
|
|
82
|
+
"name": {
|
|
83
|
+
"type": "string",
|
|
84
|
+
"description": "The runner's principal."
|
|
85
|
+
},
|
|
86
|
+
"path": {
|
|
87
|
+
"const": ".chant/trust.json",
|
|
88
|
+
"description": "The trust config, relative to the repository root."
|
|
89
|
+
},
|
|
90
|
+
"digest": {
|
|
91
|
+
"$ref": "#/$defs/digest",
|
|
92
|
+
"description": "The file's digest after the write, or as it would be with --dry-run: what the caller passes as --expect to its next trust write."
|
|
93
|
+
},
|
|
94
|
+
"previousDigest": {
|
|
95
|
+
"$ref": "#/$defs/digest",
|
|
96
|
+
"description": "The file's digest before the write."
|
|
97
|
+
},
|
|
98
|
+
"paths": {
|
|
99
|
+
"type": "array",
|
|
100
|
+
"items": {
|
|
101
|
+
"type": "string"
|
|
102
|
+
},
|
|
103
|
+
"description": "The trust config when the write changed it or would with --dry-run; empty when it already held what was given."
|
|
104
|
+
},
|
|
105
|
+
"changed": {
|
|
106
|
+
"type": "boolean",
|
|
107
|
+
"description": "Whether the file changed: paths is not empty."
|
|
108
|
+
},
|
|
109
|
+
"dryRun": {
|
|
110
|
+
"type": "boolean",
|
|
111
|
+
"description": "True when --dry-run was given: nothing was written, and the document says what would have been."
|
|
112
|
+
},
|
|
113
|
+
"previous": {
|
|
114
|
+
"oneOf": [
|
|
115
|
+
{
|
|
116
|
+
"type": "null"
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"$ref": "#/$defs/runner"
|
|
120
|
+
}
|
|
121
|
+
],
|
|
122
|
+
"description": "The principal's runner entry before the write, or null when there was none."
|
|
123
|
+
},
|
|
124
|
+
"entry": {
|
|
125
|
+
"oneOf": [
|
|
126
|
+
{
|
|
127
|
+
"type": "null"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"$ref": "#/$defs/runner"
|
|
131
|
+
}
|
|
132
|
+
],
|
|
133
|
+
"description": "The principal's runner entry after the write, or null after runner remove."
|
|
134
|
+
},
|
|
135
|
+
"runnerRole": {
|
|
136
|
+
"type": "boolean",
|
|
137
|
+
"description": "Whether the principal holds the runner role after the write. runner add grants it for class runner; runner remove takes it away."
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
"failure": {
|
|
142
|
+
"type": "object",
|
|
143
|
+
"description": "Nothing was written. Exit code 1.",
|
|
144
|
+
"required": [
|
|
145
|
+
"$schema",
|
|
146
|
+
"contract",
|
|
147
|
+
"chant",
|
|
148
|
+
"action",
|
|
149
|
+
"name",
|
|
150
|
+
"error"
|
|
151
|
+
],
|
|
152
|
+
"properties": {
|
|
153
|
+
"$schema": {
|
|
154
|
+
"const": "https://intentius.io/chant/schemas/workspace/trust-write/v1/trust-write.schema.json"
|
|
155
|
+
},
|
|
156
|
+
"contract": {
|
|
157
|
+
"const": 1
|
|
158
|
+
},
|
|
159
|
+
"chant": {
|
|
160
|
+
"type": "string"
|
|
161
|
+
},
|
|
162
|
+
"action": {
|
|
163
|
+
"enum": [
|
|
164
|
+
"runner add",
|
|
165
|
+
"runner remove"
|
|
166
|
+
]
|
|
167
|
+
},
|
|
168
|
+
"name": {
|
|
169
|
+
"type": [
|
|
170
|
+
"string",
|
|
171
|
+
"null"
|
|
172
|
+
]
|
|
173
|
+
},
|
|
174
|
+
"error": {
|
|
175
|
+
"type": "object",
|
|
176
|
+
"required": [
|
|
177
|
+
"code",
|
|
178
|
+
"message"
|
|
179
|
+
],
|
|
180
|
+
"properties": {
|
|
181
|
+
"code": {
|
|
182
|
+
"enum": [
|
|
183
|
+
"not-a-git-repository",
|
|
184
|
+
"write-usage-invalid",
|
|
185
|
+
"trust-config-invalid",
|
|
186
|
+
"trust-conflict",
|
|
187
|
+
"runner-key-invalid",
|
|
188
|
+
"runner-key-is-signer",
|
|
189
|
+
"runner-exists",
|
|
190
|
+
"runner-unknown",
|
|
191
|
+
"write-scope-member",
|
|
192
|
+
"write-scope-protected",
|
|
193
|
+
"write-scope-class-unknown",
|
|
194
|
+
"agent-unknown",
|
|
195
|
+
"principal-unidentified",
|
|
196
|
+
"write-lock-timeout",
|
|
197
|
+
"write-lock-not-held"
|
|
198
|
+
]
|
|
199
|
+
},
|
|
200
|
+
"message": {
|
|
201
|
+
"type": "string"
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
"conflict": {
|
|
206
|
+
"type": "object",
|
|
207
|
+
"description": "With trust-conflict: the digest --expect named, the one the file has now, and its last chant write when the write journal knows it.",
|
|
208
|
+
"required": [
|
|
209
|
+
"path",
|
|
210
|
+
"expected",
|
|
211
|
+
"digest",
|
|
212
|
+
"lastWrite"
|
|
213
|
+
],
|
|
214
|
+
"properties": {
|
|
215
|
+
"path": {
|
|
216
|
+
"type": "string"
|
|
217
|
+
},
|
|
218
|
+
"expected": {
|
|
219
|
+
"$ref": "#/$defs/digest"
|
|
220
|
+
},
|
|
221
|
+
"digest": {
|
|
222
|
+
"$ref": "#/$defs/digest"
|
|
223
|
+
},
|
|
224
|
+
"lastWrite": {
|
|
225
|
+
"oneOf": [
|
|
226
|
+
{
|
|
227
|
+
"type": "null"
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
"type": "object",
|
|
231
|
+
"required": [
|
|
232
|
+
"verb",
|
|
233
|
+
"by",
|
|
234
|
+
"agent",
|
|
235
|
+
"at"
|
|
236
|
+
],
|
|
237
|
+
"properties": {
|
|
238
|
+
"verb": {
|
|
239
|
+
"type": "string"
|
|
240
|
+
},
|
|
241
|
+
"by": {
|
|
242
|
+
"type": [
|
|
243
|
+
"string",
|
|
244
|
+
"null"
|
|
245
|
+
]
|
|
246
|
+
},
|
|
247
|
+
"agent": {
|
|
248
|
+
"type": [
|
|
249
|
+
"string",
|
|
250
|
+
"null"
|
|
251
|
+
]
|
|
252
|
+
},
|
|
253
|
+
"at": {
|
|
254
|
+
"type": "string"
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
]
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
package/package.json
CHANGED
|
@@ -148,6 +148,21 @@ describe("runBuild --components --generate (chant #1108 build-time parameters)",
|
|
|
148
148
|
vi.restoreAllMocks();
|
|
149
149
|
});
|
|
150
150
|
|
|
151
|
+
test("--pr-build and --pr-build-command become the prLoop build options (#4088)", async () => {
|
|
152
|
+
generateComponentsPipelineMock.mockResolvedValue({ success: true, yaml: "jobs: {}", stages: [], jobs: [] });
|
|
153
|
+
vi.spyOn(console, "log").mockImplementation(() => {});
|
|
154
|
+
|
|
155
|
+
const exit = await runBuild({
|
|
156
|
+
args: makeArgs({ generate: "github", prLoop: true, prBuild: true, prBuildCommand: "npm run build" }),
|
|
157
|
+
plugins: [],
|
|
158
|
+
serializers: [],
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
expect(exit).toBe(0);
|
|
162
|
+
expect(generateComponentsPipelineMock).toHaveBeenCalledWith(".", "github", { env: undefined, prLoop: { build: true, buildCommand: "npm run build" } }, undefined, []);
|
|
163
|
+
vi.restoreAllMocks();
|
|
164
|
+
});
|
|
165
|
+
|
|
151
166
|
test("chant.config.ts's declared buildParams resolve, log, and are forwarded to generateComponentsPipeline", async () => {
|
|
152
167
|
loadChantConfigUpwardMock.mockResolvedValue({
|
|
153
168
|
config: { buildParams: { tier: { type: "string", default: "light" } } },
|
|
@@ -13,7 +13,8 @@ import type { PrLoopPipelineOptions } from "../../components/pr-pipeline";
|
|
|
13
13
|
/**
|
|
14
14
|
* The `prLoop` generator option from `--pr-loop`'s flags (#3183): the gate
|
|
15
15
|
* and the branch, and with `--events-json` and `--report-url` (#4007) the
|
|
16
|
-
* flags the generated jobs pass to pr-plan and pr-apply.
|
|
16
|
+
* flags the generated jobs pass to pr-plan and pr-apply. `--pr-build` and
|
|
17
|
+
* `--pr-build-command` (#4088) add the build and lint steps before both.
|
|
17
18
|
*/
|
|
18
19
|
function prLoopOption(args: CommandContext["args"]): PrLoopPipelineOptions {
|
|
19
20
|
return {
|
|
@@ -21,6 +22,8 @@ function prLoopOption(args: CommandContext["args"]): PrLoopPipelineOptions {
|
|
|
21
22
|
...(args.branch ? { branch: args.branch } : {}),
|
|
22
23
|
...(args.eventsJson ? { events: true } : {}),
|
|
23
24
|
...(args.reportUrl ? { reportUrl: args.reportUrl } : {}),
|
|
25
|
+
...(args.prBuild ? { build: true } : {}),
|
|
26
|
+
...(args.prBuildCommand ? { buildCommand: args.prBuildCommand } : {}),
|
|
24
27
|
};
|
|
25
28
|
}
|
|
26
29
|
|
|
@@ -84,6 +84,13 @@ let inputs: Record<string, Record<string, unknown>> = {};
|
|
|
84
84
|
let stepOutputs: Record<string, Record<string, unknown>> = {};
|
|
85
85
|
/** Called as a component's step starts, before it applies. */
|
|
86
86
|
let onStart: (component: string) => void = () => {};
|
|
87
|
+
/**
|
|
88
|
+
* What each stack exposes in the deployed estate, read through the step's
|
|
89
|
+
* `outputs` as cfn-deploy's `describeStack` does (#4104). `undefined` leaves
|
|
90
|
+
* the capability with no `outputs`; an `Error` makes the read fail.
|
|
91
|
+
*/
|
|
92
|
+
let liveOutputs: Record<string, Record<string, unknown> | Error> | undefined;
|
|
93
|
+
let outputsRead: string[] = [];
|
|
87
94
|
|
|
88
95
|
function makeArgs(overrides: Partial<ParsedArgs>): ParsedArgs {
|
|
89
96
|
return {
|
|
@@ -114,6 +121,8 @@ beforeEach(() => {
|
|
|
114
121
|
inputs = {};
|
|
115
122
|
stepOutputs = {};
|
|
116
123
|
onStart = () => {};
|
|
124
|
+
liveOutputs = undefined;
|
|
125
|
+
outputsRead = [];
|
|
117
126
|
gateLedger = memoryGateLedgerPort();
|
|
118
127
|
stderrLines = [];
|
|
119
128
|
stdoutLines = [];
|
|
@@ -149,6 +158,16 @@ beforeEach(() => {
|
|
|
149
158
|
if (failing.includes(runCtx.component)) throw new Error(`${runCtx.component} failed`);
|
|
150
159
|
return { ok: true, ...(stepOutputs[runCtx.component] ? { outputs: stepOutputs[runCtx.component] } : {}) };
|
|
151
160
|
},
|
|
161
|
+
...(liveOutputs
|
|
162
|
+
? {
|
|
163
|
+
async outputs(readCtx: { component: string }) {
|
|
164
|
+
outputsRead.push(readCtx.component);
|
|
165
|
+
const live = liveOutputs![readCtx.component];
|
|
166
|
+
if (live instanceof Error) throw live;
|
|
167
|
+
return { ...(live ?? {}) };
|
|
168
|
+
},
|
|
169
|
+
}
|
|
170
|
+
: {}),
|
|
152
171
|
} as never);
|
|
153
172
|
return Promise.resolve(registry);
|
|
154
173
|
});
|
|
@@ -849,3 +868,66 @@ describe("progress carries only for an unchanged input (#4081)", () => {
|
|
|
849
868
|
expect(stderr()).toContain("does not record what each component applied, so none of its progress carries");
|
|
850
869
|
});
|
|
851
870
|
});
|
|
871
|
+
|
|
872
|
+
describe("a seed's outputs, read from the deployed estate (#4104)", () => {
|
|
873
|
+
/** cluster-a reads net's TopicArn, and the change touches cluster-a alone. */
|
|
874
|
+
function wireClusterA(): void {
|
|
875
|
+
const wired: Component = {
|
|
876
|
+
name: "cluster-a",
|
|
877
|
+
dependsOn: ["net"],
|
|
878
|
+
deploy: [
|
|
879
|
+
{
|
|
880
|
+
phase: "Apply",
|
|
881
|
+
steps: [{ kind: "cfn-deploy", stack: "cluster-a-stack", inputs: { topic: { stackOutput: { stack: "net", name: "TopicArn" } } } }],
|
|
882
|
+
},
|
|
883
|
+
],
|
|
884
|
+
};
|
|
885
|
+
const estate = ESTATE.map((c) => (c.name === "cluster-a" ? wired : c));
|
|
886
|
+
discoverComponentsMock.mockResolvedValue({
|
|
887
|
+
components: new Map(estate.map((c) => [c.name, { component: c, exportName: c.name, filePath: `${c.name}.component.ts` }])),
|
|
888
|
+
sourceFiles: [],
|
|
889
|
+
errors: [],
|
|
890
|
+
});
|
|
891
|
+
affectedStacksMock.mockResolvedValue({ changed: ["cluster-a-stack"], dependents: [], indeterminate: [] });
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
test("a cluster-a-only change runs cluster-a with net's deployed outputs, and says it read them", async () => {
|
|
895
|
+
wireClusterA();
|
|
896
|
+
liveOutputs = { net: { TopicArn: "arn:aws:sns:us-east-1:000000000000:net", QueueUrl: "http://q" } };
|
|
897
|
+
expect(await runComponentsFanOut(ctx({ base: "main", env: "test" }))).toBe(0);
|
|
898
|
+
expect(ran).not.toContain("net");
|
|
899
|
+
expect(outputsRead).toEqual(["net"]);
|
|
900
|
+
expect(inputs["cluster-a"]).toEqual(expect.objectContaining({ inputs: { topic: "arn:aws:sns:us-east-1:000000000000:net" } }));
|
|
901
|
+
expect(stderr()).toContain("net: outputs read from the deployed estate (QueueUrl, TopicArn)");
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
test("--seed-outputs wins: a seed it gives is not read", async () => {
|
|
905
|
+
wireClusterA();
|
|
906
|
+
liveOutputs = { net: { TopicArn: "arn:deployed" } };
|
|
907
|
+
const dir = await mkdtemp(join(tmpdir(), "fan-out-"));
|
|
908
|
+
try {
|
|
909
|
+
const seed = join(dir, "seed.json");
|
|
910
|
+
await writeFile(seed, JSON.stringify({ net: { TopicArn: "arn:seeded" } }));
|
|
911
|
+
expect(await runComponentsFanOut(ctx({ base: "main", env: "test", seedOutputs: [seed] }))).toBe(0);
|
|
912
|
+
expect(outputsRead).toEqual([]);
|
|
913
|
+
expect(inputs["cluster-a"]).toEqual(expect.objectContaining({ inputs: { topic: "arn:seeded" } }));
|
|
914
|
+
} finally {
|
|
915
|
+
await rm(dir, { recursive: true, force: true });
|
|
916
|
+
}
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
test("a read that fails stops the fan-out before anything runs, naming the seed", async () => {
|
|
920
|
+
wireClusterA();
|
|
921
|
+
liveOutputs = { net: new Error("ExpiredToken") };
|
|
922
|
+
expect(await runComponentsFanOut(ctx({ base: "main", env: "test" }))).toBe(1);
|
|
923
|
+
expect(ran).toEqual([]);
|
|
924
|
+
expect(stderr()).toContain("could not read the deployed outputs of net: ExpiredToken");
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
test("--dry-run reads nothing", async () => {
|
|
928
|
+
wireClusterA();
|
|
929
|
+
liveOutputs = { net: { TopicArn: "arn:deployed" } };
|
|
930
|
+
expect(await runComponentsFanOut(ctx({ base: "main", env: "test", dryRun: true }))).toBe(0);
|
|
931
|
+
expect(outputsRead).toEqual([]);
|
|
932
|
+
});
|
|
933
|
+
});
|
|
@@ -67,6 +67,8 @@ import { summaryLedgerPrefix, writeGatedRunSummary } from "../../op/gate-summary
|
|
|
67
67
|
import { approveCommand, gateIsSealed } from "../../op/gate";
|
|
68
68
|
import { getHeadCommit } from "../../lifecycle/git";
|
|
69
69
|
import { remainingFanOut, type ChangedUnits, type FanOutProgress } from "../../components/fan-out";
|
|
70
|
+
import { readDeployedOutputs } from "../../components/wave-plan";
|
|
71
|
+
import { deployContext } from "../../components/driver";
|
|
70
72
|
import { resolveCliBuildParams, parseParamFlags } from "../build-params-cli";
|
|
71
73
|
import { formatError, formatInfo, formatWarning } from "../format";
|
|
72
74
|
import { FAN_OUT_GATE_OP } from "../../op/gate-name";
|
|
@@ -343,6 +345,36 @@ export async function runComponentsFanOut(ctx: CommandContext): Promise<number>
|
|
|
343
345
|
}
|
|
344
346
|
|
|
345
347
|
const registry = await fanOutRegistry(projectPath, config);
|
|
348
|
+
const env = args.env ?? "local";
|
|
349
|
+
|
|
350
|
+
// A completed component's recorded outputs win over a `--seed-outputs`
|
|
351
|
+
// entry for the same name, as a fresh apply's outputs merge over a seed in
|
|
352
|
+
// the driver: the record holds what this fan-out itself applied. A seed
|
|
353
|
+
// neither gives is read from the deployed estate (#4104), through its
|
|
354
|
+
// capability's `outputs`, as pr-plan reads a dependency outside the change.
|
|
355
|
+
// One that cannot be read leaves its dependents' references unresolved.
|
|
356
|
+
const componentOutputs: Record<string, Record<string, unknown>> = { ...seededOutputs, ...priorOutputs };
|
|
357
|
+
const seeds = (progress ? remainingFanOut(derived.plan, derived.components, progress) : derived.plan).seeds;
|
|
358
|
+
let deployedSeeds: Record<string, string[]>;
|
|
359
|
+
try {
|
|
360
|
+
deployedSeeds = await readDeployedOutputs(
|
|
361
|
+
seeds,
|
|
362
|
+
new Map(derived.components.map((c) => [c.name, c])),
|
|
363
|
+
registry,
|
|
364
|
+
(name) => deployContext({ env }, name),
|
|
365
|
+
componentOutputs,
|
|
366
|
+
);
|
|
367
|
+
} catch (err) {
|
|
368
|
+
console.error(formatError({
|
|
369
|
+
message: `could not read the deployed outputs of ${seeds.filter((s) => !(s in componentOutputs)).join(", ")}: ${err instanceof Error ? err.message : String(err)}`,
|
|
370
|
+
hint: "Nothing ran. --seed-outputs <file> gives a component's outputs instead of reading them.",
|
|
371
|
+
}));
|
|
372
|
+
return 1;
|
|
373
|
+
}
|
|
374
|
+
for (const [name, keys] of Object.entries(deployedSeeds)) {
|
|
375
|
+
console.error(formatInfo(`${name}: outputs read from the deployed estate (${keys.length > 0 ? keys.join(", ") : "none yet"})`));
|
|
376
|
+
}
|
|
377
|
+
|
|
346
378
|
// #3061: the commit this run deploys, for the workloads' vcs.ref.head.revision.
|
|
347
379
|
const revision = await getHeadCommit().catch(() => undefined);
|
|
348
380
|
|
|
@@ -378,12 +410,9 @@ export async function runComponentsFanOut(ctx: CommandContext): Promise<number>
|
|
|
378
410
|
let result;
|
|
379
411
|
try {
|
|
380
412
|
result = await runFanOut(derived.plan, derived.components, registry, {
|
|
381
|
-
env
|
|
413
|
+
env,
|
|
382
414
|
releaseIdentity: () => (revision ? { revision } : {}),
|
|
383
|
-
|
|
384
|
-
// entry for the same name, as a fresh apply's outputs merge over a seed in
|
|
385
|
-
// the driver: the record holds what this fan-out itself applied.
|
|
386
|
-
componentOutputs: { ...seededOutputs, ...priorOutputs },
|
|
415
|
+
componentOutputs,
|
|
387
416
|
...(gate ? { gate } : {}),
|
|
388
417
|
...(waveGate ? { waveGate } : {}),
|
|
389
418
|
...(waveGate
|
package/src/cli/handlers/pr.ts
CHANGED
|
@@ -112,6 +112,11 @@ interface Prepared {
|
|
|
112
112
|
head: string;
|
|
113
113
|
gate: string;
|
|
114
114
|
forge?: PrForge;
|
|
115
|
+
/**
|
|
116
|
+
* Every component in the project, not the selection (`deriveFanOut`), so
|
|
117
|
+
* the plan reads the deployed outputs of a dependency the change did not
|
|
118
|
+
* select (#4104).
|
|
119
|
+
*/
|
|
115
120
|
components: DriverComponent[];
|
|
116
121
|
plan: FanOutPlan;
|
|
117
122
|
signal: ComponentChangeSignal;
|
package/src/cli/main.test.ts
CHANGED
package/src/cli/main.ts
CHANGED
|
@@ -249,6 +249,56 @@ CI:
|
|
|
249
249
|
chant/lifecycle that a runner key signed. Pushes the
|
|
250
250
|
tags. --key, or the PEM in CHANT_RUNNER_KEY, signs
|
|
251
251
|
each tag with a runner key.
|
|
252
|
+
ci run plan|apply --env <env> --base <sha> [--pr <n>] [--commit <sha>]
|
|
253
|
+
[--forge github|gitlab|forgejo] [--gate <name>] [--branch <name>]
|
|
254
|
+
[--events-json] [--report-url <url>] [--image <image>]
|
|
255
|
+
[--before-script <line>]... [--pr-build]
|
|
256
|
+
[--pr-build-command <line>] [--chant <command>]
|
|
257
|
+
[--pass-env <NAME>]... [--secrets <file>] [--key <runner key.pem>]
|
|
258
|
+
[-o <dir>]
|
|
259
|
+
[--no-status] [--status-url <url template>]
|
|
260
|
+
Run the pull-request pipeline's plan or apply job as
|
|
261
|
+
a pipeline Op in a fresh worktree of --commit (default
|
|
262
|
+
HEAD) with a clean environment. --base and --pr stand
|
|
263
|
+
in for the forge's event values. --pr-build runs
|
|
264
|
+
the job's build and lint lines first. Records the run as
|
|
265
|
+
pr-plan or pr-apply on chant/lifecycle, signed with
|
|
266
|
+
--key or CHANT_RUNNER_KEY, so ci tick from runs counts
|
|
267
|
+
it, and copies the report to -o (default .chant/pr). With a forge
|
|
268
|
+
token it sets a chant/pr-plan or chant/pr-apply commit
|
|
269
|
+
status (pending, then success or failure); --no-status
|
|
270
|
+
turns that off, and --status-url links it.
|
|
271
|
+
--secrets (or CHANT_CI_SECRETS) names a dotenv or
|
|
272
|
+
JSON file only its owner can read whose values the
|
|
273
|
+
job gets, and which are shown as *** in the events,
|
|
274
|
+
the record, the report and the output.
|
|
275
|
+
With --report-url it also adds the run to
|
|
276
|
+
<url>/runs/index.json, newest first, cut to the
|
|
277
|
+
newest --run-index-limit entries (default 200).
|
|
278
|
+
ci runs --report-url <url> [--json]
|
|
279
|
+
List the runs in the index at the report URL, newest
|
|
280
|
+
first.
|
|
281
|
+
ci serve --forge forgejo|github --repo <owner/repo> --env <env>
|
|
282
|
+
[--interval <s>] [--max-jobs <n>] [--state <dir>]
|
|
283
|
+
[--author <login>]... [--once] [--rerun-failed] [--unsigned]
|
|
284
|
+
[ci run's job flags]
|
|
285
|
+
Poll the forge for open pull requests and the target
|
|
286
|
+
branch's head, and run ci run plan for each new pull
|
|
287
|
+
request head and ci run apply for each new commit on
|
|
288
|
+
the branch. Dedupes on job and commit in a state file
|
|
289
|
+
under --state, takes a lease per apply group, keeps
|
|
290
|
+
each apply's resume record there, and runs at most
|
|
291
|
+
--max-jobs (default 1) jobs at once. --once runs one
|
|
292
|
+
poll and waits for its jobs. --rerun-failed runs a
|
|
293
|
+
failed apply or plan again. Refuses to start when its
|
|
294
|
+
runner key is not listed in .chant/trust.json at the
|
|
295
|
+
target's head, unless --unsigned
|
|
296
|
+
ci serve|run --setup-key [--trust [--principal <name>]] [--state <dir>] [--branch <name>]
|
|
297
|
+
Make the host's runner key (Ed25519, mode 0600) in the
|
|
298
|
+
state directory when there is none and print its public
|
|
299
|
+
key. --trust commits its runner entry on a branch of its
|
|
300
|
+
own and pushes it for review; the target branch is never
|
|
301
|
+
written
|
|
252
302
|
ci workflow [--workflow <name>]... [--output <file>] Write
|
|
253
303
|
[--chant <command>] [--install <command>]
|
|
254
304
|
[--token-secret <NAME> | --app-id-var <VAR> --app-key-secret <NAME>]
|
|
@@ -674,6 +724,13 @@ Workspace (level 1, #2524):
|
|
|
674
724
|
threshold of the old one. A commit an agent run made
|
|
675
725
|
is attested by the run's signed statement; with
|
|
676
726
|
attested-runs, only those commits must be
|
|
727
|
+
workspace trust runner add <principal> --key <ssh-ed25519 line or file> [--class runner|service] [--by <principal>] [--expect <digest>] [--dry-run]
|
|
728
|
+
workspace trust runner remove <principal> [--by <principal>] [--expect <digest>] [--dry-run]
|
|
729
|
+
List a runner's key under runners in .chant/trust.json,
|
|
730
|
+
granting a runner the runner role, or take both away,
|
|
731
|
+
editing the file in place. --expect refuses the write
|
|
732
|
+
when the file's digest has changed. Prints the
|
|
733
|
+
trust-write document and the new digest; never commits
|
|
677
734
|
workspace signers [--json] [rotate [--threshold <n>] | sign --key <file>]
|
|
678
735
|
Show the signer history at base, or propose the next
|
|
679
736
|
signer set and sign it with a current signer's key
|
|
@@ -1306,6 +1363,11 @@ export const commandRegistry: CommandDef[] = [
|
|
|
1306
1363
|
{ name: "ci list", runsNoConfig: true, handler: async (ctx) => (await import("../workspace/ci-green-cli")).runCiList(ctx) },
|
|
1307
1364
|
{ name: "ci tick", runsNoConfig: true, handler: async (ctx) => (await import("../workspace/ci-green-cli")).runCiTick(ctx) },
|
|
1308
1365
|
{ name: "ci workflow", runsNoConfig: true, handler: async (ctx) => (await import("../workspace/ci-green-cli")).runCiWorkflow(ctx) },
|
|
1366
|
+
// #4094: one pull-request job as a pipeline Op in a fresh checkout, the chant runner's first slice.
|
|
1367
|
+
{ name: "ci run", runsNoConfig: true, handler: async (ctx) => (await import("../workspace/ci-run")).runCiRun(ctx) },
|
|
1368
|
+
// #4098: the run index at the report URL.
|
|
1369
|
+
{ name: "ci runs", runsNoConfig: true, handler: async (ctx) => (await import("../workspace/ci-run-index")).runCiRuns(ctx) },
|
|
1370
|
+
{ name: "ci serve", runsNoConfig: true, handler: async (ctx) => (await import("../workspace/ci-serve")).runCiServe(ctx) },
|
|
1309
1371
|
// #4026: the release cut from a green commit (ws-104). Imported on first use.
|
|
1310
1372
|
{ name: "release cut", runsNoConfig: true, handler: async (ctx) => (await import("./handlers/release")).runReleaseCut(ctx) },
|
|
1311
1373
|
// #4027: publish a cut release, to the targets #4047 declares.
|
|
@@ -1409,6 +1471,8 @@ export const commandRegistry: CommandDef[] = [
|
|
|
1409
1471
|
// #2548 — an agent session's member, write scope and spec, for a session that resumes.
|
|
1410
1472
|
{ name: "workspace agent", runsNoConfig: true, handler: async (ctx) => (await import("../workspace/agent-cli")).runWorkspaceAgent(ctx) },
|
|
1411
1473
|
{ name: "workspace verify", handler: async (ctx) => (await import("../workspace/trust/verify-cli")).runWorkspaceVerify(ctx) },
|
|
1474
|
+
// #3510 — a runner entry of .chant/trust.json, written through chant so a box repo never edits it by hand (ws-074).
|
|
1475
|
+
{ name: "workspace trust", runsNoConfig: true, handler: async (ctx) => (await import("../workspace/trust/trust-write")).runWorkspaceTrust(ctx) },
|
|
1412
1476
|
{ name: "workspace signers", handler: async (ctx) => (await import("../workspace/trust/signers-cli")).runWorkspaceSigners(ctx) },
|
|
1413
1477
|
{ name: "workspace evidence", handler: async (ctx) => (await import("../workspace/trust/evidence-cli")).runWorkspaceEvidence(ctx) },
|
|
1414
1478
|
|