@akanjs/cli 3.1.0-rc.1 → 3.1.0-rc.3
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/.build-stamp +1 -1
- package/{CodeAgent-kyprmy06.js → CodeAgent-ctk4fh2b.js} +2 -2
- package/{CodeTui-21mtze3j.js → CodeTui-mrxtrdzw.js} +1 -1
- package/TunnelShare-rj74jd6x.js +13 -0
- package/{agent.command-f3gsc1gd.js → agent.command-ncre6kwz.js} +5 -5
- package/akanCode.js +2 -2
- package/{application.command-5s780vnz.js → application.command-54mawreg.js} +52 -41
- package/{applicationBuildRunner-1yaqsrdq.js → applicationBuildRunner-8573zr2w.js} +16 -30
- package/buildBatch.proc.js +22 -13
- package/{cloud.command-yj6xb2a7.js → cloud.command-nyemv0m3.js} +57 -65
- package/cloud.script-ht6yb203.js +23 -0
- package/{code.command-3wkn4ja5.js → code.command-spsrzg63.js} +6 -6
- package/{context.command-9jjn3gcw.js → context.command-768zv7z4.js} +16 -13
- package/context.runner-tf1b53qq.js +24 -0
- package/{guideline.command-dtczrqpf.js → guideline.command-4ycwc51q.js} +2 -2
- package/guidelines/conventions/conventions.instruction.md +28 -15
- package/guidelines/cssRule/cssRule.instruction.md +12 -6
- package/guidelines/mcpRule/mcpRule.instruction.md +27 -11
- package/guidelines/runtimeRule/runtimeRule.instruction.md +24 -15
- package/guidelines/transportRule/transportRule.instruction.md +28 -5
- package/incrementalBuilder.proc.js +9 -6
- package/{index-2etcv0sc.js → index-00seamy5.js} +1 -1
- package/{index-jx4bzvwg.js → index-1a7nhq56.js} +92 -52
- package/{index-e0cvze1s.js → index-1nyxvz8j.js} +4 -4
- package/index-2z3kykvd.js +248 -0
- package/{index-rtct7d43.js → index-3675mvjw.js} +1 -1
- package/{index-56d2g8ms.js → index-3ddt3qjg.js} +1 -1
- package/{index-6jjtnhkw.js → index-3x7yeecy.js} +51 -9
- package/{index-ww9bhp67.js → index-4pf8g7ff.js} +1 -1
- package/index-8935h3n1.js +33 -0
- package/{index-v29746vb.js → index-8eqkxwsx.js} +3 -3
- package/{index-c2yrsxdh.js → index-91rt5bye.js} +96 -320
- package/{index-55mynnqs.js → index-bv210r6t.js} +459 -210
- package/{index-c76k723e.js → index-c4h5b5nq.js} +2 -2
- package/{index-qt22xstj.js → index-c9zjb3tw.js} +5 -3
- package/{index-c1x0be5j.js → index-dmsvd2rv.js} +2 -2
- package/index-ec080x99.js +1124 -0
- package/{index-ctanya41.js → index-fjqvd5mx.js} +4 -4
- package/{index-aprt7pkz.js → index-fvn6f5wx.js} +229 -1327
- package/{index-rc5rwyps.js → index-jj7yapwq.js} +71 -60
- package/index-kmamh5bv.js +241 -0
- package/{index-fm46xrf1.js → index-q5abh0jq.js} +3 -3
- package/{index-1w2qaee2.js → index-qf8g88j9.js} +361 -291
- package/{index-gnyf1fqy.js → index-qvghfv6v.js} +2 -2
- package/{index-ae5pwhgs.js → index-r5d6rq19.js} +10 -8
- package/{index-2mx692qq.js → index-vhfjzda3.js} +2 -2
- package/{index-8r4d7cmt.js → index-w9b71b6y.js} +4 -4
- package/{index-8ggztp8k.js → index-wanhh011.js} +42 -177
- package/{index-05g3jayg.js → index-wdze51wz.js} +1 -1
- package/index-wjqbc7sf.js +668 -0
- package/{index-rwq3ekqc.js → index-xf40a295.js} +7 -7
- package/{index-krq7btbt.js → index-xm5tmpv0.js} +3 -3
- package/index.js +22 -22
- package/{library.command-gaga5bmq.js → library.command-88ahgych.js} +3 -3
- package/{localRegistry.command-vsreyy42.js → localRegistry.command-ykea1m7b.js} +12 -9
- package/{module.command-7vpf6db4.js → module.command-zky4b7m9.js} +5 -5
- package/{package.command-twmvvf69.js → package.command-vh8pcedh.js} +3 -3
- package/package.json +2 -2
- package/{page.command-5asq7fdf.js → page.command-fyqeqxc3.js} +3 -3
- package/{pipeline.command-d0v38572.js → pipeline.command-r5t30py6.js} +220 -219
- package/{primitive.command-dyrs2fm8.js → primitive.command-a0516zfk.js} +6 -6
- package/{projectTypecheck-cvtkrzp2.js → projectTypecheck-7djd4z50.js} +1 -1
- package/{quality.command-6emsqpbg.js → quality.command-k7jcnfn0.js} +2 -2
- package/{repair.command-8t6aegv5.js → repair.command-85zg2an3.js} +5 -5
- package/{scalar.command-bnggy1x7.js → scalar.command-g5vmqwca.js} +4 -4
- package/{subspace.command-md2teygq.js → subspace.command-mp4evgsj.js} +421 -362
- package/{tunnel.command-jx3tdm2d.js → tunnel.command-kbnr99sj.js} +7 -4
- package/{workflow.command-jx0wag5r.js → workflow.command-9y9p1n8y.js} +10 -10
- package/{workspace.command-yxd7rr7g.js → workspace.command-jmec2kjg.js} +33 -30
- package/TunnelShare-q4jq08qg.js +0 -9
- package/cloud.script-h6pvcpa2.js +0 -20
- package/context.runner-atcrzz5b.js +0 -21
- package/index-6w0rbr9d.js +0 -68
- package/index-pgj2rt78.js +0 -404
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
runner,
|
|
5
5
|
script,
|
|
6
6
|
command
|
|
7
|
-
} from "./index-
|
|
7
|
+
} from "./index-3x7yeecy.js";
|
|
8
8
|
import {
|
|
9
9
|
FileSys2
|
|
10
10
|
} from "./index-52w0fvh8.js";
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
AkanAppConfig,
|
|
13
13
|
AkanWorkspaceConfig,
|
|
14
14
|
AppExecutor
|
|
15
|
-
} from "./index-
|
|
15
|
+
} from "./index-bv210r6t.js";
|
|
16
16
|
import {
|
|
17
17
|
resolveMobileTargets
|
|
18
18
|
} from "./index-q958tqqg.js";
|
|
@@ -31,13 +31,16 @@ import {
|
|
|
31
31
|
pipelineActions,
|
|
32
32
|
pipelineMilestones as pipelineMilestones2,
|
|
33
33
|
pipelineOtaPlatforms as pipelineOtaPlatforms2,
|
|
34
|
-
|
|
34
|
+
pipelinePlanLimits as pipelinePlanLimits2,
|
|
35
|
+
pipelinePlanVersion,
|
|
36
|
+
toError
|
|
35
37
|
} from "akanjs/common";
|
|
36
38
|
|
|
37
39
|
// pkgs/@akanjs/devkit/pipeline/PipelineInputReader.ts
|
|
38
40
|
import {
|
|
39
41
|
PipelineNames,
|
|
40
42
|
PipelinePattern,
|
|
43
|
+
PipelineValueReader,
|
|
41
44
|
pipelineActionNames,
|
|
42
45
|
pipelineMilestones,
|
|
43
46
|
pipelineOtaPlatforms,
|
|
@@ -47,15 +50,31 @@ import {
|
|
|
47
50
|
|
|
48
51
|
class PipelineInputReader {
|
|
49
52
|
static #futureKeys = ["cache", "artifacts", "input", "image", "services", "notify"];
|
|
53
|
+
static #stageKeys = ["id", "name", "description", "milestone", "when", "timeout", "allowFailure"];
|
|
54
|
+
static #shellKeys = [...PipelineInputReader.#stageKeys, "sh", "steps", "env", "secrets", "retry"];
|
|
55
|
+
static #postResults = ["success", "failure", "canceled", "always"];
|
|
50
56
|
static #durationPattern = /^(\d+)(s|m|h)$/;
|
|
57
|
+
static #trailingHighSurrogate = /[\uD800-\uDBFF]$/;
|
|
51
58
|
static #stepNameChars = 80;
|
|
59
|
+
static #shownChars = 80;
|
|
60
|
+
static clip(text, max) {
|
|
61
|
+
if (text.length <= max)
|
|
62
|
+
return text;
|
|
63
|
+
const cut = text.slice(0, max - 1);
|
|
64
|
+
return `${PipelineInputReader.#trailingHighSurrogate.test(cut) ? cut.slice(0, -1) : cut}\u2026`;
|
|
65
|
+
}
|
|
52
66
|
diagnostics = [];
|
|
53
67
|
#file;
|
|
68
|
+
#read = new PipelineValueReader("config", (path, ...problems) => {
|
|
69
|
+
for (const problem of problems)
|
|
70
|
+
this.#problem(path, problem);
|
|
71
|
+
return null;
|
|
72
|
+
});
|
|
54
73
|
constructor(file) {
|
|
55
74
|
this.#file = file;
|
|
56
75
|
}
|
|
57
76
|
workspace(value) {
|
|
58
|
-
const input = this.#object(value, "pipeline", ["schemaVersion", "env", "timeout", "before", "after", "post"]);
|
|
77
|
+
const input = this.#read.object(value, "pipeline", ["schemaVersion", "env", "timeout", "before", "after", "post"]);
|
|
59
78
|
if (!input)
|
|
60
79
|
return null;
|
|
61
80
|
this.#schemaVersion(input.schemaVersion, "pipeline.schemaVersion");
|
|
@@ -75,7 +94,7 @@ class PipelineInputReader {
|
|
|
75
94
|
};
|
|
76
95
|
}
|
|
77
96
|
app(value) {
|
|
78
|
-
const input = this.#object(value, "pipeline", ["schemaVersion", "stages", "env", "after"]);
|
|
97
|
+
const input = this.#read.object(value, "pipeline", ["schemaVersion", "stages", "env", "after"]);
|
|
79
98
|
if (!input)
|
|
80
99
|
return null;
|
|
81
100
|
this.#schemaVersion(input.schemaVersion, "pipeline.schemaVersion");
|
|
@@ -95,53 +114,51 @@ class PipelineInputReader {
|
|
|
95
114
|
};
|
|
96
115
|
}
|
|
97
116
|
#workspaceStage(value, path) {
|
|
98
|
-
const raw = this.#object(value, path);
|
|
117
|
+
const raw = this.#read.object(value, path);
|
|
99
118
|
if (!raw)
|
|
100
119
|
return null;
|
|
101
|
-
if ("use"
|
|
120
|
+
if (this.#declares(raw, "use")) {
|
|
102
121
|
this.#report("pipeline.schema.invalid", "built-in actions are declared per app, in apps/<app>/akan.config.ts", `${path}.use`);
|
|
103
122
|
return null;
|
|
104
123
|
}
|
|
105
|
-
if ("parallel"
|
|
124
|
+
if (this.#declares(raw, "parallel"))
|
|
106
125
|
return this.#parallelStage(raw, path);
|
|
107
|
-
if ("matrix"
|
|
126
|
+
if (this.#declares(raw, "matrix"))
|
|
108
127
|
return this.#matrixStage(raw, path);
|
|
109
|
-
return this.#shellStage(raw, path
|
|
128
|
+
return this.#shellStage(raw, path);
|
|
110
129
|
}
|
|
111
130
|
#appStage(value, path) {
|
|
112
|
-
const raw = this.#object(value, path);
|
|
131
|
+
const raw = this.#read.object(value, path);
|
|
113
132
|
if (!raw)
|
|
114
133
|
return null;
|
|
115
|
-
|
|
116
|
-
|
|
134
|
+
const workspaceKey = this.#declares(raw, "parallel") ? "parallel" : this.#declares(raw, "matrix") ? "matrix" : null;
|
|
135
|
+
if (workspaceKey) {
|
|
136
|
+
this.#report("pipeline.schema.invalid", `an app's own stage takes no \`${workspaceKey}\`: it is already that app's, and parallel or per-app copies belong to the workspace root akan.config.ts`, path);
|
|
117
137
|
return null;
|
|
118
138
|
}
|
|
119
|
-
if ("use"
|
|
139
|
+
if (this.#declares(raw, "use"))
|
|
120
140
|
return this.#actionStage(raw, path);
|
|
121
|
-
return this.#shellStage(raw, path
|
|
141
|
+
return this.#shellStage(raw, path);
|
|
142
|
+
}
|
|
143
|
+
#declares(raw, key) {
|
|
144
|
+
return raw[key] !== undefined || key in raw && raw.sh === undefined && raw.steps === undefined;
|
|
122
145
|
}
|
|
123
146
|
#actionStage(raw, path) {
|
|
124
|
-
if (!this.#keys(raw, path, [
|
|
147
|
+
if (!this.#read.keys(raw, path, [
|
|
125
148
|
"use",
|
|
126
|
-
|
|
127
|
-
"name",
|
|
128
|
-
"description",
|
|
129
|
-
"milestone",
|
|
130
|
-
"when",
|
|
131
|
-
"timeout",
|
|
132
|
-
"allowFailure",
|
|
149
|
+
...PipelineInputReader.#stageKeys,
|
|
133
150
|
"retry",
|
|
134
151
|
...raw.use === "ota" ? ["targets", "platforms"] : []
|
|
135
152
|
]))
|
|
136
153
|
return null;
|
|
137
|
-
const action = this.#oneOf(raw.use, `${path}.use`, pipelineActionNames);
|
|
154
|
+
const action = this.#read.oneOf(raw.use, `${path}.use`, pipelineActionNames);
|
|
138
155
|
if (!action)
|
|
139
156
|
return null;
|
|
140
|
-
const name = raw.name === undefined ? null : this.#
|
|
157
|
+
const name = raw.name === undefined ? null : this.#read.string(raw.name, `${path}.name`, pipelinePlanLimits.nameChars);
|
|
141
158
|
const id = raw.id === undefined ? action : this.#id(raw.id, `${path}.id`);
|
|
142
159
|
if (!id)
|
|
143
160
|
return null;
|
|
144
|
-
const attempts = raw.retry === undefined ? null : this.#int(raw.retry, `${path}.retry`, 1, pipelinePlanLimits.retryAttempts);
|
|
161
|
+
const attempts = raw.retry === undefined ? null : this.#read.int(raw.retry, `${path}.retry`, 1, pipelinePlanLimits.retryAttempts);
|
|
145
162
|
return {
|
|
146
163
|
kind: "action",
|
|
147
164
|
action,
|
|
@@ -150,13 +167,13 @@ class PipelineInputReader {
|
|
|
150
167
|
named: name !== null,
|
|
151
168
|
description: this.#description(raw.description, `${path}.description`),
|
|
152
169
|
milestone: this.#milestone(raw.milestone, `${path}.milestone`),
|
|
153
|
-
when: this.#when(raw.when, `${path}.when
|
|
170
|
+
when: this.#when(raw.when, `${path}.when`),
|
|
154
171
|
timeoutSeconds: this.#duration(raw.timeout, `${path}.timeout`),
|
|
155
172
|
allowFailure: this.#boolean(raw.allowFailure, `${path}.allowFailure`),
|
|
156
173
|
retry: attempts === null ? null : { attempts, delaySeconds: 0 },
|
|
157
174
|
targets: raw.targets === undefined ? null : this.#nonEmptySet(this.#strings(raw.targets, `${path}.targets`), `${path}.targets`),
|
|
158
175
|
platforms: raw.platforms === undefined ? null : this.#nonEmptySet(this.#list(raw.platforms, `${path}.platforms`).flatMap((platform, idx) => {
|
|
159
|
-
const parsed = this.#oneOf(platform, `${path}.platforms[${idx}]`, pipelineOtaPlatforms);
|
|
176
|
+
const parsed = this.#read.oneOf(platform, `${path}.platforms[${idx}]`, pipelineOtaPlatforms);
|
|
160
177
|
return parsed ? [parsed] : [];
|
|
161
178
|
}), `${path}.platforms`),
|
|
162
179
|
path
|
|
@@ -169,53 +186,23 @@ class PipelineInputReader {
|
|
|
169
186
|
}
|
|
170
187
|
return [...new Set(items)];
|
|
171
188
|
}
|
|
172
|
-
#shellStage(raw, path
|
|
173
|
-
|
|
174
|
-
"id",
|
|
175
|
-
"name",
|
|
176
|
-
"description",
|
|
177
|
-
"milestone",
|
|
178
|
-
"when",
|
|
179
|
-
"timeout",
|
|
180
|
-
"allowFailure",
|
|
181
|
-
"sh",
|
|
182
|
-
"steps",
|
|
183
|
-
"env",
|
|
184
|
-
"secrets",
|
|
185
|
-
"retry"
|
|
186
|
-
];
|
|
187
|
-
if (!this.#keys(raw, path, keys))
|
|
189
|
+
#shellStage(raw, path) {
|
|
190
|
+
if (!this.#read.keys(raw, path, PipelineInputReader.#shellKeys))
|
|
188
191
|
return null;
|
|
189
|
-
const head = this.#stageHead(raw, path
|
|
192
|
+
const head = this.#stageHead(raw, path);
|
|
190
193
|
if (!head)
|
|
191
194
|
return null;
|
|
192
195
|
const steps = this.#stageSteps(raw, path);
|
|
193
196
|
return steps ? { kind: "sh", ...head, steps } : null;
|
|
194
197
|
}
|
|
195
198
|
#matrixStage(raw, path) {
|
|
196
|
-
|
|
197
|
-
"id",
|
|
198
|
-
"name",
|
|
199
|
-
"description",
|
|
200
|
-
"milestone",
|
|
201
|
-
"when",
|
|
202
|
-
"timeout",
|
|
203
|
-
"allowFailure",
|
|
204
|
-
"sh",
|
|
205
|
-
"steps",
|
|
206
|
-
"env",
|
|
207
|
-
"secrets",
|
|
208
|
-
"retry",
|
|
209
|
-
"matrix",
|
|
210
|
-
"maxParallel"
|
|
211
|
-
];
|
|
212
|
-
if (!this.#keys(raw, path, keys))
|
|
199
|
+
if (!this.#read.keys(raw, path, [...PipelineInputReader.#shellKeys, "matrix", "maxParallel"]))
|
|
213
200
|
return null;
|
|
214
201
|
if (raw.matrix !== "apps") {
|
|
215
|
-
this.#report("pipeline.schema.invalid", `${
|
|
202
|
+
this.#report("pipeline.schema.invalid", `${PipelineInputReader.#show(raw.matrix)} is not a matrix axis: only "apps"`, `${path}.matrix`);
|
|
216
203
|
return null;
|
|
217
204
|
}
|
|
218
|
-
const head = this.#stageHead(raw, path,
|
|
205
|
+
const head = this.#stageHead(raw, path, { allowApp: true });
|
|
219
206
|
if (!head)
|
|
220
207
|
return null;
|
|
221
208
|
const steps = this.#stageSteps(raw, path);
|
|
@@ -224,23 +211,10 @@ class PipelineInputReader {
|
|
|
224
211
|
return { kind: "matrix", ...head, maxParallel: this.#maxParallel(raw.maxParallel, `${path}.maxParallel`), steps };
|
|
225
212
|
}
|
|
226
213
|
#parallelStage(raw, path) {
|
|
227
|
-
const keys = [
|
|
228
|
-
|
|
229
|
-
"name",
|
|
230
|
-
"description",
|
|
231
|
-
"milestone",
|
|
232
|
-
"when",
|
|
233
|
-
"timeout",
|
|
234
|
-
"allowFailure",
|
|
235
|
-
"parallel",
|
|
236
|
-
"maxParallel",
|
|
237
|
-
"failFast",
|
|
238
|
-
"env",
|
|
239
|
-
"secrets"
|
|
240
|
-
];
|
|
241
|
-
if (!this.#keys(raw, path, keys))
|
|
214
|
+
const keys = [...PipelineInputReader.#stageKeys, "parallel", "maxParallel", "failFast", "env", "secrets"];
|
|
215
|
+
if (!this.#read.keys(raw, path, keys))
|
|
242
216
|
return null;
|
|
243
|
-
const head = this.#stageHead(raw, path
|
|
217
|
+
const head = this.#stageHead(raw, path);
|
|
244
218
|
if (!head)
|
|
245
219
|
return null;
|
|
246
220
|
const env = this.#env(raw.env, `${path}.env`);
|
|
@@ -248,20 +222,22 @@ class PipelineInputReader {
|
|
|
248
222
|
const ids = new Set;
|
|
249
223
|
const branches = this.#list(raw.parallel, `${path}.parallel`).flatMap((branch, idx) => {
|
|
250
224
|
const branchPath = `${path}.parallel[${idx}]`;
|
|
251
|
-
const branchRaw = this.#object(branch, branchPath);
|
|
225
|
+
const branchRaw = this.#read.object(branch, branchPath);
|
|
252
226
|
if (!branchRaw)
|
|
253
227
|
return [];
|
|
254
|
-
if ("use"
|
|
228
|
+
if (["use", "parallel", "matrix"].some((key) => this.#declares(branchRaw, key))) {
|
|
255
229
|
this.#report("pipeline.schema.invalid", "a parallel branch is a shell stage", branchPath);
|
|
256
230
|
return [];
|
|
257
231
|
}
|
|
258
|
-
const rule = this.#shellStage(branchRaw, branchPath
|
|
232
|
+
const rule = this.#shellStage(branchRaw, branchPath);
|
|
259
233
|
if (!rule)
|
|
260
234
|
return [];
|
|
261
235
|
if (rule.when.changed && head.when.changed) {
|
|
262
236
|
this.#report("pipeline.schema.invalid", "`when.changed` on both a parallel stage and its branch cannot be combined; keep one", `${branchPath}.when.changed`);
|
|
263
237
|
return [];
|
|
264
238
|
}
|
|
239
|
+
if (head.id === "post" && PipelineInputReader.#postResults.includes(rule.id))
|
|
240
|
+
this.#report("pipeline.id.reserved", `"post.${rule.id}" is reserved for the steps of post.${rule.id}; give this branch or its parallel stage another \`id\``, `${branchPath}.${branchRaw.id === undefined ? "name" : "id"}`);
|
|
265
241
|
this.#claimId(ids, rule.id, rule.path);
|
|
266
242
|
const steps = rule.steps.map((step) => ({
|
|
267
243
|
...step,
|
|
@@ -270,7 +246,7 @@ class PipelineInputReader {
|
|
|
270
246
|
}));
|
|
271
247
|
return [{ ...rule, steps }];
|
|
272
248
|
});
|
|
273
|
-
if (!branches.length && Array.isArray(raw.parallel))
|
|
249
|
+
if (!branches.length && (raw.parallel === undefined || Array.isArray(raw.parallel)))
|
|
274
250
|
this.#report("pipeline.schema.invalid", "a parallel stage holds at least one branch", `${path}.parallel`);
|
|
275
251
|
return {
|
|
276
252
|
kind: "parallel",
|
|
@@ -280,8 +256,8 @@ class PipelineInputReader {
|
|
|
280
256
|
branches
|
|
281
257
|
};
|
|
282
258
|
}
|
|
283
|
-
#stageHead(raw, path,
|
|
284
|
-
const name = this.#
|
|
259
|
+
#stageHead(raw, path, { allowApp = false } = {}) {
|
|
260
|
+
const name = this.#read.string(raw.name, `${path}.name`, pipelinePlanLimits.nameChars);
|
|
285
261
|
if (name === null)
|
|
286
262
|
return null;
|
|
287
263
|
const id = raw.id === undefined ? this.#slugId(name, `${path}.name`) : this.#id(raw.id, `${path}.id`);
|
|
@@ -292,7 +268,7 @@ class PipelineInputReader {
|
|
|
292
268
|
name,
|
|
293
269
|
description: this.#description(raw.description, `${path}.description`),
|
|
294
270
|
milestone: this.#milestone(raw.milestone, `${path}.milestone`),
|
|
295
|
-
when: this.#when(raw.when, `${path}.when`,
|
|
271
|
+
when: this.#when(raw.when, `${path}.when`, { allowApp }),
|
|
296
272
|
timeoutSeconds: this.#duration(raw.timeout, `${path}.timeout`),
|
|
297
273
|
allowFailure: this.#boolean(raw.allowFailure, `${path}.allowFailure`),
|
|
298
274
|
path
|
|
@@ -330,20 +306,20 @@ class PipelineInputReader {
|
|
|
330
306
|
});
|
|
331
307
|
}
|
|
332
308
|
#step(value, path, idx) {
|
|
333
|
-
const raw = typeof value === "string" ? { sh: value } : this.#object(value, path);
|
|
309
|
+
const raw = typeof value === "string" ? { sh: value } : this.#read.object(value, path);
|
|
334
310
|
if (!raw)
|
|
335
311
|
return null;
|
|
336
312
|
const keys = ["sh", "id", "name", "cwd", "env", "secrets", "timeout", "retry", "allowFailure", "skipExitCodes"];
|
|
337
|
-
if (!this.#keys(raw, path, keys))
|
|
313
|
+
if (!this.#read.keys(raw, path, keys))
|
|
338
314
|
return null;
|
|
339
|
-
const run = this.#
|
|
315
|
+
const run = this.#read.string(raw.sh, typeof value === "string" ? path : `${path}.sh`, Number.POSITIVE_INFINITY);
|
|
340
316
|
if (run === null)
|
|
341
317
|
return null;
|
|
342
318
|
if (new TextEncoder().encode(run).length > pipelinePlanLimits.shBytes) {
|
|
343
319
|
this.#report("pipeline.limit.exceeded", `a script is at most ${pipelinePlanLimits.shBytes} bytes`, path);
|
|
344
320
|
return null;
|
|
345
321
|
}
|
|
346
|
-
const name = raw.name === undefined ? PipelineInputReader.#nameOf(run) : this.#
|
|
322
|
+
const name = raw.name === undefined ? PipelineInputReader.#nameOf(run) : this.#read.string(raw.name, `${path}.name`, pipelinePlanLimits.nameChars);
|
|
347
323
|
if (name === null)
|
|
348
324
|
return null;
|
|
349
325
|
const localId = raw.id !== undefined ? this.#id(raw.id, `${path}.id`) : raw.name !== undefined ? this.#slugId(name, `${path}.name`) : String(idx + 1);
|
|
@@ -366,11 +342,11 @@ class PipelineInputReader {
|
|
|
366
342
|
const empty = { success: [], failure: [], canceled: [], always: [] };
|
|
367
343
|
if (value === undefined)
|
|
368
344
|
return empty;
|
|
369
|
-
const raw = this.#object(value, path);
|
|
345
|
+
const raw = this.#read.object(value, path);
|
|
370
346
|
if (!raw)
|
|
371
347
|
return empty;
|
|
372
348
|
for (const key of Object.keys(raw))
|
|
373
|
-
if (!
|
|
349
|
+
if (!PipelineInputReader.#postResults.includes(key))
|
|
374
350
|
this.#report("pipeline.schema.unknownKey", key === "unstable" ? "`unstable` is not a result the framework knows: the executor decides which list an allowed failure runs" : `"${key}" is not a post result: success, failure, canceled or always`, `${path}.${key}`);
|
|
375
351
|
const steps = (key) => {
|
|
376
352
|
const ids = new Set;
|
|
@@ -388,11 +364,11 @@ class PipelineInputReader {
|
|
|
388
364
|
always: steps("always")
|
|
389
365
|
};
|
|
390
366
|
}
|
|
391
|
-
#when(value, path, allowApp) {
|
|
367
|
+
#when(value, path, { allowApp = false } = {}) {
|
|
392
368
|
const none = { branch: null, trigger: null, changed: null, app: null };
|
|
393
369
|
if (value === undefined)
|
|
394
370
|
return none;
|
|
395
|
-
const raw = this.#object(value, path);
|
|
371
|
+
const raw = this.#read.object(value, path);
|
|
396
372
|
if (!raw)
|
|
397
373
|
return none;
|
|
398
374
|
for (const key of Object.keys(raw)) {
|
|
@@ -401,9 +377,11 @@ class PipelineInputReader {
|
|
|
401
377
|
this.#report("pipeline.schema.unknownKey", key === "app" ? "`when.app` narrows a workspace matrix stage only; an app's own stages are already that app's" : `"${key}" is not a condition: branch, trigger, changed${allowApp ? " or app" : ""}`, `${path}.${key}`);
|
|
402
378
|
}
|
|
403
379
|
const triggers = raw.trigger === undefined ? null : (typeof raw.trigger === "string" ? [raw.trigger] : this.#list(raw.trigger, `${path}.trigger`)).flatMap((trigger, idx) => {
|
|
404
|
-
const parsed = this.#oneOf(trigger, `${path}.trigger[${idx}]`, pipelineTriggers);
|
|
380
|
+
const parsed = this.#read.oneOf(trigger, `${path}.trigger[${idx}]`, pipelineTriggers);
|
|
405
381
|
return parsed ? [parsed] : [];
|
|
406
382
|
});
|
|
383
|
+
if (Array.isArray(raw.trigger) && !raw.trigger.length)
|
|
384
|
+
this.#report("pipeline.schema.invalid", "a trigger list may not be empty", `${path}.trigger`);
|
|
407
385
|
return {
|
|
408
386
|
branch: this.#patterns(raw.branch, `${path}.branch`),
|
|
409
387
|
trigger: triggers,
|
|
@@ -424,12 +402,12 @@ class PipelineInputReader {
|
|
|
424
402
|
}
|
|
425
403
|
#schemaVersion(value, path) {
|
|
426
404
|
if (value !== undefined && value !== 1)
|
|
427
|
-
this.#report("pipeline.schema.unsupported", `${
|
|
405
|
+
this.#report("pipeline.schema.unsupported", `${PipelineInputReader.#show(value)} is not a schemaVersion this CLI reads (1)`, path);
|
|
428
406
|
}
|
|
429
407
|
#env(value, path) {
|
|
430
408
|
if (value === undefined)
|
|
431
409
|
return {};
|
|
432
|
-
const raw = this.#object(value, path);
|
|
410
|
+
const raw = this.#read.object(value, path);
|
|
433
411
|
if (!raw)
|
|
434
412
|
return {};
|
|
435
413
|
const env = {};
|
|
@@ -439,6 +417,8 @@ class PipelineInputReader {
|
|
|
439
417
|
this.#report("pipeline.env.name", problem, `${path}.${name}`);
|
|
440
418
|
else if (typeof entry !== "string")
|
|
441
419
|
this.#report("pipeline.schema.invalid", "must be a string", `${path}.${name}`);
|
|
420
|
+
else if (entry.length > pipelinePlanLimits.shBytes)
|
|
421
|
+
this.#report("pipeline.limit.exceeded", `an env value is at most ${pipelinePlanLimits.shBytes} characters`, `${path}.${name}`);
|
|
442
422
|
else
|
|
443
423
|
env[name] = entry;
|
|
444
424
|
}
|
|
@@ -446,10 +426,10 @@ class PipelineInputReader {
|
|
|
446
426
|
}
|
|
447
427
|
#secrets(value, path) {
|
|
448
428
|
const names = value === undefined ? [] : this.#strings(value, path);
|
|
449
|
-
const valid = names.filter((name) => {
|
|
429
|
+
const valid = names.filter((name, idx) => {
|
|
450
430
|
const problem = PipelineNames.secretNameError(name);
|
|
451
431
|
if (problem)
|
|
452
|
-
this.#report("pipeline.secret.name", problem, path);
|
|
432
|
+
this.#report("pipeline.secret.name", problem, `${path}[${idx}]`);
|
|
453
433
|
return !problem;
|
|
454
434
|
});
|
|
455
435
|
return [...new Set(valid)];
|
|
@@ -457,7 +437,7 @@ class PipelineInputReader {
|
|
|
457
437
|
#cwd(value, path) {
|
|
458
438
|
if (value === undefined)
|
|
459
439
|
return ".";
|
|
460
|
-
const cwd = this.#
|
|
440
|
+
const cwd = this.#read.string(value, path, 1024);
|
|
461
441
|
if (cwd === null)
|
|
462
442
|
return ".";
|
|
463
443
|
if (cwd.startsWith("/") || cwd.startsWith("~") || cwd.includes("\\") || cwd.split("/").includes("..")) {
|
|
@@ -470,7 +450,7 @@ class PipelineInputReader {
|
|
|
470
450
|
if (value === undefined)
|
|
471
451
|
return [];
|
|
472
452
|
const codes = this.#list(value, path).flatMap((code, idx) => {
|
|
473
|
-
const parsed = this.#int(code, `${path}[${idx}]`, 1, 255);
|
|
453
|
+
const parsed = this.#read.int(code, `${path}[${idx}]`, 1, 255);
|
|
474
454
|
return parsed === null ? [] : [parsed];
|
|
475
455
|
});
|
|
476
456
|
return [...new Set(codes)];
|
|
@@ -479,13 +459,13 @@ class PipelineInputReader {
|
|
|
479
459
|
if (value === undefined)
|
|
480
460
|
return null;
|
|
481
461
|
if (typeof value === "number") {
|
|
482
|
-
const attempts = this.#int(value, path, 1, pipelinePlanLimits.retryAttempts);
|
|
462
|
+
const attempts = this.#read.int(value, path, 1, pipelinePlanLimits.retryAttempts);
|
|
483
463
|
return attempts === null ? null : { attempts, delaySeconds: 0 };
|
|
484
464
|
}
|
|
485
|
-
const raw = this.#object(value, path);
|
|
486
|
-
if (!raw || !this.#keys(raw, path, ["attempts", "delay"]))
|
|
465
|
+
const raw = this.#read.object(value, path);
|
|
466
|
+
if (!raw || !this.#read.keys(raw, path, ["attempts", "delay"]))
|
|
487
467
|
return null;
|
|
488
|
-
const attempts = this.#int(raw.attempts, `${path}.attempts`, 1, pipelinePlanLimits.retryAttempts);
|
|
468
|
+
const attempts = this.#read.int(raw.attempts, `${path}.attempts`, 1, pipelinePlanLimits.retryAttempts);
|
|
489
469
|
const delaySeconds = this.#duration(raw.delay, `${path}.delay`, {
|
|
490
470
|
min: 0,
|
|
491
471
|
max: pipelinePlanLimits.retryDelaySeconds
|
|
@@ -498,22 +478,22 @@ class PipelineInputReader {
|
|
|
498
478
|
const match = typeof value === "string" ? PipelineInputReader.#durationPattern.exec(value) : null;
|
|
499
479
|
const seconds = typeof value === "number" && Number.isInteger(value) ? value : match ? Number(match[1]) * { s: 1, m: 60, h: 3600 }[match[2]] : Number.NaN;
|
|
500
480
|
if (!Number.isInteger(seconds) || seconds < min || seconds > max) {
|
|
501
|
-
this.#report("pipeline.duration.invalid", `${
|
|
481
|
+
this.#report("pipeline.duration.invalid", `${PipelineInputReader.#show(value)} is not a duration between ${min} and ${max} seconds: whole seconds, or "30s", "20m", "1h"`, path);
|
|
502
482
|
return null;
|
|
503
483
|
}
|
|
504
484
|
return seconds;
|
|
505
485
|
}
|
|
506
486
|
#maxParallel(value, path) {
|
|
507
|
-
return value === undefined ? null : this.#int(value, path, 1, pipelinePlanLimits.stages);
|
|
487
|
+
return value === undefined ? null : this.#read.int(value, path, 1, pipelinePlanLimits.stages);
|
|
508
488
|
}
|
|
509
489
|
#milestone(value, path) {
|
|
510
|
-
return value === undefined ? null : this.#oneOf(value, path, pipelineMilestones);
|
|
490
|
+
return value === undefined ? null : this.#read.oneOf(value, path, pipelineMilestones);
|
|
511
491
|
}
|
|
512
492
|
#description(value, path) {
|
|
513
|
-
return value === undefined ? null : this.#
|
|
493
|
+
return value === undefined ? null : this.#read.string(value, path, pipelinePlanLimits.descriptionChars);
|
|
514
494
|
}
|
|
515
495
|
#id(value, path) {
|
|
516
|
-
const id = this.#
|
|
496
|
+
const id = this.#read.string(value, path, 64);
|
|
517
497
|
if (id === null)
|
|
518
498
|
return null;
|
|
519
499
|
const problem = PipelineNames.idError(id);
|
|
@@ -539,77 +519,58 @@ class PipelineInputReader {
|
|
|
539
519
|
static #nameOf(run) {
|
|
540
520
|
const line = run.split(`
|
|
541
521
|
`).map((text) => text.trim()).find(Boolean);
|
|
542
|
-
|
|
543
|
-
return name.length > PipelineInputReader.#stepNameChars ? `${name.slice(0, PipelineInputReader.#stepNameChars - 1)}\u2026` : name;
|
|
522
|
+
return PipelineInputReader.clip(line ?? run.trim(), PipelineInputReader.#stepNameChars);
|
|
544
523
|
}
|
|
545
|
-
#
|
|
546
|
-
let
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
if (PipelineInputReader.#futureKeys.includes(key))
|
|
552
|
-
this.#report("pipeline.schema.unsupported", `\`${key}\` is not supported in schemaVersion 1`, `${path}.${key}`);
|
|
553
|
-
else
|
|
554
|
-
this.#report("pipeline.schema.unknownKey", `"${key}" is not a key here: ${keys.join(", ")}`, `${path}.${key}`);
|
|
555
|
-
}
|
|
556
|
-
return ok;
|
|
557
|
-
}
|
|
558
|
-
#object(value, path, keys) {
|
|
559
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
560
|
-
this.#report("pipeline.schema.invalid", "must be an object", path);
|
|
561
|
-
return null;
|
|
524
|
+
static #show(value) {
|
|
525
|
+
let text;
|
|
526
|
+
try {
|
|
527
|
+
text = JSON.stringify(value) ?? String(value);
|
|
528
|
+
} catch {
|
|
529
|
+
text = typeof value === "bigint" ? `${value}n` : Object.prototype.toString.call(value);
|
|
562
530
|
}
|
|
563
|
-
|
|
564
|
-
if (keys && !this.#keys(raw, path, keys))
|
|
565
|
-
return null;
|
|
566
|
-
return raw;
|
|
531
|
+
return PipelineInputReader.clip(text, PipelineInputReader.#shownChars);
|
|
567
532
|
}
|
|
568
533
|
#list(value, path) {
|
|
569
|
-
|
|
570
|
-
return [];
|
|
571
|
-
if (!Array.isArray(value)) {
|
|
572
|
-
this.#report("pipeline.schema.invalid", "must be a list", path);
|
|
573
|
-
return [];
|
|
574
|
-
}
|
|
575
|
-
return value;
|
|
534
|
+
return value === undefined ? [] : this.#read.list(value, path) ?? [];
|
|
576
535
|
}
|
|
577
536
|
#strings(value, path) {
|
|
578
537
|
return this.#list(value, path).flatMap((item, idx) => {
|
|
579
|
-
const text = this.#
|
|
538
|
+
const text = this.#read.string(item, `${path}[${idx}]`);
|
|
580
539
|
return text === null ? [] : [text];
|
|
581
540
|
});
|
|
582
541
|
}
|
|
583
|
-
#text(value, path, max) {
|
|
584
|
-
if (typeof value !== "string" || !value.trim()) {
|
|
585
|
-
this.#report("pipeline.schema.invalid", "must be a non-empty string", path);
|
|
586
|
-
return null;
|
|
587
|
-
}
|
|
588
|
-
if (value.length > max) {
|
|
589
|
-
this.#report("pipeline.schema.invalid", `is longer than ${max} characters`, path);
|
|
590
|
-
return null;
|
|
591
|
-
}
|
|
592
|
-
return value;
|
|
593
|
-
}
|
|
594
542
|
#boolean(value, path) {
|
|
595
|
-
|
|
596
|
-
return false;
|
|
597
|
-
if (typeof value === "boolean")
|
|
598
|
-
return value;
|
|
599
|
-
this.#report("pipeline.schema.invalid", "must be true or false", path);
|
|
600
|
-
return false;
|
|
601
|
-
}
|
|
602
|
-
#int(value, path, min, max) {
|
|
603
|
-
if (typeof value === "number" && Number.isInteger(value) && value >= min && value <= max)
|
|
604
|
-
return value;
|
|
605
|
-
this.#report("pipeline.schema.invalid", `must be a whole number between ${min} and ${max}`, path);
|
|
606
|
-
return null;
|
|
543
|
+
return value === undefined ? false : this.#read.boolean(value, path) ?? false;
|
|
607
544
|
}
|
|
608
|
-
#
|
|
609
|
-
if (
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
545
|
+
#problem(path, problem) {
|
|
546
|
+
if (problem.kind !== "unknownKey")
|
|
547
|
+
this.#report("pipeline.schema.invalid", PipelineInputReader.#reason(problem), path);
|
|
548
|
+
else if (PipelineInputReader.#futureKeys.includes(problem.key))
|
|
549
|
+
this.#report("pipeline.schema.unsupported", `\`${problem.key}\` is not supported in schemaVersion 1`, `${path}.${problem.key}`);
|
|
550
|
+
else
|
|
551
|
+
this.#report("pipeline.schema.unknownKey", `"${problem.key}" is not a key here: ${problem.keys.join(", ")}`, `${path}.${problem.key}`);
|
|
552
|
+
}
|
|
553
|
+
static #reason(problem) {
|
|
554
|
+
switch (problem.kind) {
|
|
555
|
+
case "object":
|
|
556
|
+
return "must be an object";
|
|
557
|
+
case "missingKey":
|
|
558
|
+
return `"${problem.key}" is missing`;
|
|
559
|
+
case "list":
|
|
560
|
+
return "must be a list";
|
|
561
|
+
case "string":
|
|
562
|
+
case "empty":
|
|
563
|
+
return "must be a non-empty string";
|
|
564
|
+
case "length":
|
|
565
|
+
return `is longer than ${problem.max} characters`;
|
|
566
|
+
case "boolean":
|
|
567
|
+
return "must be true or false";
|
|
568
|
+
case "int":
|
|
569
|
+
case "range":
|
|
570
|
+
return `must be a whole number between ${problem.min} and ${problem.max}`;
|
|
571
|
+
case "oneOf":
|
|
572
|
+
return `${PipelineInputReader.#show(problem.value)} is not one of ${problem.options.join(", ")}`;
|
|
573
|
+
}
|
|
613
574
|
}
|
|
614
575
|
#report(code, message, path) {
|
|
615
576
|
this.diagnostics.push({ severity: "error", code, message, file: this.#file, path });
|
|
@@ -640,6 +601,7 @@ class PipelineCompiler {
|
|
|
640
601
|
};
|
|
641
602
|
static #reservedPhaseId = /^(?:build|docker|deploy|ota|(?:before-build|after-build|after-docker|after-deploy|after-ota)(?:-\d+)?)$/;
|
|
642
603
|
static #actionStepNames = { build: "akan build", docker: "image", deploy: "deploy", ota: "ota" };
|
|
604
|
+
static #reasonChars = 1024;
|
|
643
605
|
static compile(input) {
|
|
644
606
|
return new PipelineCompiler(input).#compile();
|
|
645
607
|
}
|
|
@@ -654,13 +616,9 @@ class PipelineCompiler {
|
|
|
654
616
|
const { context, workspaceApps } = this.#input;
|
|
655
617
|
this.#checkContext();
|
|
656
618
|
const pipelineContext = {
|
|
657
|
-
|
|
658
|
-
env: context.env,
|
|
619
|
+
...context,
|
|
659
620
|
apps: [...context.apps],
|
|
660
|
-
workspaceApps: [...workspaceApps]
|
|
661
|
-
trigger: context.trigger,
|
|
662
|
-
buildNum: context.buildNum,
|
|
663
|
-
commit: context.commit
|
|
621
|
+
workspaceApps: [...workspaceApps]
|
|
664
622
|
};
|
|
665
623
|
const workspace = this.#readWorkspace(pipelineContext);
|
|
666
624
|
const lanes = new Map;
|
|
@@ -691,11 +649,18 @@ class PipelineCompiler {
|
|
|
691
649
|
try {
|
|
692
650
|
PipelinePlanSchema.parse(plan);
|
|
693
651
|
} catch (error) {
|
|
694
|
-
const message = error
|
|
652
|
+
const message = toError(error).message;
|
|
695
653
|
this.#error(message.includes("at most") ? "pipeline.limit.exceeded" : "pipeline.internal", message, null, null);
|
|
696
654
|
return this.#failed();
|
|
697
655
|
}
|
|
698
|
-
|
|
656
|
+
const result = { ok: true, plan, diagnostics: this.#diagnostics };
|
|
657
|
+
const bytes = new TextEncoder().encode(`${JSON.stringify(result, null, 2)}
|
|
658
|
+
`).length;
|
|
659
|
+
if (bytes > pipelinePlanLimits2.planBytes) {
|
|
660
|
+
this.#error("pipeline.limit.exceeded", `the plan is ${bytes} bytes as written, and an executor reads at most ${pipelinePlanLimits2.planBytes}`, null, null);
|
|
661
|
+
return this.#failed();
|
|
662
|
+
}
|
|
663
|
+
return result;
|
|
699
664
|
}
|
|
700
665
|
#checkContext() {
|
|
701
666
|
const { context, workspaceApps } = this.#input;
|
|
@@ -710,13 +675,13 @@ class PipelineCompiler {
|
|
|
710
675
|
}
|
|
711
676
|
#readWorkspace(context) {
|
|
712
677
|
const source = this.#input.workspace;
|
|
713
|
-
if (
|
|
678
|
+
if (source?.pipeline == null)
|
|
714
679
|
return null;
|
|
715
|
-
const
|
|
716
|
-
if (
|
|
680
|
+
const evaluated = this.#evaluate(source.pipeline, context, source.file);
|
|
681
|
+
if (!evaluated)
|
|
717
682
|
return null;
|
|
718
683
|
const reader = new PipelineInputReader(source.file);
|
|
719
|
-
const rule = reader.workspace(value);
|
|
684
|
+
const rule = reader.workspace(evaluated.value);
|
|
720
685
|
this.#diagnostics.push(...reader.diagnostics);
|
|
721
686
|
return rule;
|
|
722
687
|
}
|
|
@@ -725,25 +690,38 @@ class PipelineCompiler {
|
|
|
725
690
|
if (!source)
|
|
726
691
|
return null;
|
|
727
692
|
const appContext = { ...context, app };
|
|
728
|
-
const
|
|
729
|
-
if (
|
|
693
|
+
const evaluated = source.pipeline == null ? { value: defaultAppPipeline } : this.#evaluate(source.pipeline, appContext, source.file);
|
|
694
|
+
if (!evaluated)
|
|
730
695
|
return null;
|
|
731
696
|
const reader = new PipelineInputReader(source.file);
|
|
732
|
-
const rule = reader.app(value);
|
|
697
|
+
const rule = reader.app(evaluated.value);
|
|
733
698
|
this.#diagnostics.push(...reader.diagnostics);
|
|
734
699
|
return rule ? this.#lane(app, rule, source.file) : null;
|
|
735
700
|
}
|
|
736
701
|
#evaluate(config, context, file) {
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
702
|
+
let value = config;
|
|
703
|
+
if (typeof config === "function") {
|
|
704
|
+
if (this.#input.sampled)
|
|
705
|
+
this.#warning("pipeline.function.sampled", "a function form was checked against one sample context; other branches, triggers or apps may differ", file, "pipeline");
|
|
706
|
+
try {
|
|
707
|
+
value = config(context);
|
|
708
|
+
} catch (error) {
|
|
709
|
+
this.#error("pipeline.load.failed", `the pipeline function threw: ${toError(error).message}`, file, "pipeline");
|
|
710
|
+
return null;
|
|
711
|
+
}
|
|
712
|
+
if (value === undefined) {
|
|
713
|
+
this.#error("pipeline.schema.invalid", "the pipeline function returned nothing for this run: return a pipeline on every path", file, "pipeline");
|
|
714
|
+
return null;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
if (typeof value?.then === "function") {
|
|
718
|
+
Promise.resolve(value).catch(() => {
|
|
719
|
+
return;
|
|
720
|
+
});
|
|
721
|
+
this.#error("pipeline.schema.invalid", "is a Promise, which the planner cannot wait for: return the pipeline itself, not from an async function", file, "pipeline");
|
|
722
|
+
return null;
|
|
746
723
|
}
|
|
724
|
+
return { value };
|
|
747
725
|
}
|
|
748
726
|
#lane(app, rule, file) {
|
|
749
727
|
const lane = {
|
|
@@ -987,7 +965,7 @@ class PipelineCompiler {
|
|
|
987
965
|
maxParallel: stage.maxParallel,
|
|
988
966
|
failFast: false,
|
|
989
967
|
timeoutSeconds: stage.timeoutSeconds,
|
|
990
|
-
stages: apps.map((app) => this.#shellStage(copy, { id: `${stage.id}
|
|
968
|
+
stages: apps.map((app) => this.#shellStage(copy, { id: `${stage.id}.apps.${app}`, name: app, app, milestone, env }))
|
|
991
969
|
}
|
|
992
970
|
];
|
|
993
971
|
}
|
|
@@ -1034,10 +1012,11 @@ class PipelineCompiler {
|
|
|
1034
1012
|
}
|
|
1035
1013
|
#skipOf(when) {
|
|
1036
1014
|
const { branch, trigger } = this.#input.context;
|
|
1015
|
+
const skip = (reason) => ({ reason: PipelineInputReader.clip(reason, PipelineCompiler.#reasonChars) });
|
|
1037
1016
|
if (when.branch && !PipelinePattern2.matches(when.branch, branch))
|
|
1038
|
-
return
|
|
1017
|
+
return skip(`branch "${branch}" does not match ${when.branch.map((glob) => `"${glob}"`).join(", ")}`);
|
|
1039
1018
|
if (when.trigger && !when.trigger.includes(trigger))
|
|
1040
|
-
return
|
|
1019
|
+
return skip(`trigger "${trigger}" is not ${when.trigger.map((name) => `"${name}"`).join(" or ")}`);
|
|
1041
1020
|
return null;
|
|
1042
1021
|
}
|
|
1043
1022
|
#sources() {
|
|
@@ -1045,7 +1024,7 @@ class PipelineCompiler {
|
|
|
1045
1024
|
const sourceOf = (source) => ({
|
|
1046
1025
|
file: source.file,
|
|
1047
1026
|
sha256: source.sha256,
|
|
1048
|
-
declared: source.pipeline
|
|
1027
|
+
declared: source.pipeline != null
|
|
1049
1028
|
});
|
|
1050
1029
|
return {
|
|
1051
1030
|
workspace: workspace ? sourceOf(workspace) : null,
|
|
@@ -1071,6 +1050,7 @@ class PipelineCompiler {
|
|
|
1071
1050
|
// pkgs/@akanjs/devkit/pipeline/PipelineDefinition.ts
|
|
1072
1051
|
import { readFile } from "fs/promises";
|
|
1073
1052
|
import path from "path";
|
|
1053
|
+
import { toError as toError2 } from "akanjs/common";
|
|
1074
1054
|
class PipelineDefinition {
|
|
1075
1055
|
static async load(workspace, apps) {
|
|
1076
1056
|
const definition = new PipelineDefinition(workspace);
|
|
@@ -1099,7 +1079,7 @@ class PipelineDefinition {
|
|
|
1099
1079
|
});
|
|
1100
1080
|
}
|
|
1101
1081
|
async#load(apps) {
|
|
1102
|
-
this.workspaceApps = (await this.#workspace.getApps()).sort(
|
|
1082
|
+
this.workspaceApps = (await this.#workspace.getApps()).sort();
|
|
1103
1083
|
this.workspaceSource = await this.#loadWorkspace();
|
|
1104
1084
|
for (const app of new Set(apps)) {
|
|
1105
1085
|
if (!this.workspaceApps.includes(app))
|
|
@@ -1121,7 +1101,7 @@ class PipelineDefinition {
|
|
|
1121
1101
|
const targets = await resolveMobileTargets(AppExecutor.from(this.#workspace, app), "all");
|
|
1122
1102
|
return { targets: targets.map(({ name, config }) => ({ name, updates: !!config.updates })) };
|
|
1123
1103
|
} catch (error) {
|
|
1124
|
-
return { error: error
|
|
1104
|
+
return { error: toError2(error).message };
|
|
1125
1105
|
}
|
|
1126
1106
|
}
|
|
1127
1107
|
async#loadWorkspace() {
|
|
@@ -1155,7 +1135,7 @@ class PipelineDefinition {
|
|
|
1155
1135
|
this.diagnostics.push({
|
|
1156
1136
|
severity: "error",
|
|
1157
1137
|
code: "pipeline.load.failed",
|
|
1158
|
-
message: `could not load: ${error
|
|
1138
|
+
message: `could not load: ${toError2(error).message}`,
|
|
1159
1139
|
file,
|
|
1160
1140
|
path: null
|
|
1161
1141
|
});
|
|
@@ -1211,7 +1191,7 @@ class PipelineDryRun {
|
|
|
1211
1191
|
if (phase.stages.length === 1) {
|
|
1212
1192
|
const [stage] = phase.stages;
|
|
1213
1193
|
const command = stage?.steps.map((step) => step.kind === "sh" ? step.name : `action ${step.action}`).join(" \xB7 ");
|
|
1214
|
-
return [labels[0], command].filter((part) => part && part !==
|
|
1194
|
+
return [labels[0], command].filter((part) => part && part !== phase.name).join(" ");
|
|
1215
1195
|
}
|
|
1216
1196
|
return `\u2016 ${labels.join(" \xB7 ")}`;
|
|
1217
1197
|
}
|
|
@@ -1234,20 +1214,24 @@ class PipelineDryRun {
|
|
|
1234
1214
|
}
|
|
1235
1215
|
}
|
|
1236
1216
|
// pkgs/@akanjs/cli/pipeline/pipeline.script.ts
|
|
1237
|
-
import { Logger } from "akanjs/common";
|
|
1217
|
+
import { Logger, toError as toError3 } from "akanjs/common";
|
|
1238
1218
|
|
|
1239
1219
|
// pkgs/@akanjs/cli/pipeline/pipeline.runner.ts
|
|
1240
|
-
import { mkdir, open, rename } from "fs/promises";
|
|
1241
1220
|
import path2 from "path";
|
|
1242
1221
|
class PipelineRunner extends runner("pipeline") {
|
|
1222
|
+
static #flagChars = 1024;
|
|
1243
1223
|
async context(workspace, flags) {
|
|
1244
|
-
if (!Number.
|
|
1224
|
+
if (!Number.isSafeInteger(flags.buildNum) || flags.buildNum < 0)
|
|
1245
1225
|
throw new Error(`--build-num ${flags.buildNum} is not a whole number of 0 or more`);
|
|
1246
|
-
const
|
|
1226
|
+
for (const flag of ["branch", "env", "commit"])
|
|
1227
|
+
PipelineRunner.#assertFlag(flag, flags[flag]);
|
|
1228
|
+
const branch = flags.branch ?? await PipelineRunner.#git(workspace, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
1229
|
+
if (!branch)
|
|
1230
|
+
throw new Error("git names no branch here (a detached HEAD, or no checkout): name it with --branch");
|
|
1247
1231
|
return {
|
|
1248
1232
|
branch,
|
|
1249
1233
|
env: flags.env ?? branch,
|
|
1250
|
-
apps: flags.apps === null ? (await workspace.getApps()).sort(
|
|
1234
|
+
apps: flags.apps === null ? (await workspace.getApps()).sort() : flags.apps.split(",").map((app) => app.trim()).filter(Boolean),
|
|
1251
1235
|
trigger: flags.trigger,
|
|
1252
1236
|
buildNum: flags.buildNum,
|
|
1253
1237
|
commit: flags.commit ?? await PipelineRunner.#git(workspace, ["rev-parse", "HEAD"])
|
|
@@ -1257,17 +1241,16 @@ class PipelineRunner extends runner("pipeline") {
|
|
|
1257
1241
|
return (await PipelineDefinition.load(workspace, context.apps)).compile(context, { sampled });
|
|
1258
1242
|
}
|
|
1259
1243
|
async writeResult(out, result) {
|
|
1260
|
-
|
|
1261
|
-
await mkdir(path2.dirname(target), { recursive: true });
|
|
1262
|
-
const temp = `${target}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
1263
|
-
const handle = await open(temp, "wx");
|
|
1264
|
-
try {
|
|
1265
|
-
await handle.writeFile(`${JSON.stringify(result, null, 2)}
|
|
1244
|
+
await FileSys2.writeTextAtomic(path2.resolve(out), `${JSON.stringify(result, null, 2)}
|
|
1266
1245
|
`);
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1246
|
+
}
|
|
1247
|
+
static #assertFlag(flag, value) {
|
|
1248
|
+
if (value === null)
|
|
1249
|
+
return;
|
|
1250
|
+
if (!value.trim())
|
|
1251
|
+
throw new Error(`--${flag} is blank`);
|
|
1252
|
+
if (value.length > PipelineRunner.#flagChars)
|
|
1253
|
+
throw new Error(`--${flag} is longer than ${PipelineRunner.#flagChars} characters`);
|
|
1271
1254
|
}
|
|
1272
1255
|
static async#git(workspace, args) {
|
|
1273
1256
|
try {
|
|
@@ -1282,6 +1265,15 @@ class PipelineRunner extends runner("pipeline") {
|
|
|
1282
1265
|
// pkgs/@akanjs/cli/pipeline/pipeline.script.ts
|
|
1283
1266
|
class PipelineScript extends script("pipeline", [PipelineRunner]) {
|
|
1284
1267
|
async run(workspace, action, { flags, out, format }) {
|
|
1268
|
+
const json = format === "json" || action === "plan" && !out;
|
|
1269
|
+
const restoreLogs = json ? PipelineScript.#logToStderr() : null;
|
|
1270
|
+
try {
|
|
1271
|
+
await this.#run(workspace, action, { flags, out, json });
|
|
1272
|
+
} finally {
|
|
1273
|
+
restoreLogs?.();
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
async#run(workspace, action, { flags, out, json }) {
|
|
1285
1277
|
let result;
|
|
1286
1278
|
try {
|
|
1287
1279
|
const context = await this.pipelineRunner.context(workspace, flags);
|
|
@@ -1289,11 +1281,11 @@ class PipelineScript extends script("pipeline", [PipelineRunner]) {
|
|
|
1289
1281
|
if (out)
|
|
1290
1282
|
await this.pipelineRunner.writeResult(out, result);
|
|
1291
1283
|
} catch (error) {
|
|
1292
|
-
Logger.rawLog(`akan pipeline ${action}: ${error
|
|
1284
|
+
Logger.rawLog(`akan pipeline ${action}: ${toError3(error).message}`, undefined, "error");
|
|
1293
1285
|
process.exitCode = 2;
|
|
1294
1286
|
return;
|
|
1295
1287
|
}
|
|
1296
|
-
if (
|
|
1288
|
+
if (json)
|
|
1297
1289
|
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
1298
1290
|
`);
|
|
1299
1291
|
else
|
|
@@ -1301,6 +1293,15 @@ class PipelineScript extends script("pipeline", [PipelineRunner]) {
|
|
|
1301
1293
|
if (!result.ok)
|
|
1302
1294
|
process.exitCode = 1;
|
|
1303
1295
|
}
|
|
1296
|
+
static #logToStderr() {
|
|
1297
|
+
const consoleOutput = Logger.consoleOutput;
|
|
1298
|
+
Logger.consoleOutput = false;
|
|
1299
|
+
const removeSink = Logger.addSink((entry) => void process.stderr.write(entry.message), { minLevel: Logger.level });
|
|
1300
|
+
return () => {
|
|
1301
|
+
removeSink();
|
|
1302
|
+
Logger.consoleOutput = consoleOutput;
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1304
1305
|
static #summary(action, result, out) {
|
|
1305
1306
|
const head = result.ok ? `${action === "plan" ? "planned" : "valid"}: ${result.plan.phases.length} phases, ${result.plan.phases.reduce((sum, phase) => sum + phase.stages.length, 0)} stages` : `${action === "plan" ? "no plan" : "invalid"}: ${result.diagnostics.filter((diagnostic) => diagnostic.severity === "error").length} error(s)`;
|
|
1306
1307
|
return [`${head}${out ? ` \u2192 ${out}` : ""}`, ...PipelineDryRun.diagnostics(result.diagnostics)].join(`
|