@intentius/chant 0.124.23 → 0.124.24
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/ci-jobs.d.ts +41 -1
- package/dist/ci-jobs.d.ts.map +1 -1
- package/dist/ci-jobs.js +48 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +14 -0
- package/dist/cli/handlers/approve.d.ts.map +1 -1
- package/dist/cli/handlers/approve.js +9 -0
- package/dist/cli/handlers/emulator.d.ts +13 -2
- package/dist/cli/handlers/emulator.d.ts.map +1 -1
- package/dist/cli/handlers/emulator.js +63 -3
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/promote.js +7 -1
- package/dist/cli/handlers/run-wave.d.ts.map +1 -1
- package/dist/cli/handlers/run-wave.js +5 -4
- package/dist/cli/handlers/run.d.ts +4 -3
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/run.js +73 -9
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +17 -4
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +31 -0
- package/dist/cli/registry.d.ts +12 -0
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +45 -6
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/cli-support.js +97 -22
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/driver.js +25 -10
- package/dist/graph-detail.d.ts.map +1 -1
- package/dist/graph-detail.js +24 -2
- package/dist/lifecycle/git.d.ts +11 -0
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/git.js +62 -6
- package/dist/lifecycle/no-record.d.ts +59 -0
- package/dist/lifecycle/no-record.d.ts.map +1 -0
- package/dist/lifecycle/no-record.js +88 -0
- package/dist/op/activities/shell.d.ts.map +1 -1
- package/dist/op/activities/shell.js +30 -9
- package/dist/op/emulator-lifecycle.d.ts +23 -0
- package/dist/op/emulator-lifecycle.d.ts.map +1 -1
- package/dist/op/emulator-lifecycle.js +39 -2
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/gate.js +3 -0
- package/dist/op/index.d.ts +1 -1
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/index.js +1 -1
- package/dist/op/local-executor.d.ts +10 -0
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-executor.js +7 -2
- package/dist/op/op-waves-notify.d.ts +2 -0
- package/dist/op/op-waves-notify.d.ts.map +1 -1
- package/dist/op/op-waves-notify.js +9 -3
- package/dist/op/op-waves-run.d.ts +8 -2
- package/dist/op/op-waves-run.d.ts.map +1 -1
- package/dist/op/op-waves-run.js +36 -11
- package/dist/op/op-waves.d.ts +85 -1
- package/dist/op/op-waves.d.ts.map +1 -1
- package/dist/op/op-waves.js +153 -1
- package/dist/op/runtimes/local.d.ts.map +1 -1
- package/dist/op/runtimes/local.js +6 -0
- package/dist/op/step-console.d.ts +65 -0
- package/dist/op/step-console.d.ts.map +1 -0
- package/dist/op/step-console.js +140 -0
- package/dist/point-decision.d.ts.map +1 -1
- package/dist/point-decision.js +3 -0
- package/dist/wave-runtime.mjs +238 -238
- package/dist/workspace/compose-graph.d.ts +22 -3
- package/dist/workspace/compose-graph.d.ts.map +1 -1
- package/dist/workspace/compose-graph.js +18 -5
- package/dist/workspace/graph.schema.json +68 -1
- package/dist/workspace/nested-graph.d.ts +1 -1
- package/dist/workspace/nested-graph.d.ts.map +1 -1
- package/dist/workspace/nested-graph.js +5 -1
- package/dist/workspace/runs.d.ts.map +1 -1
- package/dist/workspace/runs.js +4 -2
- package/package.json +1 -1
- package/src/ci-jobs.ts +58 -1
- package/src/cli/commands/build.ts +18 -0
- package/src/cli/handlers/approve.ts +9 -0
- package/src/cli/handlers/emulator.test.ts +61 -1
- package/src/cli/handlers/emulator.ts +65 -4
- package/src/cli/handlers/promote.test.ts +7 -0
- package/src/cli/handlers/promote.ts +7 -1
- package/src/cli/handlers/run-no-record.e2e.test.ts +87 -0
- package/src/cli/handlers/run-wave.ts +5 -4
- package/src/cli/handlers/run.test.ts +126 -0
- package/src/cli/handlers/run.ts +69 -9
- package/src/cli/main.test.ts +24 -0
- package/src/cli/main.ts +17 -4
- package/src/cli/parse-args.ts +21 -0
- package/src/cli/registry.ts +12 -0
- package/src/components/cli-support.test.ts +75 -0
- package/src/components/cli-support.ts +130 -25
- package/src/components/driver.test.ts +28 -0
- package/src/components/driver.ts +23 -10
- package/src/components/generate-disabled.test.ts +56 -0
- package/src/graph-ci-edges.test.ts +50 -0
- package/src/graph-detail.ts +21 -2
- package/src/lifecycle/git.ts +61 -6
- package/src/lifecycle/no-record.test.ts +175 -0
- package/src/lifecycle/no-record.ts +108 -0
- package/src/op/activities/shell.ts +28 -6
- package/src/op/emulator-lifecycle.test.ts +24 -1
- package/src/op/emulator-lifecycle.ts +58 -2
- package/src/op/gate.ts +3 -0
- package/src/op/index.ts +1 -1
- package/src/op/local-executor.ts +20 -3
- package/src/op/op-waves-jobs.test.ts +213 -0
- package/src/op/op-waves-notify.ts +11 -5
- package/src/op/op-waves-run.ts +42 -11
- package/src/op/op-waves.ts +202 -2
- package/src/op/runtimes/local.ts +6 -0
- package/src/op/step-console.test.ts +150 -0
- package/src/op/step-console.ts +155 -0
- package/src/point-decision-bundle.test.ts +1 -0
- package/src/point-decision.ts +3 -0
- package/src/workspace/compose-graph.test.ts +55 -0
- package/src/workspace/compose-graph.ts +28 -6
- package/src/workspace/graph-contract.test.ts +26 -1
- package/src/workspace/graph.schema.json +68 -1
- package/src/workspace/nested-graph.test.ts +14 -0
- package/src/workspace/nested-graph.ts +7 -2
- package/src/workspace/runs.ts +4 -2
package/dist/op/op-waves.js
CHANGED
|
@@ -216,6 +216,8 @@ export function assertOpWavesSpec(spec) {
|
|
|
216
216
|
}
|
|
217
217
|
if (spec.neverOverMcp !== undefined && typeof spec.neverOverMcp !== "boolean")
|
|
218
218
|
throw new Error(`${where}: neverOverMcp must be true or false.`);
|
|
219
|
+
if (spec.runner !== undefined)
|
|
220
|
+
assertOpWaveRunner(where, "runner", spec.runner);
|
|
219
221
|
if (spec.notify !== undefined) {
|
|
220
222
|
const problem = notifyConfigProblem(spec.notify, "notify");
|
|
221
223
|
if (problem)
|
|
@@ -241,6 +243,12 @@ export function assertOpWavesSpec(spec) {
|
|
|
241
243
|
if (problems.length > 0)
|
|
242
244
|
throw new Error(`${where}: wave "${wave.name}": ${problems.join("; ")}.`);
|
|
243
245
|
}
|
|
246
|
+
if (wave.runner !== undefined)
|
|
247
|
+
assertOpWaveRunner(where, `wave "${wave.name}" runner`, wave.runner);
|
|
248
|
+
if (wave.idTokens !== undefined)
|
|
249
|
+
assertOpWaveIdTokens(where, `wave "${wave.name}" idTokens`, wave.idTokens);
|
|
250
|
+
if (wave.jobs !== undefined)
|
|
251
|
+
assertOpWaveJobGroups(where, wave);
|
|
244
252
|
if (wave.discover !== undefined && typeof wave.discover !== "boolean")
|
|
245
253
|
throw new Error(`${where}: wave "${wave.name}" has discover ${JSON.stringify(wave.discover)}; use true or false.`);
|
|
246
254
|
if (wave.rest !== undefined && typeof wave.rest !== "boolean")
|
|
@@ -282,7 +290,127 @@ export function assertOpWavesSpec(spec) {
|
|
|
282
290
|
if (shares > wave.runs.length) {
|
|
283
291
|
throw new Error(`${where}: wave "${wave.name}" has ${shares} shares for ${wave.runs.length} runs, so a share would apply nothing.`);
|
|
284
292
|
}
|
|
293
|
+
if (wave.jobs !== undefined) {
|
|
294
|
+
const problem = opWaveGroupsProblem(wave, wave.runs.map((r) => r.target));
|
|
295
|
+
if (problem)
|
|
296
|
+
throw new Error(`${where}: ${problem}.`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
301
|
+
/** Refuse a `runner` no forge could read (#3914). */
|
|
302
|
+
function assertOpWaveRunner(where, field, runner) {
|
|
303
|
+
const labels = (value) => Array.isArray(value) && value.length > 0 && value.every((l) => typeof l === "string" && l.trim() !== "");
|
|
304
|
+
if (typeof runner === "string" && runner.trim() !== "")
|
|
305
|
+
return;
|
|
306
|
+
if (labels(runner))
|
|
307
|
+
return;
|
|
308
|
+
if (runner !== null && typeof runner === "object" && !Array.isArray(runner)) {
|
|
309
|
+
const { group, labels: l, ...rest } = runner;
|
|
310
|
+
const extra = Object.keys(rest)[0];
|
|
311
|
+
if (extra !== undefined)
|
|
312
|
+
throw new Error(`${where}: ${field}.${extra} is not a setting; a runner group is { "group": "<name>", "labels"?: [...] }.`);
|
|
313
|
+
if (typeof group === "string" && group.trim() !== "" && (l === undefined || labels(l)))
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
throw new Error(`${where}: ${field} must be a runner label, a non-empty list of labels, or { "group": "<name>", "labels"?: [...] } (GitHub only).`);
|
|
317
|
+
}
|
|
318
|
+
/** Refuse `idTokens` that are not `{ <variable>: <audience> }` (#3914). */
|
|
319
|
+
function assertOpWaveIdTokens(where, field, idTokens) {
|
|
320
|
+
if (idTokens === null || typeof idTokens !== "object" || Array.isArray(idTokens) || Object.keys(idTokens).length === 0) {
|
|
321
|
+
throw new Error(`${where}: ${field} must be { "<variable>": "<audience>" }, with at least one token.`);
|
|
285
322
|
}
|
|
323
|
+
for (const [name, aud] of Object.entries(idTokens)) {
|
|
324
|
+
if (!ENV_NAME.test(name))
|
|
325
|
+
throw new Error(`${where}: ${field} names "${name}", which is not a variable name (letters, digits and "_", not starting with a digit).`);
|
|
326
|
+
if (typeof aud !== "string" || aud.trim() === "")
|
|
327
|
+
throw new Error(`${where}: ${field}.${name} must be an audience, a non-empty string.`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/** Refuse a wave's `jobs` no pipeline could render (#3914). Which runs each group holds is {@link opWaveGroupsProblem}'s. */
|
|
331
|
+
function assertOpWaveJobGroups(where, wave) {
|
|
332
|
+
const at = `wave "${wave.name}"`;
|
|
333
|
+
if (!Array.isArray(wave.jobs) || wave.jobs.length === 0)
|
|
334
|
+
throw new Error(`${where}: ${at} jobs must be a non-empty list of job groups.`);
|
|
335
|
+
if ((wave.shares ?? 1) > 1)
|
|
336
|
+
throw new Error(`${where}: ${at} has jobs and shares; split each job group with its own shares.`);
|
|
337
|
+
if (wave.rest)
|
|
338
|
+
throw new Error(`${where}: ${at} has jobs and rest; the waves after it run in its one job, so its apply cannot be split into groups.`);
|
|
339
|
+
const names = new Set();
|
|
340
|
+
wave.jobs.forEach((group, i) => {
|
|
341
|
+
if (group === null || typeof group !== "object")
|
|
342
|
+
throw new Error(`${where}: ${at} jobs[${i}] must be { "name", "runs", ... }.`);
|
|
343
|
+
const field = `${at} jobs[${i}]${typeof group.name === "string" ? ` (${group.name})` : ""}`;
|
|
344
|
+
if (typeof group.name !== "string" || !NAME.test(group.name))
|
|
345
|
+
throw new Error(`${where}: ${field} must be named with lowercase letters, digits, "-" and "_".`);
|
|
346
|
+
if (group.name === "decide" || /^share-\d+$/.test(group.name))
|
|
347
|
+
throw new Error(`${where}: ${field} is named like the wave's own jobs (decide, share-<i>); name it another way.`);
|
|
348
|
+
if (names.has(group.name))
|
|
349
|
+
throw new Error(`${where}: ${at} names job group "${group.name}" twice.`);
|
|
350
|
+
names.add(group.name);
|
|
351
|
+
const known = new Set(["name", "runs", "shares", "runner", "environment", "variables", "setup", "idTokens"]);
|
|
352
|
+
const extra = Object.keys(group).find((key) => !known.has(key));
|
|
353
|
+
if (extra !== undefined)
|
|
354
|
+
throw new Error(`${where}: ${field}.${extra} is not a setting; a job group takes ${[...known].join(", ")}.`);
|
|
355
|
+
if (!Array.isArray(group.runs) || group.runs.length === 0 || !group.runs.every((r) => typeof r === "string" && r.trim() !== "")) {
|
|
356
|
+
throw new Error(`${where}: ${field} runs must be a non-empty list of targets or patterns.`);
|
|
357
|
+
}
|
|
358
|
+
const shares = group.shares ?? 1;
|
|
359
|
+
if (!Number.isInteger(shares) || shares < 1)
|
|
360
|
+
throw new Error(`${where}: ${field} has shares ${group.shares}; use a whole number from 1.`);
|
|
361
|
+
if (group.runner !== undefined)
|
|
362
|
+
assertOpWaveRunner(where, `${field} runner`, group.runner);
|
|
363
|
+
if (group.idTokens !== undefined)
|
|
364
|
+
assertOpWaveIdTokens(where, `${field} idTokens`, group.idTokens);
|
|
365
|
+
if (group.variables !== undefined && (group.variables === null || typeof group.variables !== "object" || Array.isArray(group.variables) || !Object.values(group.variables).every((v) => typeof v === "string"))) {
|
|
366
|
+
throw new Error(`${where}: ${field} variables must be { "<name>": "<value>" }.`);
|
|
367
|
+
}
|
|
368
|
+
if (group.setup !== undefined && !Array.isArray(group.setup))
|
|
369
|
+
throw new Error(`${where}: ${field} setup must be a list of steps.`);
|
|
370
|
+
if (group.environment !== undefined && (group.environment === null || typeof group.environment !== "object" || typeof group.environment.name !== "string")) {
|
|
371
|
+
throw new Error(`${where}: ${field} environment must be { "name", "url"? }.`);
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* True when `target` matches a job group's `runs` entry (#3914): the target
|
|
377
|
+
* itself, or a pattern where `*` matches within one `/` segment and `**`
|
|
378
|
+
* across segments.
|
|
379
|
+
*/
|
|
380
|
+
export function opWaveRunMatches(pattern, target) {
|
|
381
|
+
if (!pattern.includes("*"))
|
|
382
|
+
return pattern === target;
|
|
383
|
+
let re = "";
|
|
384
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
385
|
+
const c = pattern[i];
|
|
386
|
+
if (c === "*" && pattern[i + 1] === "*") {
|
|
387
|
+
re += ".*";
|
|
388
|
+
i++;
|
|
389
|
+
}
|
|
390
|
+
else if (c === "*")
|
|
391
|
+
re += "[^/]*";
|
|
392
|
+
else
|
|
393
|
+
re += c.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
394
|
+
}
|
|
395
|
+
return new RegExp(`^${re}$`).test(target);
|
|
396
|
+
}
|
|
397
|
+
/** The runs of `runs` that job group `group` holds (#3914). */
|
|
398
|
+
export function opWaveGroupRuns(group, runs) {
|
|
399
|
+
return runs.filter((run) => group.runs.some((pattern) => opWaveRunMatches(pattern, run.target)));
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* What is wrong with how a wave's job groups hold `targets` (#3914): a run in
|
|
403
|
+
* two groups, or in none. Undefined when each run is in exactly one.
|
|
404
|
+
*/
|
|
405
|
+
export function opWaveGroupsProblem(wave, targets) {
|
|
406
|
+
for (const target of targets) {
|
|
407
|
+
const holders = (wave.jobs ?? []).filter((group) => group.runs.some((pattern) => opWaveRunMatches(pattern, target))).map((g) => `"${g.name}"`);
|
|
408
|
+
if (holders.length > 1)
|
|
409
|
+
return `wave "${wave.name}" jobs: run "${target}" is in job groups ${holders.join(" and ")}; each run is in exactly one`;
|
|
410
|
+
if (holders.length === 0)
|
|
411
|
+
return `wave "${wave.name}" jobs: run "${target}" is in no job group; each run is in exactly one`;
|
|
412
|
+
}
|
|
413
|
+
return undefined;
|
|
286
414
|
}
|
|
287
415
|
function assertArgv(where, field, argv) {
|
|
288
416
|
if (!Array.isArray(argv) || argv.length === 0 || !argv.every((part) => typeof part === "string") || argv[0] === "") {
|
|
@@ -381,7 +509,7 @@ export function opWaveJobs(spec, specFile) {
|
|
|
381
509
|
const command = ["chant", "run", "wave", "--spec", specFile, "--wave", String(k), ...(spec.base ? ["--base", spec.base] : [])];
|
|
382
510
|
const prefix = `wave-${k}-${wave.name}`;
|
|
383
511
|
const shares = wave.shares ?? 1;
|
|
384
|
-
if (shares === 1) {
|
|
512
|
+
if (shares === 1 && wave.jobs === undefined) {
|
|
385
513
|
jobs.push({ jobName: prefix, wave: k, waveName: wave.name, kind: "wave", needs: previous, command });
|
|
386
514
|
previous = [prefix];
|
|
387
515
|
return;
|
|
@@ -390,6 +518,26 @@ export function opWaveJobs(spec, specFile) {
|
|
|
390
518
|
const decide = `${prefix}-decide`;
|
|
391
519
|
jobs.push({ jobName: decide, wave: k, waveName: wave.name, kind: "decide", needs: previous, command: [...command, "--decide"], record });
|
|
392
520
|
const shareJobs = [];
|
|
521
|
+
if (wave.jobs !== undefined) {
|
|
522
|
+
// #3914: a job per group, or per share of a group, each applying its runs under the deciding job's decision.
|
|
523
|
+
for (const group of wave.jobs) {
|
|
524
|
+
const groupShares = group.shares ?? 1;
|
|
525
|
+
const groupCommand = [...command, "--group", group.name];
|
|
526
|
+
if (groupShares === 1) {
|
|
527
|
+
const jobName = `${prefix}-${group.name}`;
|
|
528
|
+
shareJobs.push(jobName);
|
|
529
|
+
jobs.push({ jobName, wave: k, waveName: wave.name, kind: "share", group: group.name, needs: [decide], command: groupCommand, record });
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
for (let share = 1; share <= groupShares; share++) {
|
|
533
|
+
const jobName = `${prefix}-${group.name}-share-${share}`;
|
|
534
|
+
shareJobs.push(jobName);
|
|
535
|
+
jobs.push({ jobName, wave: k, waveName: wave.name, kind: "share", group: group.name, share, needs: [decide], command: [...groupCommand, "--share", String(share)], record });
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
previous = shareJobs;
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
393
541
|
for (let share = 1; share <= shares; share++) {
|
|
394
542
|
const jobName = `${prefix}-share-${share}`;
|
|
395
543
|
shareJobs.push(jobName);
|
|
@@ -399,6 +547,10 @@ export function opWaveJobs(spec, specFile) {
|
|
|
399
547
|
});
|
|
400
548
|
return jobs;
|
|
401
549
|
}
|
|
550
|
+
/** The job group a job applies (#3914), or undefined for a wave's own jobs. */
|
|
551
|
+
export function opWaveGroupOf(spec, job) {
|
|
552
|
+
return job.group === undefined ? undefined : spec.waves[job.wave - 1]?.jobs?.find((g) => g.name === job.group);
|
|
553
|
+
}
|
|
402
554
|
/** The wave a job belongs to. */
|
|
403
555
|
export function opWaveOf(spec, job) {
|
|
404
556
|
return spec.waves[job.wave - 1];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../../../src/op/runtimes/local.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;
|
|
1
|
+
{"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../../../src/op/runtimes/local.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAWH,OAAO,EAML,KAAK,iBAAiB,EACvB,MAAM,YAAY,CAAC;AA0CpB;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,GAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,iBAAiB,CA2J3F"}
|
|
@@ -21,6 +21,7 @@ import { lexiconNames } from "../../lexicon-module.js";
|
|
|
21
21
|
import { loadChantConfig } from "../../config.js";
|
|
22
22
|
import { loadActivities, loadProfiles } from "../activity-registry.js";
|
|
23
23
|
import { runOpLocally, OpRunFailure } from "../local-executor.js";
|
|
24
|
+
import { stepHeartbeatMs } from "../step-console.js";
|
|
24
25
|
import { runComponents } from "../../components/cli-support.js";
|
|
25
26
|
import { discoverOps } from "../discover.js";
|
|
26
27
|
import { readRunLedger, runEnvOf, DEFAULT_RUN_ENV } from "../../lifecycle/run-ledger.js";
|
|
@@ -142,6 +143,11 @@ export function createLocalOpRuntime(opts = {}) {
|
|
|
142
143
|
onLedgerError: (err) => process.stderr.write(`warning: run "${runId}" of "${op.name}" finished, but its record could not be ` +
|
|
143
144
|
`appended to the run ledger: ${err instanceof Error ? err.message : String(err)}\n`),
|
|
144
145
|
...(startOpts.progress ? { onRecord: startOpts.progress } : {}),
|
|
146
|
+
// #3956: a running step's output, and a still-running line while
|
|
147
|
+
// it is quiet, on stderr as they happen; stdout stays the
|
|
148
|
+
// `--json` record's and `--progress-json`'s.
|
|
149
|
+
stepOutput: (line) => process.stderr.write(`${line}\n`),
|
|
150
|
+
stepHeartbeatMs: stepHeartbeatMs(process.env, (message) => process.stderr.write(`warning: ${message}\n`)),
|
|
145
151
|
...(startOpts.work ? { work: startOpts.work } : {}),
|
|
146
152
|
onWorkLeaseWarning: (message) => process.stderr.write(`warning: "${op.name}" could not renew its work lease, retrying at the next beat: ${message}\n`),
|
|
147
153
|
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A running step's console (#3956): its output as it arrives, and a
|
|
3
|
+
* "still running" line while it is quiet.
|
|
4
|
+
*
|
|
5
|
+
* The run's own render (`./local-output.ts`) is printed once the run
|
|
6
|
+
* settles, so before this a 15-minute step printed nothing for 15 minutes and
|
|
7
|
+
* a watchdog reading the output killed a healthy run. When the caller hands
|
|
8
|
+
* the executor a writer (`RunOpOptions.stepOutput`, which the local runtime
|
|
9
|
+
* passes, writing to stderr), each activity step runs with a console:
|
|
10
|
+
*
|
|
11
|
+
* - an activity that runs a process (`shellCmd`) passes each line the process
|
|
12
|
+
* writes, stdout and stderr alike, through {@link stepOutputLine}, and it is
|
|
13
|
+
* printed at once as `[<phase>] <step> | <line>`;
|
|
14
|
+
* - a step that prints nothing for `heartbeatMs` (an activity that only
|
|
15
|
+
* returns a value, or a command that is quiet) gets
|
|
16
|
+
* `[<phase>] <step>: still running after <n>s` each time that much passes.
|
|
17
|
+
*
|
|
18
|
+
* `<step>` is the step's id when it has one, else its call as the run's
|
|
19
|
+
* render names it, `fn(arg=value, ...)`, cut to a readable length. Without a
|
|
20
|
+
* writer there is no console, and `shellCmd` prints a command's stderr when it
|
|
21
|
+
* ends, as it did before.
|
|
22
|
+
*/
|
|
23
|
+
/** How long a step may be quiet before the runner says it is still running. */
|
|
24
|
+
export declare const DEFAULT_STEP_HEARTBEAT_MS = 60000;
|
|
25
|
+
/** The environment variable that sets the interval: a duration ("30s", "5m"), or `0` / `off`. */
|
|
26
|
+
export declare const STEP_HEARTBEAT_ENV = "CHANT_STEP_HEARTBEAT";
|
|
27
|
+
/** Where a run's step lines go, and how often a quiet step says it is still running. */
|
|
28
|
+
export interface RunConsole {
|
|
29
|
+
write: (line: string) => void;
|
|
30
|
+
heartbeatMs?: number;
|
|
31
|
+
}
|
|
32
|
+
/** Run `fn` as a run whose steps print through `run`. Without one, steps get no console. */
|
|
33
|
+
export declare function withRunConsole<T>(run: RunConsole | undefined, fn: () => Promise<T>): Promise<T>;
|
|
34
|
+
/** The name a step's lines carry: its id, else `fn(args)` on one line, cut to {@link MAX_LABEL}. */
|
|
35
|
+
export declare function stepLabel(fn: string, args: Record<string, unknown>, id?: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* Run `fn` with a console for one step, when the run has one
|
|
38
|
+
* ({@link withRunConsole}). The heartbeat timer is stopped when `fn`
|
|
39
|
+
* settles; it never holds the process open on its own.
|
|
40
|
+
*/
|
|
41
|
+
export declare function withStepConsole<T>(opts: {
|
|
42
|
+
phase: string;
|
|
43
|
+
label: string;
|
|
44
|
+
}, fn: () => Promise<T>): Promise<T>;
|
|
45
|
+
/** Whether the calling activity runs under a step console, so its output is printed as it arrives. */
|
|
46
|
+
export declare function hasStepConsole(): boolean;
|
|
47
|
+
/** Print one line of the calling step's output. A no-op outside a step console. */
|
|
48
|
+
export declare function stepOutputLine(text: string): void;
|
|
49
|
+
/**
|
|
50
|
+
* A sink for a stream's chunks that prints whole lines to the calling step's
|
|
51
|
+
* console as they complete; `end()` prints what is left. The console is the
|
|
52
|
+
* one current when the splitter is made, since a stream's `data` events need
|
|
53
|
+
* not run in the step's async context.
|
|
54
|
+
*/
|
|
55
|
+
export declare function stepLineSplitter(): {
|
|
56
|
+
push(chunk: string | Buffer): void;
|
|
57
|
+
end(): void;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* The heartbeat interval `CHANT_STEP_HEARTBEAT` sets, or the default. `0` and
|
|
61
|
+
* `off` turn it off. A value that is not a duration falls back to the default
|
|
62
|
+
* and says so through `warn`.
|
|
63
|
+
*/
|
|
64
|
+
export declare function stepHeartbeatMs(env?: NodeJS.ProcessEnv, warn?: (message: string) => void): number;
|
|
65
|
+
//# sourceMappingURL=step-console.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-console.d.ts","sourceRoot":"","sources":["../../src/op/step-console.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAKH,+EAA+E;AAC/E,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAEhD,iGAAiG;AACjG,eAAO,MAAM,kBAAkB,yBAAyB,CAAC;AASzD,wFAAwF;AACxF,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAKD,4FAA4F;AAC5F,wBAAgB,cAAc,CAAC,CAAC,EAAE,GAAG,EAAE,UAAU,GAAG,SAAS,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAE/F;AAQD,oGAAoG;AACpG,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAIxF;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,CAAC,EAAE,IAAI,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CA6BjH;AAED,sGAAsG;AACtG,wBAAgB,cAAc,IAAI,OAAO,CAExC;AAED,mFAAmF;AACnF,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAEjD;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,IAAI;IAAE,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAAC,GAAG,IAAI,IAAI,CAAA;CAAE,CAmBtF;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,EAAE,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,CAU9G"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A running step's console (#3956): its output as it arrives, and a
|
|
3
|
+
* "still running" line while it is quiet.
|
|
4
|
+
*
|
|
5
|
+
* The run's own render (`./local-output.ts`) is printed once the run
|
|
6
|
+
* settles, so before this a 15-minute step printed nothing for 15 minutes and
|
|
7
|
+
* a watchdog reading the output killed a healthy run. When the caller hands
|
|
8
|
+
* the executor a writer (`RunOpOptions.stepOutput`, which the local runtime
|
|
9
|
+
* passes, writing to stderr), each activity step runs with a console:
|
|
10
|
+
*
|
|
11
|
+
* - an activity that runs a process (`shellCmd`) passes each line the process
|
|
12
|
+
* writes, stdout and stderr alike, through {@link stepOutputLine}, and it is
|
|
13
|
+
* printed at once as `[<phase>] <step> | <line>`;
|
|
14
|
+
* - a step that prints nothing for `heartbeatMs` (an activity that only
|
|
15
|
+
* returns a value, or a command that is quiet) gets
|
|
16
|
+
* `[<phase>] <step>: still running after <n>s` each time that much passes.
|
|
17
|
+
*
|
|
18
|
+
* `<step>` is the step's id when it has one, else its call as the run's
|
|
19
|
+
* render names it, `fn(arg=value, ...)`, cut to a readable length. Without a
|
|
20
|
+
* writer there is no console, and `shellCmd` prints a command's stderr when it
|
|
21
|
+
* ends, as it did before.
|
|
22
|
+
*/
|
|
23
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
24
|
+
import { parseDuration } from "./duration.js";
|
|
25
|
+
/** How long a step may be quiet before the runner says it is still running. */
|
|
26
|
+
export const DEFAULT_STEP_HEARTBEAT_MS = 60000;
|
|
27
|
+
/** The environment variable that sets the interval: a duration ("30s", "5m"), or `0` / `off`. */
|
|
28
|
+
export const STEP_HEARTBEAT_ENV = "CHANT_STEP_HEARTBEAT";
|
|
29
|
+
/** The longest step label a line carries before it is cut. */
|
|
30
|
+
const MAX_LABEL = 60;
|
|
31
|
+
const store = new AsyncLocalStorage();
|
|
32
|
+
const runStore = new AsyncLocalStorage();
|
|
33
|
+
/** Run `fn` as a run whose steps print through `run`. Without one, steps get no console. */
|
|
34
|
+
export function withRunConsole(run, fn) {
|
|
35
|
+
return run ? runStore.run(run, fn) : fn();
|
|
36
|
+
}
|
|
37
|
+
function formatArgs(args) {
|
|
38
|
+
return Object.entries(args)
|
|
39
|
+
.map(([k, v]) => `${k}=${typeof v === "string" ? v : JSON.stringify(v)}`)
|
|
40
|
+
.join(", ");
|
|
41
|
+
}
|
|
42
|
+
/** The name a step's lines carry: its id, else `fn(args)` on one line, cut to {@link MAX_LABEL}. */
|
|
43
|
+
export function stepLabel(fn, args, id) {
|
|
44
|
+
if (id)
|
|
45
|
+
return id;
|
|
46
|
+
const call = `${fn}(${formatArgs(args)})`.replace(/\s+/g, " ");
|
|
47
|
+
return call.length > MAX_LABEL ? `${call.slice(0, MAX_LABEL - 4)}...)` : call;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Run `fn` with a console for one step, when the run has one
|
|
51
|
+
* ({@link withRunConsole}). The heartbeat timer is stopped when `fn`
|
|
52
|
+
* settles; it never holds the process open on its own.
|
|
53
|
+
*/
|
|
54
|
+
export async function withStepConsole(opts, fn) {
|
|
55
|
+
const run = runStore.getStore();
|
|
56
|
+
if (!run)
|
|
57
|
+
return fn();
|
|
58
|
+
const { write } = run;
|
|
59
|
+
const prefix = `[${opts.phase}] ${opts.label}`;
|
|
60
|
+
const started = Date.now();
|
|
61
|
+
let spoke = false;
|
|
62
|
+
const sink = {
|
|
63
|
+
line(text) {
|
|
64
|
+
spoke = true;
|
|
65
|
+
write(`${prefix} | ${text}`);
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
const every = run.heartbeatMs ?? DEFAULT_STEP_HEARTBEAT_MS;
|
|
69
|
+
const timer = every > 0
|
|
70
|
+
? setInterval(() => {
|
|
71
|
+
if (spoke) {
|
|
72
|
+
spoke = false;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
write(`${prefix}: still running after ${Math.round((Date.now() - started) / 1000)}s`);
|
|
76
|
+
}, every)
|
|
77
|
+
: undefined;
|
|
78
|
+
timer?.unref();
|
|
79
|
+
try {
|
|
80
|
+
return await store.run(sink, fn);
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
if (timer)
|
|
84
|
+
clearInterval(timer);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/** Whether the calling activity runs under a step console, so its output is printed as it arrives. */
|
|
88
|
+
export function hasStepConsole() {
|
|
89
|
+
return store.getStore() !== undefined;
|
|
90
|
+
}
|
|
91
|
+
/** Print one line of the calling step's output. A no-op outside a step console. */
|
|
92
|
+
export function stepOutputLine(text) {
|
|
93
|
+
store.getStore()?.line(text);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* A sink for a stream's chunks that prints whole lines to the calling step's
|
|
97
|
+
* console as they complete; `end()` prints what is left. The console is the
|
|
98
|
+
* one current when the splitter is made, since a stream's `data` events need
|
|
99
|
+
* not run in the step's async context.
|
|
100
|
+
*/
|
|
101
|
+
export function stepLineSplitter() {
|
|
102
|
+
const sink = store.getStore();
|
|
103
|
+
let pending = "";
|
|
104
|
+
const emit = (line) => sink?.line(line.endsWith("\r") ? line.slice(0, -1) : line);
|
|
105
|
+
return {
|
|
106
|
+
push(chunk) {
|
|
107
|
+
pending += typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
108
|
+
let at = pending.indexOf("\n");
|
|
109
|
+
while (at >= 0) {
|
|
110
|
+
emit(pending.slice(0, at));
|
|
111
|
+
pending = pending.slice(at + 1);
|
|
112
|
+
at = pending.indexOf("\n");
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
end() {
|
|
116
|
+
if (pending !== "")
|
|
117
|
+
emit(pending);
|
|
118
|
+
pending = "";
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* The heartbeat interval `CHANT_STEP_HEARTBEAT` sets, or the default. `0` and
|
|
124
|
+
* `off` turn it off. A value that is not a duration falls back to the default
|
|
125
|
+
* and says so through `warn`.
|
|
126
|
+
*/
|
|
127
|
+
export function stepHeartbeatMs(env = process.env, warn) {
|
|
128
|
+
const raw = env[STEP_HEARTBEAT_ENV]?.trim();
|
|
129
|
+
if (!raw)
|
|
130
|
+
return DEFAULT_STEP_HEARTBEAT_MS;
|
|
131
|
+
if (raw === "0" || raw.toLowerCase() === "off")
|
|
132
|
+
return 0;
|
|
133
|
+
try {
|
|
134
|
+
return parseDuration(raw);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
warn?.(`${STEP_HEARTBEAT_ENV}="${raw}" is not a duration (such as 30s or 5m) or off; using ${DEFAULT_STEP_HEARTBEAT_MS / 1000}s`);
|
|
138
|
+
return DEFAULT_STEP_HEARTBEAT_MS;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"point-decision.d.ts","sourceRoot":"","sources":["../src/point-decision.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAMH,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAoB,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAExH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EAAgF,KAAK,SAAS,EAAE,KAAK,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACzK,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAyB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"point-decision.d.ts","sourceRoot":"","sources":["../src/point-decision.ts"],"names":[],"mappings":"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"}
|
package/dist/point-decision.js
CHANGED
|
@@ -33,6 +33,7 @@ import { samePlanDigest } from "./lifecycle/plan-digest.js";
|
|
|
33
33
|
import { parseDuration } from "./op/duration.js";
|
|
34
34
|
import { DEFAULT_GATE_POINT_PASS, defaultGatePointStore, gatePointInputs, gateSubject } from "./op/gate-point.js";
|
|
35
35
|
import { resolveGateRunLocator } from "./op/gate-run-locator.js";
|
|
36
|
+
import { refuseGateWithoutRecording } from "./lifecycle/no-record.js";
|
|
36
37
|
export { DEFAULT_GATE_POINT_PASS, GATE_POINT_INPUTS, defaultGatePointStore, gatePointInputs, gatePointOf, gatePointProblems, gateSubject, parseGateSubject, } from "./op/gate-point.js";
|
|
37
38
|
export { isGateRunLocator, resolveGateRunLocator } from "./op/gate-run-locator.js";
|
|
38
39
|
/** Who a resolution written for an answer is by: the people who answered, or the decider that did. */
|
|
@@ -41,6 +42,8 @@ function resolvedByOf(q) {
|
|
|
41
42
|
}
|
|
42
43
|
/** Decide a gate that asks a point: ask it, then record the pending fact or the resolution that cites the answer. */
|
|
43
44
|
export async function evaluatePointGate(port, asker, input) {
|
|
45
|
+
// #3955: refused before the point is asked, which would itself write to the ledger.
|
|
46
|
+
refuseGateWithoutRecording(input.op, input.gate);
|
|
44
47
|
const now = input.now ?? new Date().toISOString();
|
|
45
48
|
const facts = { component: input.op, name: input.gate, env: input.env ?? null, planDigest: input.planDigest ?? null };
|
|
46
49
|
const q = await asker.ask({ point: input.point.name, inputs: input.inputs ?? {}, facts, subject: gateSubject(input.op, input.gate, input.env), ...(input.point.store ? { store: input.point.store } : {}) });
|