@intentius/plywood 0.3.2 → 0.3.5
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/image-digests.json +4 -0
- package/dist/plywood.mjs +446 -1036
- package/dist/plywood.mjs.map +4 -4
- package/package.json +6 -6
- package/plywood.schema.json +3 -3
- package/templates/agent-guard/bin/plunge +0 -27
- package/templates/agent-guard/bin/pulumi +0 -27
- package/templates/agent-guard/plywood-guard.mjs +0 -60
package/dist/plywood.mjs
CHANGED
|
@@ -16,6 +16,14 @@ var __export = (target, all) => {
|
|
|
16
16
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
+
// <define:__PLYWOOD_IMAGE_DIGESTS__>
|
|
20
|
+
var define_PLYWOOD_IMAGE_DIGESTS_default;
|
|
21
|
+
var init_define_PLYWOOD_IMAGE_DIGESTS = __esm({
|
|
22
|
+
"<define:__PLYWOOD_IMAGE_DIGESTS__>"() {
|
|
23
|
+
define_PLYWOOD_IMAGE_DIGESTS_default = { "ghcr.io/intentius/plywood-pulumi:0.3.5-pulumi3.272.0": "sha256:a1288d42eaba8ec556ed3ea40a24cfda94a8413576b7f0129b30ab7620639f07", "ghcr.io/intentius/plywood-plunge:0.3.5-plunge0.2.0": "sha256:4384ee4b92c8b0cb00c4833bc0118a820a0c3620ad78dc50d9e21fb0d9fff370" };
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
19
27
|
// src/config/types.ts
|
|
20
28
|
function isObject(v) {
|
|
21
29
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
@@ -45,6 +53,7 @@ var BACKEND_SCHEMA;
|
|
|
45
53
|
var init_types = __esm({
|
|
46
54
|
"src/config/types.ts"() {
|
|
47
55
|
"use strict";
|
|
56
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
48
57
|
BACKEND_SCHEMA = {
|
|
49
58
|
type: "string",
|
|
50
59
|
description: "The backend URL. A plunge+ URL (plunge+file://, plunge+s3://) picks the plunge engine."
|
|
@@ -89,6 +98,7 @@ var DEFAULT_KEY_ENV, VARIABLE, REVIEW_KEYS, AGENT_KEYS, modelSchema, agents;
|
|
|
89
98
|
var init_agents = __esm({
|
|
90
99
|
"src/config/agents.ts"() {
|
|
91
100
|
"use strict";
|
|
101
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
92
102
|
init_types();
|
|
93
103
|
DEFAULT_KEY_ENV = "ANTHROPIC_API_KEY";
|
|
94
104
|
VARIABLE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
@@ -182,6 +192,7 @@ var APPLY_WHEN, DEFAULT_APPLY_WHEN, APPLY_REQUIRES, APPLY_MERGE, MERGE_METHODS,
|
|
|
182
192
|
var init_apply = __esm({
|
|
183
193
|
"src/config/apply.ts"() {
|
|
184
194
|
"use strict";
|
|
195
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
185
196
|
init_types();
|
|
186
197
|
APPLY_WHEN = ["merge", "pull-request"];
|
|
187
198
|
DEFAULT_APPLY_WHEN = "merge";
|
|
@@ -261,7 +272,7 @@ function gateOf(doc) {
|
|
|
261
272
|
return typeof doc.gate === "string" && GATES.includes(doc.gate) ? doc.gate : DEFAULT_GATE;
|
|
262
273
|
}
|
|
263
274
|
function previewOf(doc) {
|
|
264
|
-
return doc.preview
|
|
275
|
+
return doc.preview !== false;
|
|
265
276
|
}
|
|
266
277
|
function quorumOf(rules) {
|
|
267
278
|
return Object.entries(rules).map(([glob, r]) => {
|
|
@@ -273,12 +284,13 @@ var GATES, DEFAULT_GATE, APPROVALS, DEFAULT_APPROVAL, DEFAULT_PREVIEW, APPROVAL_
|
|
|
273
284
|
var init_approval = __esm({
|
|
274
285
|
"src/config/approval.ts"() {
|
|
275
286
|
"use strict";
|
|
287
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
276
288
|
init_types();
|
|
277
289
|
GATES = ["always", "on-destructive", "never"];
|
|
278
290
|
DEFAULT_GATE = "on-destructive";
|
|
279
291
|
APPROVALS = ["ledger", "sealed", "pr-review"];
|
|
280
292
|
DEFAULT_APPROVAL = "ledger";
|
|
281
|
-
DEFAULT_PREVIEW =
|
|
293
|
+
DEFAULT_PREVIEW = true;
|
|
282
294
|
APPROVAL_RULE_KEYS = ["role", "roles", "quorum"];
|
|
283
295
|
approval = {
|
|
284
296
|
name: "approval",
|
|
@@ -289,7 +301,7 @@ var init_approval = __esm({
|
|
|
289
301
|
preview: {
|
|
290
302
|
type: "boolean",
|
|
291
303
|
default: DEFAULT_PREVIEW,
|
|
292
|
-
description: "true: plywood init sets approvals.preview in the waves spec, so a merged wave applies only the plans its pull request previewed, or exits 4.
|
|
304
|
+
description: "true (the default): plywood init sets approvals.preview in the waves spec, so a merged wave applies only the plans its pull request previewed, or exits 4. false leaves it out."
|
|
293
305
|
},
|
|
294
306
|
approval_rules: {
|
|
295
307
|
type: "object",
|
|
@@ -351,6 +363,7 @@ var PLYWOOD_KEY_FOR, chantKeys;
|
|
|
351
363
|
var init_chant_keys = __esm({
|
|
352
364
|
"src/config/chant-keys.ts"() {
|
|
353
365
|
"use strict";
|
|
366
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
354
367
|
init_types();
|
|
355
368
|
PLYWOOD_KEY_FOR = {
|
|
356
369
|
workflow: "apply.when",
|
|
@@ -431,6 +444,7 @@ var PRODUCT, TAGLINE, OP_NAME, PR_GATE, VERBS, BIN, NOTIFY_WORDS, PRODUCT_WORDS,
|
|
|
431
444
|
var init_words = __esm({
|
|
432
445
|
"src/words.ts"() {
|
|
433
446
|
"use strict";
|
|
447
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
434
448
|
PRODUCT = "plywood";
|
|
435
449
|
TAGLINE = "Pulumi ops and governance.";
|
|
436
450
|
OP_NAME = "pulumi-up";
|
|
@@ -496,7 +510,7 @@ ${r.tail}` : ""}`,
|
|
|
496
510
|
};
|
|
497
511
|
DRIFT_OP_PREFIX = "plywood-drift-";
|
|
498
512
|
LOCK_GRAIN_WORDS = {
|
|
499
|
-
perEngine: (
|
|
513
|
+
perEngine: (engine3) => engine3 === "plunge" ? "resource, plunge's default (no ci.lockGrain; chant's pulumi lexicon sets it)" : engine3 === "pulumi" ? "stack, stock Pulumi's default (no ci.lockGrain; chant's pulumi lexicon sets it)" : "per engine: resource on plunge, stack on stock Pulumi (no ci.lockGrain; chant's pulumi lexicon sets it)"
|
|
500
514
|
};
|
|
501
515
|
PLUNGE_TIMING_ATTRIBUTES = {
|
|
502
516
|
resources: `${PRODUCT}.plunge.resources`,
|
|
@@ -525,10 +539,17 @@ function controlOf(doc) {
|
|
|
525
539
|
}
|
|
526
540
|
return out;
|
|
527
541
|
}
|
|
542
|
+
function controlBuildArgv(doc, member) {
|
|
543
|
+
const settings = controlOf(doc);
|
|
544
|
+
if (!settings) return [];
|
|
545
|
+
const { repo, ...rest } = { member, ...settings };
|
|
546
|
+
return ["--control", repo, ...Object.entries(rest).flatMap(([k, v]) => ["--control-setting", `${k}=${v}`])];
|
|
547
|
+
}
|
|
528
548
|
var CONTROL_KEYS, CONTROL_FORGES, control;
|
|
529
549
|
var init_control = __esm({
|
|
530
550
|
"src/config/control.ts"() {
|
|
531
551
|
"use strict";
|
|
552
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
532
553
|
init_words();
|
|
533
554
|
init_types();
|
|
534
555
|
CONTROL_KEYS = {
|
|
@@ -597,37 +618,27 @@ var init_control = __esm({
|
|
|
597
618
|
var version, dependencies;
|
|
598
619
|
var init_package = __esm({
|
|
599
620
|
"package.json"() {
|
|
600
|
-
version = "0.3.
|
|
621
|
+
version = "0.3.5";
|
|
601
622
|
dependencies = {
|
|
602
|
-
"@intentius/chant": "0.124.
|
|
603
|
-
"@intentius/chant-lexicon-forgejo": "0.124.
|
|
604
|
-
"@intentius/chant-lexicon-github": "0.124.
|
|
605
|
-
"@intentius/chant-lexicon-gitlab": "0.124.
|
|
606
|
-
"@intentius/chant-lexicon-pulumi": "0.124.
|
|
607
|
-
};
|
|
608
|
-
}
|
|
609
|
-
});
|
|
610
|
-
|
|
611
|
-
// src/image-digests.json
|
|
612
|
-
var image_digests_default;
|
|
613
|
-
var init_image_digests = __esm({
|
|
614
|
-
"src/image-digests.json"() {
|
|
615
|
-
image_digests_default = {
|
|
616
|
-
"ghcr.io/intentius/plywood-pulumi:0.3.2-pulumi3.272.0": "sha256:eeb44ac18dc594acd8a56dfc1a6ed00751154f6b6bfba8bbdff97a513f4b9f84",
|
|
617
|
-
"ghcr.io/intentius/plywood-plunge:0.3.2-plunge0.2.0": "sha256:5239ffabd5f5b7a4ffc5cbbe1628a34990bc95549cd4a1bef27953d3dfb7d8a9"
|
|
623
|
+
"@intentius/chant": "0.124.79",
|
|
624
|
+
"@intentius/chant-lexicon-forgejo": "0.124.79",
|
|
625
|
+
"@intentius/chant-lexicon-github": "0.124.79",
|
|
626
|
+
"@intentius/chant-lexicon-gitlab": "0.124.79",
|
|
627
|
+
"@intentius/chant-lexicon-pulumi": "0.124.79"
|
|
618
628
|
};
|
|
619
629
|
}
|
|
620
630
|
});
|
|
621
631
|
|
|
622
632
|
// src/images.ts
|
|
623
|
-
|
|
624
|
-
|
|
633
|
+
import { CI_DOCKER_ARCH, ciFetchScript, plungeInstallLine, pulumiInstallLine } from "@intentius/chant-lexicon-pulumi/ci-setup";
|
|
634
|
+
function engineDir(engine3, version2) {
|
|
635
|
+
return `${ENGINE_ROOT}/${engine3}-${version2}`;
|
|
625
636
|
}
|
|
626
|
-
function engineBin(
|
|
627
|
-
return `${engineDir(
|
|
637
|
+
function engineBin(engine3, version2) {
|
|
638
|
+
return `${engineDir(engine3, version2)}/${engine3}`;
|
|
628
639
|
}
|
|
629
|
-
function release(
|
|
630
|
-
if (
|
|
640
|
+
function release(engine3, version2) {
|
|
641
|
+
if (engine3 === "pulumi") {
|
|
631
642
|
return {
|
|
632
643
|
base: `https://github.com/pulumi/pulumi/releases/download/v${version2}`,
|
|
633
644
|
archive: (arch) => `pulumi-v${version2}-linux-${arch}.tar.gz`,
|
|
@@ -640,8 +651,8 @@ function release(engine2, version2) {
|
|
|
640
651
|
checksums: `plunge-v${version2}-checksums.txt`
|
|
641
652
|
};
|
|
642
653
|
}
|
|
643
|
-
function pinnedSums(
|
|
644
|
-
const r = release(
|
|
654
|
+
function pinnedSums(engine3, version2) {
|
|
655
|
+
const r = release(engine3, version2);
|
|
645
656
|
const out = {};
|
|
646
657
|
for (const arch of ARCHES) {
|
|
647
658
|
const sum = PINNED_SHA256[r.archive(arch)];
|
|
@@ -649,54 +660,10 @@ function pinnedSums(engine2, version2) {
|
|
|
649
660
|
}
|
|
650
661
|
return out;
|
|
651
662
|
}
|
|
652
|
-
function
|
|
653
|
-
const
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
const file = `${r.archive("x64").replace("-x64.", "-$a.")}`;
|
|
657
|
-
return [
|
|
658
|
-
`a=${arch}`,
|
|
659
|
-
`f="${file}"`,
|
|
660
|
-
`t="$(mktemp -d)"`,
|
|
661
|
-
`curl -fsSLo "$t/$f" "${r.base}/$f"`,
|
|
662
|
-
`curl -fsSLo "$t/sums" ${checksumsUrl ?? `"${r.base}/${r.checksums}"`}`,
|
|
663
|
-
// The archive's line, with any ./ or * before the name taken off.
|
|
664
|
-
`line="$(sed -n "s#^\\([0-9a-f]\\{64\\}\\) *[*.]*/*$f\\$#\\1#p" "$t/sums")"`,
|
|
665
|
-
`{ [ -n "$line" ] || { echo "${engine2} ${version2}: $f is not in ${r.checksums}" >&2; exit 1; }; }`,
|
|
666
|
-
pinCase,
|
|
667
|
-
`{ [ -z "$want" ] || [ "$line" = "$want" ] || { echo "${engine2} ${version2}: ${r.checksums} lists $line for $f; plywood pins $want" >&2; exit 1; }; }`,
|
|
668
|
-
`echo "$line $t/$f" | sha256sum -c - >/dev/null`,
|
|
669
|
-
`mkdir -p "${dir}"`,
|
|
670
|
-
`tar -xzf "$t/$f" -C "${dir}" --strip-components=1`
|
|
671
|
-
].join(" && ");
|
|
672
|
-
}
|
|
673
|
-
function installLine(engine2, version2, link = true) {
|
|
674
|
-
const chant = chantLines;
|
|
675
|
-
if (link && chant) {
|
|
676
|
-
const sums = pinnedSums(engine2, version2);
|
|
677
|
-
const options = { root: ENGINE_ROOT, ...Object.keys(sums).length ? { sha256: sums } : {} };
|
|
678
|
-
return engine2 === "pulumi" ? chant.pulumiInstallLine(version2, options) : chant.plungeInstallLine(version2, options);
|
|
679
|
-
}
|
|
680
|
-
return plywoodInstallLine(engine2, version2, link);
|
|
681
|
-
}
|
|
682
|
-
function plywoodInstallLine(engine2, version2, link = true) {
|
|
683
|
-
const dir = engineDir(engine2, version2);
|
|
684
|
-
const linkCmd = `ln -sf "${dir}/${engine2}" /usr/local/bin/${engine2}`;
|
|
685
|
-
const install = `(set -e; ${fetchScript(engine2, version2, dir, JOB_ARCH)})`;
|
|
686
|
-
return link ? `{ [ -x "${dir}/${engine2}" ] || ${install}; } && ${linkCmd}` : `[ -x "${dir}/${engine2}" ] || ${install}`;
|
|
687
|
-
}
|
|
688
|
-
function useChantInstallLines() {
|
|
689
|
-
loading ??= (async () => {
|
|
690
|
-
try {
|
|
691
|
-
const m = await import("@intentius/chant-lexicon-pulumi/ci-setup");
|
|
692
|
-
const ok = typeof m.ciRelease === "function" && typeof m.PINNED_SHA256 === "object" && typeof m.pulumiInstallLine === "function" && typeof m.plungeInstallLine === "function";
|
|
693
|
-
chantLines = ok ? m : void 0;
|
|
694
|
-
} catch {
|
|
695
|
-
chantLines = void 0;
|
|
696
|
-
}
|
|
697
|
-
return chantLines !== void 0;
|
|
698
|
-
})();
|
|
699
|
-
return loading;
|
|
663
|
+
function installLine(engine3, version2, link = true) {
|
|
664
|
+
const sums = pinnedSums(engine3, version2);
|
|
665
|
+
const options = { root: ENGINE_ROOT, ...Object.keys(sums).length ? { sha256: sums } : {}, link };
|
|
666
|
+
return engine3 === "pulumi" ? pulumiInstallLine(version2, options) : plungeInstallLine(version2, options);
|
|
700
667
|
}
|
|
701
668
|
function installLines(pins) {
|
|
702
669
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -710,15 +677,15 @@ function installLines(pins) {
|
|
|
710
677
|
}
|
|
711
678
|
return out;
|
|
712
679
|
}
|
|
713
|
-
function imageName(
|
|
714
|
-
return `plywood-${
|
|
680
|
+
function imageName(engine3) {
|
|
681
|
+
return `plywood-${engine3}`;
|
|
715
682
|
}
|
|
716
|
-
function imageTag(
|
|
717
|
-
return `${version2}-${
|
|
683
|
+
function imageTag(engine3, version2 = version) {
|
|
684
|
+
return `${version2}-${engine3}${TOOL_VERSIONS[engine3]}`;
|
|
718
685
|
}
|
|
719
|
-
function imageFor(
|
|
720
|
-
const repository = `${REGISTRY}/${imageName(
|
|
721
|
-
const tag = imageTag(
|
|
686
|
+
function imageFor(engine3, table = digests) {
|
|
687
|
+
const repository = `${REGISTRY}/${imageName(engine3)}`;
|
|
688
|
+
const tag = imageTag(engine3);
|
|
722
689
|
const digest = table[`${repository}:${tag}`];
|
|
723
690
|
return { repository, tag, ...digest ? { digest } : {} };
|
|
724
691
|
}
|
|
@@ -730,23 +697,20 @@ function parsePlugin(s) {
|
|
|
730
697
|
return m ? { kind: m[1] ?? "resource", name: m[2], version: m[3] } : void 0;
|
|
731
698
|
}
|
|
732
699
|
function chantBuildFlags(o) {
|
|
733
|
-
const lines2 =
|
|
700
|
+
const lines2 = o.beforeScript ?? [];
|
|
734
701
|
return [...o.image ? ["--image", o.image] : [], ...lines2.flatMap((l) => ["--before-script", l])];
|
|
735
702
|
}
|
|
736
|
-
function chantPipelineOptions(p
|
|
703
|
+
function chantPipelineOptions(p) {
|
|
737
704
|
const lines2 = p.engineSetup ?? [];
|
|
738
|
-
|
|
739
|
-
if (lines2.length === 0) return out;
|
|
740
|
-
if (!p.image && chantReadsEngineSetup) out.prLoop = { engineSetup: [...lines2] };
|
|
741
|
-
else out.beforeScript = [...lines2];
|
|
742
|
-
return out;
|
|
705
|
+
return { ...p.image ? { image: p.image } : {}, ...p.runner !== void 0 ? { runner: p.runner } : {}, ...lines2.length ? { beforeScript: [...lines2] } : {} };
|
|
743
706
|
}
|
|
744
|
-
var REGISTRY, TOOL_VERSIONS, ARCHES, PINNED_SHA256, ENGINE_ROOT,
|
|
707
|
+
var digests, REGISTRY, TOOL_VERSIONS, ARCHES, PINNED_SHA256, ENGINE_ROOT, DIGEST, RELEASE_LABEL_ARG, LABEL_REF, PLUGIN;
|
|
745
708
|
var init_images = __esm({
|
|
746
709
|
"src/images.ts"() {
|
|
747
710
|
"use strict";
|
|
711
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
748
712
|
init_package();
|
|
749
|
-
|
|
713
|
+
digests = typeof define_PLYWOOD_IMAGE_DIGESTS_default !== "undefined" ? define_PLYWOOD_IMAGE_DIGESTS_default : {};
|
|
750
714
|
REGISTRY = "ghcr.io/intentius";
|
|
751
715
|
TOOL_VERSIONS = {
|
|
752
716
|
pulumi: "3.272.0",
|
|
@@ -761,8 +725,9 @@ var init_images = __esm({
|
|
|
761
725
|
"plunge-v0.2.0-linux-arm64.tar.gz": "c176a82f038164085aa19c8ad6530d6022e560e124050e535dbe364ae0473357"
|
|
762
726
|
};
|
|
763
727
|
ENGINE_ROOT = "/opt/plywood";
|
|
764
|
-
JOB_ARCH = `"$(uname -m | sed 's/x86_64/x64/;s/aarch64/arm64/')"`;
|
|
765
728
|
DIGEST = /@sha256:[0-9a-f]{64}$/;
|
|
729
|
+
RELEASE_LABEL_ARG = "CHANT_RELEASE_LABEL";
|
|
730
|
+
LABEL_REF = `\${${RELEASE_LABEL_ARG}}`;
|
|
766
731
|
PLUGIN = /^(?:(resource|converter|language|analyzer|tool):)?([a-z0-9][a-z0-9-]*)@(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/;
|
|
767
732
|
}
|
|
768
733
|
});
|
|
@@ -831,8 +796,8 @@ function userImage(doc) {
|
|
|
831
796
|
if (typeof doc.image === "string") return doc.image;
|
|
832
797
|
return isObject(doc.image) && typeof doc.image.name === "string" ? doc.image.name : void 0;
|
|
833
798
|
}
|
|
834
|
-
function linkedVersion(
|
|
835
|
-
const top = pinFor(doc[
|
|
799
|
+
function linkedVersion(engine3, doc, versions) {
|
|
800
|
+
const top = pinFor(doc[engine3], void 0) ?? TOOL_VERSIONS[engine3];
|
|
836
801
|
if (versions.length === 0) return top;
|
|
837
802
|
const count = /* @__PURE__ */ new Map();
|
|
838
803
|
for (const v of versions) count.set(v, (count.get(v) ?? 0) + 1);
|
|
@@ -843,14 +808,15 @@ var VERSION, LABEL, RUNNER_STAGES, IMAGE_KEYS, versionSchema, runnerSpecSchema,
|
|
|
843
808
|
var init_images2 = __esm({
|
|
844
809
|
"src/config/images.ts"() {
|
|
845
810
|
"use strict";
|
|
811
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
846
812
|
init_images();
|
|
847
813
|
init_types();
|
|
848
814
|
VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
849
815
|
LABEL = /^[^\s,]+$/;
|
|
850
816
|
RUNNER_STAGES = ["default", "preview", "up", "drift"];
|
|
851
817
|
IMAGE_KEYS = ["name", "plugins"];
|
|
852
|
-
versionSchema = (
|
|
853
|
-
description: `The ${
|
|
818
|
+
versionSchema = (engine3, example) => ({
|
|
819
|
+
description: `The ${engine3} release the jobs run, such as ${example}: one for every stack, or a map of stack glob to release. plywood's image's release when absent.`,
|
|
854
820
|
anyOf: [
|
|
855
821
|
{ type: "string", pattern: VERSION.source },
|
|
856
822
|
{ type: "object", minProperties: 1, additionalProperties: { type: "string", pattern: VERSION.source } }
|
|
@@ -910,22 +876,22 @@ var init_images2 = __esm({
|
|
|
910
876
|
const engineOfMember = (e) => e ?? out.memberDefaults.engine ?? "pulumi";
|
|
911
877
|
const per = { pulumi: [], plunge: [] };
|
|
912
878
|
const chosen = out.members.map((m) => {
|
|
913
|
-
const
|
|
914
|
-
const version2 = pinFor(doc[
|
|
915
|
-
per[
|
|
916
|
-
return { m, engine:
|
|
879
|
+
const engine3 = engineOfMember(m.engine);
|
|
880
|
+
const version2 = pinFor(doc[engine3], m.name) ?? pinFor(doc[engine3], void 0) ?? TOOL_VERSIONS[engine3];
|
|
881
|
+
per[engine3].push(version2);
|
|
882
|
+
return { m, engine: engine3, version: version2 };
|
|
917
883
|
});
|
|
918
884
|
const engines = new Set(chosen.map((c) => c.engine));
|
|
919
885
|
engines.add(engineOfMember(void 0));
|
|
920
886
|
if (doc.plunge !== void 0) engines.add("plunge");
|
|
921
887
|
const linked = Object.fromEntries([...engines].map((e) => [e, linkedVersion(e, doc, per[e])]));
|
|
922
|
-
const pins = [...engines].map((
|
|
923
|
-
for (const { m, engine:
|
|
924
|
-
if (doc[
|
|
925
|
-
if (version2 !== linked[
|
|
926
|
-
m.engineBin = engineBin(
|
|
888
|
+
const pins = [...engines].map((engine3) => ({ engine: engine3, version: linked[engine3], linked: true }));
|
|
889
|
+
for (const { m, engine: engine3, version: version2 } of chosen) {
|
|
890
|
+
if (doc[engine3] !== void 0) m.engineVersion = version2;
|
|
891
|
+
if (version2 !== linked[engine3]) {
|
|
892
|
+
m.engineBin = engineBin(engine3, version2);
|
|
927
893
|
m.engineRoot = ENGINE_ROOT;
|
|
928
|
-
pins.push({ engine:
|
|
894
|
+
pins.push({ engine: engine3, version: version2, linked: false });
|
|
929
895
|
}
|
|
930
896
|
}
|
|
931
897
|
out.pipeline.engineSetup = installLines(pins);
|
|
@@ -1051,6 +1017,7 @@ var STACK_KEYS, ACTION_KEYS, WAVES_KEYS, STACK_NAME, ACTION_NAME, ACTION_TYPE_PR
|
|
|
1051
1017
|
var init_stacks = __esm({
|
|
1052
1018
|
"src/config/stacks.ts"() {
|
|
1053
1019
|
"use strict";
|
|
1020
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
1054
1021
|
init_approval();
|
|
1055
1022
|
init_types();
|
|
1056
1023
|
STACK_KEYS = ["dir", "paths", "gate", "backend", "actions"];
|
|
@@ -1215,6 +1182,7 @@ var DRIFT_RESPONSES, DEFAULT_DRIFT_RESPONSE, EVENT_RESPONSES, EVENT_KEYS, RESPON
|
|
|
1215
1182
|
var init_drift = __esm({
|
|
1216
1183
|
"src/config/drift.ts"() {
|
|
1217
1184
|
"use strict";
|
|
1185
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
1218
1186
|
init_images2();
|
|
1219
1187
|
init_stacks();
|
|
1220
1188
|
init_types();
|
|
@@ -1339,6 +1307,7 @@ var ENGINES, PLUNGE_URL, engine;
|
|
|
1339
1307
|
var init_engine = __esm({
|
|
1340
1308
|
"src/config/engine.ts"() {
|
|
1341
1309
|
"use strict";
|
|
1310
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
1342
1311
|
init_stacks();
|
|
1343
1312
|
init_types();
|
|
1344
1313
|
ENGINES = ["pulumi", "plunge"];
|
|
@@ -1472,6 +1441,7 @@ var IDENTITY_CLOUDS, CLOUD_SETTINGS, SETTINGS_CLOUDS, REQUIRED, IDENTITY_KEYS, S
|
|
|
1472
1441
|
var init_identity = __esm({
|
|
1473
1442
|
"src/config/identity.ts"() {
|
|
1474
1443
|
"use strict";
|
|
1444
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
1475
1445
|
init_types();
|
|
1476
1446
|
IDENTITY_CLOUDS = ["aws", "gcp", "azure", "pulumi"];
|
|
1477
1447
|
CLOUD_SETTINGS = {
|
|
@@ -1630,6 +1600,7 @@ var VARIABLE3, RESERVED_VARIABLES, environmentSchema, jobs;
|
|
|
1630
1600
|
var init_jobs = __esm({
|
|
1631
1601
|
"src/config/jobs.ts"() {
|
|
1632
1602
|
"use strict";
|
|
1603
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
1633
1604
|
init_types();
|
|
1634
1605
|
init_images2();
|
|
1635
1606
|
VARIABLE3 = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
@@ -1736,6 +1707,7 @@ var LATER_KEYS, later;
|
|
|
1736
1707
|
var init_later = __esm({
|
|
1737
1708
|
"src/config/later.ts"() {
|
|
1738
1709
|
"use strict";
|
|
1710
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
1739
1711
|
LATER_KEYS = {};
|
|
1740
1712
|
later = {
|
|
1741
1713
|
name: "later",
|
|
@@ -1749,10 +1721,10 @@ var init_later = __esm({
|
|
|
1749
1721
|
});
|
|
1750
1722
|
|
|
1751
1723
|
// src/config/locks.ts
|
|
1752
|
-
function lockGrainFor(doc,
|
|
1724
|
+
function lockGrainFor(doc, engine3) {
|
|
1753
1725
|
const set = locksOf(doc).grain;
|
|
1754
1726
|
if (set) return set;
|
|
1755
|
-
return
|
|
1727
|
+
return engine3 ? DEFAULT_LOCK_GRAIN[engine3] : "per-engine";
|
|
1756
1728
|
}
|
|
1757
1729
|
function locksOf(doc) {
|
|
1758
1730
|
const l = isObject(doc.locks) ? doc.locks : {};
|
|
@@ -1770,6 +1742,7 @@ var LOCKS_WHEN, DEFAULT_LOCKS_WHEN, LOCK_GRAINS, DEFAULT_LOCK_GRAIN, CI_LOCKS_OF
|
|
|
1770
1742
|
var init_locks = __esm({
|
|
1771
1743
|
"src/config/locks.ts"() {
|
|
1772
1744
|
"use strict";
|
|
1745
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
1773
1746
|
init_engine();
|
|
1774
1747
|
init_stacks();
|
|
1775
1748
|
init_types();
|
|
@@ -1814,11 +1787,6 @@ var init_locks = __esm({
|
|
|
1814
1787
|
});
|
|
1815
1788
|
|
|
1816
1789
|
// src/config/observe.ts
|
|
1817
|
-
function notifyForChant(notify2, chant) {
|
|
1818
|
-
if (notify2.relay === void 0 || typeof chant?.NOTIFY_RELAY_FLOOR === "string") return { ...notify2 };
|
|
1819
|
-
const { relay: _relay, ...rest } = notify2;
|
|
1820
|
-
return rest;
|
|
1821
|
-
}
|
|
1822
1790
|
function variable(v, key) {
|
|
1823
1791
|
if (v === void 0) return [];
|
|
1824
1792
|
return typeof v === "string" && VARIABLE4.test(v) ? [] : [error(key, "names an environment variable, such as SLACK_WEBHOOK_URL; the URL or key itself never goes in plywood.yml")];
|
|
@@ -1849,16 +1817,16 @@ function telemetryOf(doc) {
|
|
|
1849
1817
|
traces: t.traces !== false
|
|
1850
1818
|
};
|
|
1851
1819
|
}
|
|
1852
|
-
var NOTIFY_EVENTS, VARIABLE4, DEFAULT_SERVICE_NAME, REPORT_SCHEMES,
|
|
1820
|
+
var NOTIFY_EVENTS, VARIABLE4, DEFAULT_SERVICE_NAME, REPORT_SCHEMES, WEBHOOK_VAR, observe;
|
|
1853
1821
|
var init_observe = __esm({
|
|
1854
1822
|
"src/config/observe.ts"() {
|
|
1855
1823
|
"use strict";
|
|
1824
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
1856
1825
|
init_types();
|
|
1857
1826
|
NOTIFY_EVENTS = { "wave-waiting": "waiting", "up-refused": "refused", "up-failed": "failed", drift: "drift" };
|
|
1858
1827
|
VARIABLE4 = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
1859
1828
|
DEFAULT_SERVICE_NAME = "plywood";
|
|
1860
1829
|
REPORT_SCHEMES = ["s3", "gs", "az", "file"];
|
|
1861
|
-
NEWER_SCHEMES = /* @__PURE__ */ new Set(["gs", "az"]);
|
|
1862
1830
|
WEBHOOK_VAR = { type: "object", additionalProperties: false, required: ["webhook"], properties: { webhook: { type: "string", pattern: VARIABLE4.source, description: "The variable holding the incoming webhook's URL." } } };
|
|
1863
1831
|
observe = {
|
|
1864
1832
|
name: "notify, telemetry and reports",
|
|
@@ -1914,7 +1882,6 @@ var init_observe = __esm({
|
|
|
1914
1882
|
if (n.relay !== void 0) {
|
|
1915
1883
|
out.push(...variable(n.relay, "notify.relay"));
|
|
1916
1884
|
if (n.slack === void 0 && n.teams === void 0) out.push(error("notify.relay", "needs notify.slack or notify.teams: approvals come back from a chat message"));
|
|
1917
|
-
else out.push(warning("notify.relay", "needs chant >= the release after 0.124.54 (chant#4611); plywood init leaves it out of the waves spec on a chant without the relay"));
|
|
1918
1885
|
}
|
|
1919
1886
|
for (const chat of ["slack", "teams"]) {
|
|
1920
1887
|
const c = n[chat];
|
|
@@ -1954,7 +1921,6 @@ var init_observe = __esm({
|
|
|
1954
1921
|
out.push(...onlyKeys(r, ["url"], "reports"));
|
|
1955
1922
|
const scheme = typeof r.url === "string" ? /^([a-z0-9]+):\/\/\S+$/.exec(r.url)?.[1] : void 0;
|
|
1956
1923
|
if (!scheme || !REPORT_SCHEMES.includes(scheme)) out.push(error("reports.url", `is a ${REPORT_SCHEMES.map((s) => `${s}://`).join(", ")} URL`));
|
|
1957
|
-
else if (NEWER_SCHEMES.has(scheme)) out.push(warning("reports.url", `${scheme}:// reports need chant >= the release after 0.124.54 (chant#4554); the pinned chant takes s3:// and file://`));
|
|
1958
1924
|
}
|
|
1959
1925
|
}
|
|
1960
1926
|
return out;
|
|
@@ -1970,21 +1936,7 @@ var init_observe = __esm({
|
|
|
1970
1936
|
|
|
1971
1937
|
// src/config/policy.ts
|
|
1972
1938
|
import { posix } from "node:path";
|
|
1973
|
-
|
|
1974
|
-
policySourceLoading ??= (async () => {
|
|
1975
|
-
try {
|
|
1976
|
-
const m = await import("@intentius/chant/policy-source");
|
|
1977
|
-
policySource = typeof m.CI_POLICY_SOURCE_FLOOR === "string" && typeof m.assertGatePolicySource === "function";
|
|
1978
|
-
} catch {
|
|
1979
|
-
policySource = false;
|
|
1980
|
-
}
|
|
1981
|
-
return policySource;
|
|
1982
|
-
})();
|
|
1983
|
-
return policySourceLoading;
|
|
1984
|
-
}
|
|
1985
|
-
function chantHasPolicySource() {
|
|
1986
|
-
return policySource;
|
|
1987
|
-
}
|
|
1939
|
+
import { assertGatePolicySource } from "@intentius/chant/policy-source";
|
|
1988
1940
|
function repoPath(v, key) {
|
|
1989
1941
|
if (typeof v !== "string" || v.trim() === "") return [error(key, "is a path inside the repository")];
|
|
1990
1942
|
if (v.startsWith("/") || v.startsWith("-") || v.split("/").includes("..")) return [error(key, `is ${JSON.stringify(v)}; give a path relative to the repository root, inside it`)];
|
|
@@ -2019,13 +1971,13 @@ function policySourceOf(doc) {
|
|
|
2019
1971
|
if (!isObject(src) || typeof src.repo !== "string" || typeof src.ref !== "string") return void 0;
|
|
2020
1972
|
return { repo: src.repo, ref: src.ref, ...typeof src.path === "string" ? { path: cleanPath(src.path) } : {} };
|
|
2021
1973
|
}
|
|
2022
|
-
function chantPolicyOf(doc
|
|
1974
|
+
function chantPolicyOf(doc) {
|
|
2023
1975
|
const p = doc.policy;
|
|
2024
1976
|
if (!isObject(p)) return void 0;
|
|
2025
1977
|
const rego = typeof p.rego === "string" ? cleanPath(p.rego) : void 0;
|
|
2026
1978
|
const command = Array.isArray(p.command) ? p.command : void 0;
|
|
2027
|
-
const source =
|
|
2028
|
-
const packs =
|
|
1979
|
+
const source = policySourceOf(doc);
|
|
1980
|
+
const packs = policyPacksOf(doc);
|
|
2029
1981
|
if (rego === void 0 && command === void 0 && p.engine === void 0 && p.namespace === void 0 && !source && packs.length === 0) return void 0;
|
|
2030
1982
|
return {
|
|
2031
1983
|
...typeof p.engine === "string" ? { engine: p.engine } : {},
|
|
@@ -2036,26 +1988,22 @@ function chantPolicyOf(doc, chantReadsSource = chantHasPolicySource()) {
|
|
|
2036
1988
|
...packs.length ? { packs } : {}
|
|
2037
1989
|
};
|
|
2038
1990
|
}
|
|
2039
|
-
function stepPolicyPacksOf(doc
|
|
2040
|
-
return
|
|
1991
|
+
function stepPolicyPacksOf(doc) {
|
|
1992
|
+
return policySourceOf(doc) ? [] : policyPacksOf(doc);
|
|
2041
1993
|
}
|
|
2042
|
-
var POLICY_ENGINES, POLICY_KEYS, PACK_KEYS,
|
|
1994
|
+
var POLICY_ENGINES, POLICY_KEYS, PACK_KEYS, NO_SUCH_KEY, policy;
|
|
2043
1995
|
var init_policy = __esm({
|
|
2044
1996
|
"src/config/policy.ts"() {
|
|
2045
1997
|
"use strict";
|
|
1998
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
2046
1999
|
init_types();
|
|
2047
2000
|
POLICY_ENGINES = ["conftest", "opa"];
|
|
2048
2001
|
POLICY_KEYS = ["rego", "engine", "namespace", "command", "packs", "source", "providers"];
|
|
2049
2002
|
PACK_KEYS = ["dir", "config"];
|
|
2050
|
-
SOURCE_KEYS = ["repo", "ref", "path"];
|
|
2051
|
-
SOURCE_REPO = /^(https?:\/\/[^\s/]+\/\S+|ssh:\/\/[^\s/]+\/\S+|file:\/\/\/?\S+|[A-Za-z0-9._-]+@[A-Za-z0-9.-]+:\S+)$/;
|
|
2052
|
-
SOURCE_REF = /^[A-Za-z0-9][A-Za-z0-9._/-]*$/;
|
|
2053
|
-
policySource = false;
|
|
2054
2003
|
NO_SUCH_KEY = {
|
|
2055
2004
|
allow_unattended: "chant's gate denies and holds only; a non-empty change always waits for a person, so there is no unattended outcome",
|
|
2056
2005
|
outcomes: "a Rego rule's name sets its outcome: deny_* denies, hold_* holds, warn_* is a note line"
|
|
2057
2006
|
};
|
|
2058
|
-
SOURCE_NEEDS_CHANT = "is a shared policy source from a git ref, which needs chant >= the release after 0.124.54 (chant#4613's ci.policy.source); on this chant, vendor the rules into the repository, where they are read at base";
|
|
2059
2007
|
policy = {
|
|
2060
2008
|
name: "policy",
|
|
2061
2009
|
keys: ["policy"],
|
|
@@ -2073,7 +2021,7 @@ var init_policy = __esm({
|
|
|
2073
2021
|
type: "object",
|
|
2074
2022
|
additionalProperties: false,
|
|
2075
2023
|
required: ["repo", "ref"],
|
|
2076
|
-
description: "A shared rule set from a git ref, fetched by the job at the ref the base names
|
|
2024
|
+
description: "A shared rule set from a git ref, fetched by the job at the ref the base names. packs are then paths inside it.",
|
|
2077
2025
|
properties: {
|
|
2078
2026
|
repo: { type: "string", description: "The repository: https://, ssh:// or file:// URL, or git@<host>:<path>." },
|
|
2079
2027
|
ref: { type: "string", description: "A tag, a branch or a full commit." },
|
|
@@ -2114,16 +2062,13 @@ var init_policy = __esm({
|
|
|
2114
2062
|
const out = [];
|
|
2115
2063
|
for (const k of Object.keys(p)) if (k in NO_SUCH_KEY) out.push(error(`policy.${k}`, `is not a plywood key: ${NO_SUCH_KEY[k]}`));
|
|
2116
2064
|
if (p.source !== void 0) {
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
const
|
|
2121
|
-
out.push(
|
|
2122
|
-
if (typeof src.repo !== "string" || !SOURCE_REPO.test(src.repo) || src.repo.startsWith("-")) out.push(error("policy.source.repo", "is a git URL: https://, ssh://, file:// or git@<host>:<path>"));
|
|
2123
|
-
if (typeof src.ref !== "string" || !SOURCE_REF.test(src.ref) || src.ref.includes("..") || src.ref.endsWith("/")) out.push(error("policy.source.ref", "is a tag, a branch or a commit"));
|
|
2124
|
-
if (src.path !== void 0) out.push(...repoPath(src.path, "policy.source.path"));
|
|
2125
|
-
if (p.rego !== void 0) out.push(error("policy.rego", "goes with no policy.source: with a source, policy.source.path names the Rego directory inside it"));
|
|
2065
|
+
try {
|
|
2066
|
+
assertGatePolicySource("policy.source", p.source);
|
|
2067
|
+
} catch (e) {
|
|
2068
|
+
const m = /^(\S+) (.*)$/.exec(e.message);
|
|
2069
|
+
out.push(error(m?.[1] ?? "policy.source", m?.[2] ?? e.message));
|
|
2126
2070
|
}
|
|
2071
|
+
if (p.rego !== void 0) out.push(error("policy.rego", "goes with no policy.source: with a source, policy.source.path names the Rego directory inside it"));
|
|
2127
2072
|
}
|
|
2128
2073
|
out.push(...onlyKeys(Object.fromEntries(Object.entries(p).filter(([k]) => !(k in NO_SUCH_KEY))), POLICY_KEYS, "policy"));
|
|
2129
2074
|
if (p.rego !== void 0) out.push(...repoPath(p.rego, "policy.rego"));
|
|
@@ -2173,6 +2118,7 @@ var DEFAULT_REVIEW_STACK_NAME, TTL, REVIEW_STACK_NAME, STACK_KEY, reviewStacks;
|
|
|
2173
2118
|
var init_review_stacks = __esm({
|
|
2174
2119
|
"src/config/review-stacks.ts"() {
|
|
2175
2120
|
"use strict";
|
|
2121
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
2176
2122
|
init_types();
|
|
2177
2123
|
DEFAULT_REVIEW_STACK_NAME = "pr-{number}";
|
|
2178
2124
|
TTL = /^\d+[mhd]$/;
|
|
@@ -2227,6 +2173,7 @@ var FORGES, GLOB_MAP, copy, routing;
|
|
|
2227
2173
|
var init_routing = __esm({
|
|
2228
2174
|
"src/config/routing.ts"() {
|
|
2229
2175
|
"use strict";
|
|
2176
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
2230
2177
|
init_types();
|
|
2231
2178
|
FORGES = ["github", "gitlab", "forgejo"];
|
|
2232
2179
|
GLOB_MAP = {
|
|
@@ -2322,6 +2269,7 @@ var GROUPS, CONFIG_NAMES, ConfigError;
|
|
|
2322
2269
|
var init_config = __esm({
|
|
2323
2270
|
"src/config/index.ts"() {
|
|
2324
2271
|
"use strict";
|
|
2272
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
2325
2273
|
init_agents();
|
|
2326
2274
|
init_apply();
|
|
2327
2275
|
init_approval();
|
|
@@ -2362,6 +2310,7 @@ function toChantSettings(config2) {
|
|
|
2362
2310
|
var init_chant_config = __esm({
|
|
2363
2311
|
"src/chant-config.ts"() {
|
|
2364
2312
|
"use strict";
|
|
2313
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
2365
2314
|
init_config();
|
|
2366
2315
|
}
|
|
2367
2316
|
});
|
|
@@ -2385,25 +2334,25 @@ function configCheck(cwd, file) {
|
|
|
2385
2334
|
const lines2 = problems.map((p) => `${path}: ${p.key} ${p.message}${p.severity === "warning" ? " (warning)" : ""}`);
|
|
2386
2335
|
if (errors.length) return { ok: false, lines: lines2, envelope: { file: path, problems } };
|
|
2387
2336
|
const doc = config2.doc;
|
|
2388
|
-
const
|
|
2337
|
+
const engine3 = engineOf(doc);
|
|
2389
2338
|
const locks2 = locksOf(doc);
|
|
2390
2339
|
const later2 = Object.keys(doc).filter((k) => k in LATER_KEYS);
|
|
2391
2340
|
const settings = {
|
|
2392
|
-
engine:
|
|
2341
|
+
engine: engine3?.engine ?? null,
|
|
2393
2342
|
applyWhen: applyWhenOf(doc),
|
|
2394
2343
|
gate: gateOf(doc),
|
|
2395
2344
|
approval: approvalOf(doc),
|
|
2396
2345
|
locksWhen: locks2.when,
|
|
2397
|
-
lockGrain: lockGrainFor(doc,
|
|
2346
|
+
lockGrain: lockGrainFor(doc, engine3?.engine)
|
|
2398
2347
|
};
|
|
2399
2348
|
lines2.push(
|
|
2400
2349
|
`${path}: ok`,
|
|
2401
|
-
` engine ${
|
|
2350
|
+
` engine ${engine3 ? `${engine3.engine} (${engine3.because})` : "not set here; chant reads PULUMI_BACKEND_URL, then Pulumi.yaml"}`,
|
|
2402
2351
|
` apply.when ${settings.applyWhen}`,
|
|
2403
2352
|
` gate ${settings.gate}`,
|
|
2404
2353
|
` approval ${settings.approval}`,
|
|
2405
2354
|
` locks.when ${locks2.when} (ci.locks ${CI_LOCKS_OF[locks2.when]})`,
|
|
2406
|
-
` locks.grain ${locks2.grain ? `${locks2.grain} (ci.lockGrain ${LOCK_GRAIN_OF[locks2.grain]})` : LOCK_GRAIN_WORDS.perEngine(
|
|
2355
|
+
` locks.grain ${locks2.grain ? `${locks2.grain} (ci.lockGrain ${LOCK_GRAIN_OF[locks2.grain]})` : LOCK_GRAIN_WORDS.perEngine(engine3?.engine)}`,
|
|
2407
2356
|
` policy ${policyLine(doc)}`,
|
|
2408
2357
|
...later2.length ? [` read as written, not yet passed to chant: ${later2.join(", ")}`] : []
|
|
2409
2358
|
);
|
|
@@ -2424,6 +2373,7 @@ function policyLine(doc) {
|
|
|
2424
2373
|
var init_config_check = __esm({
|
|
2425
2374
|
"src/config-check.ts"() {
|
|
2426
2375
|
"use strict";
|
|
2376
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
2427
2377
|
init_chant_config();
|
|
2428
2378
|
init_apply();
|
|
2429
2379
|
init_approval();
|
|
@@ -2438,315 +2388,43 @@ var init_config_check = __esm({
|
|
|
2438
2388
|
});
|
|
2439
2389
|
|
|
2440
2390
|
// src/chant-product.ts
|
|
2441
|
-
function hasChant4620(chant) {
|
|
2442
|
-
return typeof chant.prCommentPreviewJob === "function" && typeof chant.PR_PREVIEW_FLOOR === "string";
|
|
2443
|
-
}
|
|
2444
|
-
function hasChant4607(chant) {
|
|
2445
|
-
return typeof chant.pendingGatesByPlan === "function" && typeof chant.parseRequiresList === "function";
|
|
2446
|
-
}
|
|
2447
|
-
function needsChantMessage(what) {
|
|
2448
|
-
return `${what} needs chant >= the release after 0.124.54, which carries @intentius/chant/ci-product (chant#4550). This plywood was built with chant ${BUILT_CHANT}, which lacks it. Upgrade plywood once that chant is out, or build against a local chant with 'just chant-local'.`;
|
|
2449
|
-
}
|
|
2450
|
-
function isChantProduct(m) {
|
|
2451
|
-
if (!m || typeof m !== "object") return false;
|
|
2452
|
-
const r = m;
|
|
2453
|
-
return REQUIRED2.every((name) => typeof r[name] === "function");
|
|
2454
|
-
}
|
|
2455
2391
|
function chantProduct() {
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
const m = await import("@intentius/chant/ci-product");
|
|
2459
|
-
return isChantProduct(m) ? m : void 0;
|
|
2460
|
-
} catch {
|
|
2461
|
-
return void 0;
|
|
2462
|
-
}
|
|
2463
|
-
})();
|
|
2464
|
-
return cached2;
|
|
2392
|
+
cached ??= import("@intentius/chant/ci-product").then((m) => m);
|
|
2393
|
+
return cached;
|
|
2465
2394
|
}
|
|
2466
|
-
var
|
|
2395
|
+
var cached;
|
|
2467
2396
|
var init_chant_product = __esm({
|
|
2468
2397
|
"src/chant-product.ts"() {
|
|
2469
2398
|
"use strict";
|
|
2470
|
-
|
|
2471
|
-
BUILT_CHANT = true ? "0.124.67" : "unknown";
|
|
2472
|
-
}
|
|
2473
|
-
});
|
|
2474
|
-
|
|
2475
|
-
// src/chant-next.ts
|
|
2476
|
-
function hasChantRespond(chant) {
|
|
2477
|
-
return typeof chant?.CI_RESPOND_FLOOR === "string";
|
|
2478
|
-
}
|
|
2479
|
-
function hasChantPrLoopFlags(chant) {
|
|
2480
|
-
return typeof chant?.PR_LOOP_BUILD_FLAGS_FLOOR === "string";
|
|
2481
|
-
}
|
|
2482
|
-
function hasChantApprovalStatus(chant) {
|
|
2483
|
-
return typeof chant?.APPROVAL_STATUS_FLOOR === "string";
|
|
2484
|
-
}
|
|
2485
|
-
function hasChantPullRequestApprovalStatus(chant) {
|
|
2486
|
-
return typeof chant?.APPROVAL_STATUS_PULL_REQUEST_FLOOR === "string";
|
|
2487
|
-
}
|
|
2488
|
-
function hasChantWavePreview() {
|
|
2489
|
-
wavePreview ??= (async () => {
|
|
2490
|
-
try {
|
|
2491
|
-
const m = await import("@intentius/chant/op");
|
|
2492
|
-
return typeof m.OP_WAVES_PREVIEW_FLOOR === "string";
|
|
2493
|
-
} catch {
|
|
2494
|
-
return false;
|
|
2495
|
-
}
|
|
2496
|
-
})();
|
|
2497
|
-
return wavePreview;
|
|
2498
|
-
}
|
|
2499
|
-
function withWavePreview(spec, forge) {
|
|
2500
|
-
const approvals = spec.approvals && typeof spec.approvals === "object" && !Array.isArray(spec.approvals) ? spec.approvals : {};
|
|
2501
|
-
const next = approvals.preview !== void 0 ? spec : { ...spec, approvals: { ...approvals, preview: true } };
|
|
2502
|
-
if (forge === "gitlab" || next.approvals.preview !== true || !Array.isArray(next.waves)) return next;
|
|
2503
|
-
const waves = next.waves.map((w) => ({
|
|
2504
|
-
...w,
|
|
2505
|
-
variables: { [WAVE_TOKEN_VARIABLE]: "${{ github.token }}", ...w.variables ?? {} }
|
|
2506
|
-
}));
|
|
2507
|
-
return { ...next, waves };
|
|
2508
|
-
}
|
|
2509
|
-
function chantAudit() {
|
|
2510
|
-
audit ??= import("@intentius/chant/ci-audit").then((m) => m);
|
|
2511
|
-
return audit;
|
|
2512
|
-
}
|
|
2513
|
-
var wavePreview, WAVE_TOKEN_VARIABLE, GITLAB_READ_TOKEN, audit;
|
|
2514
|
-
var init_chant_next = __esm({
|
|
2515
|
-
"src/chant-next.ts"() {
|
|
2516
|
-
"use strict";
|
|
2517
|
-
WAVE_TOKEN_VARIABLE = "GITHUB_TOKEN";
|
|
2518
|
-
GITLAB_READ_TOKEN = "CHANT_FORGE_TOKEN";
|
|
2519
|
-
}
|
|
2520
|
-
});
|
|
2521
|
-
|
|
2522
|
-
// src/init/own-jobs.ts
|
|
2523
|
-
function indentOf(line) {
|
|
2524
|
-
return line.length - line.trimStart().length;
|
|
2525
|
-
}
|
|
2526
|
-
function isContent(line) {
|
|
2527
|
-
const t = line.trim();
|
|
2528
|
-
return t !== "" && !t.startsWith("#");
|
|
2529
|
-
}
|
|
2530
|
-
function jobBlocks(text, forge) {
|
|
2531
|
-
const lines2 = text.split("\n");
|
|
2532
|
-
const out = /* @__PURE__ */ new Map();
|
|
2533
|
-
let start = 0;
|
|
2534
|
-
let end = lines2.length;
|
|
2535
|
-
let depth = 0;
|
|
2536
|
-
if (forge !== "gitlab") {
|
|
2537
|
-
const at = lines2.findIndex((l) => /^jobs:\s*(#.*)?$/.test(l));
|
|
2538
|
-
if (at < 0) return out;
|
|
2539
|
-
start = at + 1;
|
|
2540
|
-
const next = lines2.findIndex((l, i) => i > at && indentOf(l) === 0 && isContent(l));
|
|
2541
|
-
end = next < 0 ? lines2.length : next;
|
|
2542
|
-
depth = 2;
|
|
2543
|
-
}
|
|
2544
|
-
let name;
|
|
2545
|
-
let body = [];
|
|
2546
|
-
const flush = () => {
|
|
2547
|
-
if (name !== void 0) {
|
|
2548
|
-
while (body.length > 0 && body[body.length - 1].trim() === "") body.pop();
|
|
2549
|
-
out.set(name, body.join("\n"));
|
|
2550
|
-
}
|
|
2551
|
-
name = void 0;
|
|
2552
|
-
body = [];
|
|
2553
|
-
};
|
|
2554
|
-
for (let i = start; i < end; i++) {
|
|
2555
|
-
const line = lines2[i];
|
|
2556
|
-
if (isContent(line) && indentOf(line) === depth) {
|
|
2557
|
-
const m = KEY.exec(line.slice(depth));
|
|
2558
|
-
flush();
|
|
2559
|
-
if (m) {
|
|
2560
|
-
name = m[1].replace(/^["']|["']$/g, "");
|
|
2561
|
-
body = [line];
|
|
2562
|
-
}
|
|
2563
|
-
continue;
|
|
2564
|
-
}
|
|
2565
|
-
if (isContent(line) && indentOf(line) < depth) {
|
|
2566
|
-
flush();
|
|
2567
|
-
continue;
|
|
2568
|
-
}
|
|
2569
|
-
if (name !== void 0) body.push(line);
|
|
2570
|
-
}
|
|
2571
|
-
flush();
|
|
2572
|
-
if (forge === "gitlab") {
|
|
2573
|
-
for (const k of [...out.keys()]) if (GITLAB_KEYWORDS.has(k)) out.delete(k);
|
|
2574
|
-
}
|
|
2575
|
-
return out;
|
|
2576
|
-
}
|
|
2577
|
-
function ownJobs(previous, generated, forge) {
|
|
2578
|
-
const ours = jobBlocks(generated, forge);
|
|
2579
|
-
const out = /* @__PURE__ */ new Map();
|
|
2580
|
-
for (const [name, block] of jobBlocks(previous, forge)) {
|
|
2581
|
-
if (ours.has(name) || PLYWOOD_LINE.test(block)) continue;
|
|
2582
|
-
out.set(name, block);
|
|
2583
|
-
}
|
|
2584
|
-
return out;
|
|
2585
|
-
}
|
|
2586
|
-
function withOwnJobs(generated, own, forge) {
|
|
2587
|
-
if (own.size === 0) return generated;
|
|
2588
|
-
const indent = forge === "gitlab" ? "" : " ";
|
|
2589
|
-
const block = [`${indent}${OWN_JOBS_LINE}`, ...own.values()].join("\n");
|
|
2590
|
-
const lines2 = generated.replace(/\n+$/, "").split("\n");
|
|
2591
|
-
if (forge !== "gitlab") {
|
|
2592
|
-
const at = lines2.findIndex((l) => /^jobs:\s*(#.*)?$/.test(l));
|
|
2593
|
-
if (at >= 0) {
|
|
2594
|
-
const next = lines2.findIndex((l, i) => i > at && indentOf(l) === 0 && isContent(l));
|
|
2595
|
-
if (next >= 0) {
|
|
2596
|
-
let cut = next;
|
|
2597
|
-
while (cut > at + 1 && lines2[cut - 1].trim() === "") cut--;
|
|
2598
|
-
return [...lines2.slice(0, cut), block, ...lines2.slice(cut)].join("\n") + "\n";
|
|
2599
|
-
}
|
|
2600
|
-
}
|
|
2601
|
-
}
|
|
2602
|
-
return [...lines2, block].join("\n") + "\n";
|
|
2603
|
-
}
|
|
2604
|
-
function keepOwnJobs(previous, generated, forge) {
|
|
2605
|
-
if (previous === void 0) return { text: generated, kept: [] };
|
|
2606
|
-
const own = ownJobs(previous, generated, forge);
|
|
2607
|
-
return { text: withOwnJobs(generated, own, forge), kept: [...own.keys()] };
|
|
2608
|
-
}
|
|
2609
|
-
var OWN_JOBS_LINE, GITLAB_KEYWORDS, PLYWOOD_LINE, KEY;
|
|
2610
|
-
var init_own_jobs = __esm({
|
|
2611
|
-
"src/init/own-jobs.ts"() {
|
|
2612
|
-
"use strict";
|
|
2613
|
-
OWN_JOBS_LINE = "# Your own jobs, kept as they are by plywood init and plywood ci.";
|
|
2614
|
-
GITLAB_KEYWORDS = /* @__PURE__ */ new Set(["default", "include", "stages", "variables", "workflow", "image", "services", "cache", "before_script", "after_script", "spec"]);
|
|
2615
|
-
PLYWOOD_LINE = /\bplywood\b\s+(components|ci|run|lock|unlock|check|preview|up|drift)\b/;
|
|
2616
|
-
KEY = /^([A-Za-z0-9_.-]+|"[^"]+"|'[^']+'):(\s|$)/;
|
|
2617
|
-
}
|
|
2618
|
-
});
|
|
2619
|
-
|
|
2620
|
-
// src/init/note-control.ts
|
|
2621
|
-
import { emitYAML } from "@intentius/chant/yaml";
|
|
2622
|
-
function indentOf2(line) {
|
|
2623
|
-
return line.length - line.trimStart().length;
|
|
2624
|
-
}
|
|
2625
|
-
function jobRange(lines2, name, depth) {
|
|
2626
|
-
const head = new RegExp(`^${" ".repeat(depth)}${name}:\\s*(#.*)?$`);
|
|
2627
|
-
const start = lines2.findIndex((l) => head.test(l));
|
|
2628
|
-
if (start < 0) return void 0;
|
|
2629
|
-
let end = lines2.length;
|
|
2630
|
-
for (let i = start + 1; i < lines2.length; i++) {
|
|
2631
|
-
const l = lines2[i];
|
|
2632
|
-
if (l.trim() === "" || l.trimStart().startsWith("#")) continue;
|
|
2633
|
-
if (indentOf2(l) <= depth) {
|
|
2634
|
-
end = i;
|
|
2635
|
-
break;
|
|
2636
|
-
}
|
|
2637
|
-
}
|
|
2638
|
-
return [start, end];
|
|
2639
|
-
}
|
|
2640
|
-
function withNoteControl(text, forge, step) {
|
|
2641
|
-
if (!step) return text;
|
|
2642
|
-
const lines2 = text.split("\n");
|
|
2643
|
-
const range = jobRange(lines2, NOTE_JOB, forge === "gitlab" ? 0 : 2);
|
|
2644
|
-
if (!range) return text;
|
|
2645
|
-
const [start, end] = range;
|
|
2646
|
-
const job = lines2.slice(start, end);
|
|
2647
|
-
if (job.some((l) => l.includes(step.stepName)) || job.some((l) => l.includes("CHANT_CONTROL_DIR="))) return text;
|
|
2648
|
-
if (forge === "gitlab") {
|
|
2649
|
-
const at2 = job.findIndex((l) => /^ {4}- /.test(l) && l.includes("components pr-post"));
|
|
2650
|
-
if (at2 < 0) return text;
|
|
2651
|
-
const item2 = emitYAML([step.script], 2).replace(/^\n/, "");
|
|
2652
|
-
return [...lines2.slice(0, start + at2), ...item2.split("\n"), ...lines2.slice(start + at2)].join("\n");
|
|
2653
|
-
}
|
|
2654
|
-
const at = job.findIndex((l) => /^ {6}- name: Post the plan note\b/.test(l));
|
|
2655
|
-
if (at < 0) return text;
|
|
2656
|
-
const item = emitYAML([{ name: step.stepName, if: "always()", ...Object.keys(step.env).length ? { env: step.env } : {}, run: step.script }], 3).replace(/^\n/, "");
|
|
2657
|
-
return [...lines2.slice(0, start + at), ...item.split("\n"), ...lines2.slice(start + at)].join("\n");
|
|
2658
|
-
}
|
|
2659
|
-
var NOTE_JOB;
|
|
2660
|
-
var init_note_control = __esm({
|
|
2661
|
-
"src/init/note-control.ts"() {
|
|
2662
|
-
"use strict";
|
|
2663
|
-
NOTE_JOB = "plan-note";
|
|
2664
|
-
}
|
|
2665
|
-
});
|
|
2666
|
-
|
|
2667
|
-
// src/init/run-group.ts
|
|
2668
|
-
function withRunGroup(text, forge, key) {
|
|
2669
|
-
if (forge !== "forgejo" || TOP_CONCURRENCY.test(text)) return text;
|
|
2670
|
-
const jobs2 = TOP_JOBS.exec(text);
|
|
2671
|
-
if (!jobs2) return text;
|
|
2672
|
-
const name = (TOP_NAME.exec(text)?.[1] ?? "plywood").replace(/^["']|["']$/g, "");
|
|
2673
|
-
const group = key === "commit" ? `${name}-\${{ github.event_name }}-\${{ github.sha }}` : `${name}-\${{ github.event_name == 'pull_request' && github.ref || github.run_id }}`;
|
|
2674
|
-
const cancel = key === "commit" ? "false" : "${{ github.event_name == 'pull_request' }}";
|
|
2675
|
-
const block = [
|
|
2676
|
-
"# Without a group Forgejo cancels the run in progress when a push starts another run of this workflow.",
|
|
2677
|
-
key === "commit" ? "# One group for each commit keeps a merge's wave jobs running to completion when another merge lands (plywood#51)." : "# A push to main and a comment get a group of their own, so the record job runs to completion (plywood#51).",
|
|
2678
|
-
"concurrency:",
|
|
2679
|
-
` group: ${yamlScalar(group)}`,
|
|
2680
|
-
` cancel-in-progress: ${yamlScalar(cancel)}`,
|
|
2681
|
-
"",
|
|
2682
|
-
""
|
|
2683
|
-
].join("\n");
|
|
2684
|
-
return text.slice(0, jobs2.index) + block + text.slice(jobs2.index);
|
|
2685
|
-
}
|
|
2686
|
-
function yamlScalar(value) {
|
|
2687
|
-
return value === "false" ? value : JSON.stringify(value);
|
|
2688
|
-
}
|
|
2689
|
-
var TOP_CONCURRENCY, TOP_JOBS, TOP_NAME;
|
|
2690
|
-
var init_run_group = __esm({
|
|
2691
|
-
"src/init/run-group.ts"() {
|
|
2692
|
-
"use strict";
|
|
2693
|
-
TOP_CONCURRENCY = /^concurrency:/m;
|
|
2694
|
-
TOP_JOBS = /^jobs:/m;
|
|
2695
|
-
TOP_NAME = /^name:\s*(.+?)\s*$/m;
|
|
2399
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
2696
2400
|
}
|
|
2697
2401
|
});
|
|
2698
2402
|
|
|
2699
2403
|
// src/init/workflow.ts
|
|
2700
2404
|
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
2701
2405
|
import { dirname as dirname2, isAbsolute, join as join2 } from "node:path";
|
|
2406
|
+
import { jobBlocks, ownJobsOf } from "@intentius/chant/ci-own-jobs";
|
|
2702
2407
|
async function writeWorkflow(chant, input) {
|
|
2703
2408
|
const file = isAbsolute(input.output) ? input.output : join2(input.root, input.output);
|
|
2704
2409
|
const previous = existsSync2(file) ? readFileSync2(file, "utf8") : void 0;
|
|
2705
2410
|
mkdirSync(dirname2(file), { recursive: true });
|
|
2706
|
-
const code = await chant.runChantInProcess([...input.argv, "--output", file], input.words);
|
|
2411
|
+
const code = await chant.runChantInProcess([...input.argv, "--keep-own-jobs", "--run-group", "pull-request", "--output", file], input.words);
|
|
2707
2412
|
if (code !== 0 || !existsSync2(file)) {
|
|
2708
2413
|
if (previous !== void 0) writeFileSync(file, previous);
|
|
2709
2414
|
return { code: code !== 0 ? code : 1, file, status: "failed", kept: [] };
|
|
2710
2415
|
}
|
|
2711
|
-
const
|
|
2712
|
-
const
|
|
2713
|
-
writeFileSync(file, text);
|
|
2416
|
+
const text = readFileSync2(file, "utf8");
|
|
2417
|
+
const kept = previous === void 0 ? [] : keptJobs(previous, text, input.forge, input.words.bin);
|
|
2714
2418
|
return { code, file, status: text === previous ? "unchanged" : "written", kept };
|
|
2715
2419
|
}
|
|
2420
|
+
function keptJobs(previous, written, forge, bin) {
|
|
2421
|
+
const now = jobBlocks(written, forge);
|
|
2422
|
+
return [...ownJobsOf(previous, "", forge, bin ? [bin] : [])].filter(([name, block]) => now.get(name) === block).map(([name]) => name);
|
|
2423
|
+
}
|
|
2716
2424
|
var init_workflow = __esm({
|
|
2717
2425
|
"src/init/workflow.ts"() {
|
|
2718
2426
|
"use strict";
|
|
2719
|
-
|
|
2720
|
-
init_note_control();
|
|
2721
|
-
init_run_group();
|
|
2722
|
-
}
|
|
2723
|
-
});
|
|
2724
|
-
|
|
2725
|
-
// src/review-stacks.ts
|
|
2726
|
-
function chantReviewStacks() {
|
|
2727
|
-
cached3 ??= (async () => {
|
|
2728
|
-
try {
|
|
2729
|
-
const m = await import("@intentius/chant-lexicon-pulumi");
|
|
2730
|
-
const plugin = m.pulumiPlugin;
|
|
2731
|
-
return typeof m.initReviewStack === "function" && typeof m.removeReviewStack === "function" && typeof plugin?.prEnvTemplate === "function" && typeof plugin?.prEnvTeardown === "function";
|
|
2732
|
-
} catch {
|
|
2733
|
-
return false;
|
|
2734
|
-
}
|
|
2735
|
-
})();
|
|
2736
|
-
return cached3;
|
|
2737
|
-
}
|
|
2738
|
-
function reviewSuffixOf(pattern) {
|
|
2739
|
-
const m = pattern ? /^pr-\{number\}-([a-z0-9][a-z0-9-]*)$/.exec(pattern) : null;
|
|
2740
|
-
return m ? m[1] : void 0;
|
|
2741
|
-
}
|
|
2742
|
-
var REVIEW_GATE, REVIEW_GATE_WORD, reviewOp, cached3, REVIEW_STACKS_NEED_CHANT;
|
|
2743
|
-
var init_review_stacks2 = __esm({
|
|
2744
|
-
"src/review-stacks.ts"() {
|
|
2745
|
-
"use strict";
|
|
2746
|
-
REVIEW_GATE = "pr-env";
|
|
2747
|
-
REVIEW_GATE_WORD = "review";
|
|
2748
|
-
reviewOp = (pr) => `pr-${pr}`;
|
|
2749
|
-
REVIEW_STACKS_NEED_CHANT = "review_stacks is set, and the installed @intentius/chant-lexicon-pulumi cannot run a review stack end to end (initReviewStack, removeReviewStack, and the plugin's prEnvTemplate and prEnvTeardown from chant#4609), so plywood writes no ci.prEnvironment and the workflow has no review stack jobs yet";
|
|
2427
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
2750
2428
|
}
|
|
2751
2429
|
});
|
|
2752
2430
|
|
|
@@ -2754,11 +2432,11 @@ var init_review_stacks2 = __esm({
|
|
|
2754
2432
|
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "node:fs";
|
|
2755
2433
|
import { dirname as dirname3, join as join3 } from "node:path";
|
|
2756
2434
|
function wavesEngine(pipeline) {
|
|
2757
|
-
const o = chantPipelineOptions({ ...pipeline.image ? { image: pipeline.image } : {}, ...pipeline.engineSetup ? { engineSetup: pipeline.engineSetup } : {} }
|
|
2435
|
+
const o = chantPipelineOptions({ ...pipeline.image ? { image: pipeline.image } : {}, ...pipeline.engineSetup ? { engineSetup: pipeline.engineSetup } : {} });
|
|
2758
2436
|
return { ...o.image ? { image: o.image } : {}, ...o.beforeScript?.length ? { beforeScript: o.beforeScript } : {} };
|
|
2759
2437
|
}
|
|
2760
|
-
function wavesSpecText(spec,
|
|
2761
|
-
const options = { ...
|
|
2438
|
+
function wavesSpecText(spec, engine3 = {}) {
|
|
2439
|
+
const options = { ...engine3.image ? { image: engine3.image } : {}, ...engine3.beforeScript?.length ? { beforeScript: engine3.beforeScript } : {} };
|
|
2762
2440
|
return JSON.stringify(Object.keys(options).length ? { waves: spec, options } : spec, null, 2) + "\n";
|
|
2763
2441
|
}
|
|
2764
2442
|
function unwrapWavesSpec(doc) {
|
|
@@ -2821,28 +2499,13 @@ function writeUpOp(cwd, spec) {
|
|
|
2821
2499
|
writeFileSync2(path, text);
|
|
2822
2500
|
return true;
|
|
2823
2501
|
}
|
|
2824
|
-
function binLines(waves, bin) {
|
|
2825
|
-
const binArgs = bin.split(/\s+/).filter(Boolean);
|
|
2826
|
-
return waves.jobs.map((job) => {
|
|
2827
|
-
const own = job.command.slice(0, binArgs.length).join(" ") === binArgs.join(" ");
|
|
2828
|
-
const rest = own ? job.command.slice(binArgs.length) : job.command.slice(1);
|
|
2829
|
-
return { chant: ["chant", ...rest].join(" "), product: [...binArgs, ...rest].join(" ") };
|
|
2830
|
-
});
|
|
2831
|
-
}
|
|
2832
|
-
function withBinLines(text, lines2) {
|
|
2833
|
-
const sorted = [...lines2].sort((a, b) => b.chant.length - a.chant.length);
|
|
2834
|
-
return text.replace(/chant run wave [^\n"']*/g, (found) => {
|
|
2835
|
-
const hit = sorted.find((l) => found.trimEnd() === l.chant);
|
|
2836
|
-
return hit ? hit.product : found;
|
|
2837
|
-
});
|
|
2838
|
-
}
|
|
2839
2502
|
function generatedFor(dir, specFile) {
|
|
2840
2503
|
if (!existsSync3(dir)) return [];
|
|
2841
2504
|
return readdirSync(dir).filter((f) => /\.ya?ml$/.test(f)).map((f) => join3(dir, f)).filter((p) => (readFileSync3(p, "utf8").split("\n", 1)[0] ?? "").includes(`--spec ${specFile}`));
|
|
2842
2505
|
}
|
|
2843
|
-
async function writeWaveJobs(chant, forge, waves, words, table, io,
|
|
2506
|
+
async function writeWaveJobs(chant, forge, waves, words, table, io, engine3 = {}) {
|
|
2844
2507
|
const specPath = join3(io.cwd, waves.specFile);
|
|
2845
|
-
const specText = wavesSpecText(waves.spec,
|
|
2508
|
+
const specText = wavesSpecText(waves.spec, engine3);
|
|
2846
2509
|
const before = existsSync3(specPath) ? readFileSync3(specPath, "utf8") : void 0;
|
|
2847
2510
|
if (before !== specText) {
|
|
2848
2511
|
mkdirSync2(dirname3(specPath), { recursive: true });
|
|
@@ -2851,18 +2514,14 @@ async function writeWaveJobs(chant, forge, waves, words, table, io, engine2 = {}
|
|
|
2851
2514
|
}
|
|
2852
2515
|
if (writeUpOp(io.cwd, waves.spec)) io.err(`${PRODUCT} ci: wrote ${UP_OP_FILE}, the Op the waves spec names; commit it with the workflow`);
|
|
2853
2516
|
const outDir = WAVES_OUTPUT_DIR[forge];
|
|
2854
|
-
const code = await chant.runChantInProcess(["run", "--generate", forge, "--spec", waves.specFile, "--output", outDir], words);
|
|
2517
|
+
const code = await chant.runChantInProcess(["run", "--generate", forge, "--spec", waves.specFile, "--run-group", "commit", "--output", outDir], words);
|
|
2855
2518
|
if (code !== table.done) return code;
|
|
2856
2519
|
const files = generatedFor(join3(io.cwd, outDir), waves.specFile);
|
|
2857
2520
|
if (files.length === 0) {
|
|
2858
2521
|
io.err(`${PRODUCT} ci: chant wrote no waves workflow for ${waves.specFile} under ${outDir}`);
|
|
2859
2522
|
return table.failed;
|
|
2860
2523
|
}
|
|
2861
|
-
const lines2 = hasChant4607(chant) ? void 0 : binLines(waves, words.bin);
|
|
2862
2524
|
for (const file of files) {
|
|
2863
|
-
const text = readFileSync3(file, "utf8");
|
|
2864
|
-
const next = withRunGroup(lines2 ? withBinLines(text, lines2) : text, forge, "commit");
|
|
2865
|
-
if (next !== text) writeFileSync2(file, next);
|
|
2866
2525
|
io.err(`${PRODUCT} ci: waves workflow at ${file.slice(io.cwd.length + 1)}`);
|
|
2867
2526
|
}
|
|
2868
2527
|
if (forge === "gitlab") {
|
|
@@ -2874,9 +2533,8 @@ var WAVES_OUTPUT_DIR, UP_OP_FILE;
|
|
|
2874
2533
|
var init_waves_workflow = __esm({
|
|
2875
2534
|
"src/waves-workflow.ts"() {
|
|
2876
2535
|
"use strict";
|
|
2877
|
-
|
|
2536
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
2878
2537
|
init_images();
|
|
2879
|
-
init_run_group();
|
|
2880
2538
|
init_words();
|
|
2881
2539
|
WAVES_OUTPUT_DIR = {
|
|
2882
2540
|
github: ".github/workflows",
|
|
@@ -2887,12 +2545,34 @@ var init_waves_workflow = __esm({
|
|
|
2887
2545
|
}
|
|
2888
2546
|
});
|
|
2889
2547
|
|
|
2548
|
+
// src/chant-next.ts
|
|
2549
|
+
function withWavePreview(spec, forge) {
|
|
2550
|
+
const approvals = spec.approvals && typeof spec.approvals === "object" && !Array.isArray(spec.approvals) ? spec.approvals : {};
|
|
2551
|
+
const next = approvals.preview !== void 0 ? spec : { ...spec, approvals: { ...approvals, preview: true } };
|
|
2552
|
+
if (forge === "gitlab" || next.approvals.preview !== true || !Array.isArray(next.waves)) return next;
|
|
2553
|
+
const waves = next.waves.map((w) => ({
|
|
2554
|
+
...w,
|
|
2555
|
+
variables: { [WAVE_TOKEN_VARIABLE]: "${{ github.token }}", ...w.variables ?? {} }
|
|
2556
|
+
}));
|
|
2557
|
+
return { ...next, waves };
|
|
2558
|
+
}
|
|
2559
|
+
function chantAudit() {
|
|
2560
|
+
audit ??= import("@intentius/chant/ci-audit").then((m) => m);
|
|
2561
|
+
return audit;
|
|
2562
|
+
}
|
|
2563
|
+
var WAVE_TOKEN_VARIABLE, GITLAB_READ_TOKEN, audit;
|
|
2564
|
+
var init_chant_next = __esm({
|
|
2565
|
+
"src/chant-next.ts"() {
|
|
2566
|
+
"use strict";
|
|
2567
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
2568
|
+
WAVE_TOKEN_VARIABLE = "GITHUB_TOKEN";
|
|
2569
|
+
GITLAB_READ_TOKEN = "CHANT_FORGE_TOKEN";
|
|
2570
|
+
}
|
|
2571
|
+
});
|
|
2572
|
+
|
|
2890
2573
|
// src/agent-jobs.ts
|
|
2891
2574
|
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
2892
2575
|
import { dirname as dirname4, join as join4 } from "node:path";
|
|
2893
|
-
function hasChantAgent(chant) {
|
|
2894
|
-
return typeof chant?.CI_AGENT_FLOOR === "string";
|
|
2895
|
-
}
|
|
2896
2576
|
function stageRunner(runner, stage) {
|
|
2897
2577
|
if (runner === void 0) return void 0;
|
|
2898
2578
|
if (typeof runner === "string" || Array.isArray(runner) || "group" in runner) return runner;
|
|
@@ -2915,22 +2595,21 @@ function chantAgentOptions(a, doc) {
|
|
|
2915
2595
|
};
|
|
2916
2596
|
}
|
|
2917
2597
|
function driftOpFields(chant, doc, op, respond) {
|
|
2918
|
-
if (!hasChantAgent(chant)) return {};
|
|
2919
2598
|
const runner = stageRunner(toChantSettings({ doc }).pipeline.runner, "drift");
|
|
2920
2599
|
const a = agentOf(doc);
|
|
2921
2600
|
const wantsAgent = respond === "pull-request" && a?.drift === true;
|
|
2922
|
-
const agent = wantsAgent ? chant.productDriftAgent
|
|
2601
|
+
const agent = wantsAgent ? chant.productDriftAgent(PRODUCT_WORDS, op, chantAgentOptions(a, doc)) : void 0;
|
|
2923
2602
|
return { ...runner !== void 0 ? { runner } : {}, ...agent ? { agent } : {} };
|
|
2924
2603
|
}
|
|
2925
2604
|
function agentOpSpec(spec, doc) {
|
|
2926
2605
|
const p = toChantSettings({ doc }).pipeline;
|
|
2927
|
-
const
|
|
2606
|
+
const engine3 = chantPipelineOptions({ ...p.image ? { image: p.image } : {}, ...p.engineSetup ? { engineSetup: p.engineSetup } : {} });
|
|
2928
2607
|
const runner = stageRunner(p.runner, "preview");
|
|
2929
2608
|
return {
|
|
2930
2609
|
ops: [{ ...spec, ...runner !== void 0 && spec.runner === void 0 ? { runner } : {} }],
|
|
2931
2610
|
options: {
|
|
2932
|
-
...
|
|
2933
|
-
...
|
|
2611
|
+
...engine3.beforeScript ? { beforeScript: engine3.beforeScript } : {},
|
|
2612
|
+
...engine3.image ? { image: engine3.image } : {}
|
|
2934
2613
|
}
|
|
2935
2614
|
};
|
|
2936
2615
|
}
|
|
@@ -2944,10 +2623,6 @@ function writeIfChanged(cwd, file, text) {
|
|
|
2944
2623
|
async function writeAgentJobs(chant, forge, env, doc, words, table, io) {
|
|
2945
2624
|
const a = agentOf(doc);
|
|
2946
2625
|
if (!a?.comment) return table.done;
|
|
2947
|
-
if (!hasChantAgent(chant)) {
|
|
2948
|
-
io.err(`${PRODUCT} ci: ${AGENT_NEEDS_CHANT}`);
|
|
2949
|
-
return table.done;
|
|
2950
|
-
}
|
|
2951
2626
|
const agentWords = { ...words, commands: { ...words.commands, agent: "agent" } };
|
|
2952
2627
|
let spec;
|
|
2953
2628
|
try {
|
|
@@ -2973,10 +2648,11 @@ async function writeAgentJobs(chant, forge, env, doc, words, table, io) {
|
|
|
2973
2648
|
if (forge === "gitlab") io.err(`${PRODUCT} ci: on GitLab a pipeline of the default branch started with CHANT_AGENT_PR and CHANT_AGENT_ASK runs the agent; scope CHANT_FORGE_TOKEN to environment ${AGENT_OP}-push`);
|
|
2974
2649
|
return table.done;
|
|
2975
2650
|
}
|
|
2976
|
-
var AGENT_OP, AGENT_SPEC_FILE,
|
|
2651
|
+
var AGENT_OP, AGENT_SPEC_FILE, OP_OUTPUT_DIR;
|
|
2977
2652
|
var init_agent_jobs = __esm({
|
|
2978
2653
|
"src/agent-jobs.ts"() {
|
|
2979
2654
|
"use strict";
|
|
2655
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
2980
2656
|
init_chant_config();
|
|
2981
2657
|
init_agents();
|
|
2982
2658
|
init_config();
|
|
@@ -2984,7 +2660,6 @@ var init_agent_jobs = __esm({
|
|
|
2984
2660
|
init_words();
|
|
2985
2661
|
AGENT_OP = "plywood-agent";
|
|
2986
2662
|
AGENT_SPEC_FILE = ".chant/ops/plywood-agent.spec.json";
|
|
2987
|
-
AGENT_NEEDS_CHANT = "agent needs a chant with the agent job (chant#4616), and the installed one has none, so no agent job was written";
|
|
2988
2663
|
OP_OUTPUT_DIR = { github: ".github/workflows", forgejo: ".forgejo/workflows", gitlab: "." };
|
|
2989
2664
|
}
|
|
2990
2665
|
});
|
|
@@ -3003,7 +2678,6 @@ async function sendDriftNotice(input) {
|
|
|
3003
2678
|
const drifted = input.results.filter((r) => r.ok && r.drifted > 0);
|
|
3004
2679
|
if (drifted.length === 0) return void 0;
|
|
3005
2680
|
const fingerprint = runFingerprint(input.results, input.stackFingerprint);
|
|
3006
|
-
if (!input.chant) return { sent: false, reason: DRIFT_NOTIFY_NEEDS_CHANT, fingerprint };
|
|
3007
2681
|
if (drifted.every((r) => r.issue?.action === "unchanged")) {
|
|
3008
2682
|
return { sent: false, reason: "no drift event was sent: every drifted stack's finding is the one its issue already holds", fingerprint };
|
|
3009
2683
|
}
|
|
@@ -3021,62 +2695,39 @@ async function sendDriftNotice(input) {
|
|
|
3021
2695
|
}
|
|
3022
2696
|
function chantDriftNotify() {
|
|
3023
2697
|
loaded ??= (async () => {
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
makeNotifyEvent: notify2.makeNotifyEvent,
|
|
3033
|
-
sendNotify: notify2.sendNotify
|
|
3034
|
-
};
|
|
3035
|
-
} catch {
|
|
3036
|
-
return void 0;
|
|
3037
|
-
}
|
|
2698
|
+
const product = await import("@intentius/chant/ci-product");
|
|
2699
|
+
const notify2 = await import("@intentius/chant/notify");
|
|
2700
|
+
return {
|
|
2701
|
+
driftNotifyFields: product.driftNotifyFields,
|
|
2702
|
+
productSendOptions: product.productSendOptions,
|
|
2703
|
+
makeNotifyEvent: notify2.makeNotifyEvent,
|
|
2704
|
+
sendNotify: notify2.sendNotify
|
|
2705
|
+
};
|
|
3038
2706
|
})();
|
|
3039
2707
|
return loaded;
|
|
3040
2708
|
}
|
|
3041
|
-
var
|
|
2709
|
+
var loaded;
|
|
3042
2710
|
var init_drift_notify = __esm({
|
|
3043
2711
|
"src/drift-notify.ts"() {
|
|
3044
2712
|
"use strict";
|
|
3045
|
-
|
|
2713
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
3046
2714
|
}
|
|
3047
2715
|
});
|
|
3048
2716
|
|
|
3049
2717
|
// src/jobs.ts
|
|
3050
2718
|
var jobs_exports = {};
|
|
3051
2719
|
__export(jobs_exports, {
|
|
3052
|
-
JOBS_NEED_CHANT: () => JOBS_NEED_CHANT,
|
|
3053
2720
|
environmentFor: () => environmentFor,
|
|
3054
2721
|
groupName: () => groupName,
|
|
3055
|
-
hasChant4625: () => hasChant4625,
|
|
3056
2722
|
jobVariables: () => jobVariables,
|
|
3057
2723
|
prJobsFor: () => prJobsFor,
|
|
3058
2724
|
prLoopJobFlags: () => prLoopJobFlags,
|
|
3059
2725
|
prLoopJobOptions: () => prLoopJobOptions,
|
|
3060
2726
|
prLoopRunners: () => prLoopRunners,
|
|
3061
2727
|
secretReference: () => secretReference,
|
|
3062
|
-
setChant4625ForTests: () => setChant4625ForTests,
|
|
3063
2728
|
wantsChant4625: () => wantsChant4625,
|
|
3064
2729
|
withJobSettings: () => withJobSettings
|
|
3065
2730
|
});
|
|
3066
|
-
function hasChant4625() {
|
|
3067
|
-
detected ??= (async () => {
|
|
3068
|
-
try {
|
|
3069
|
-
const m = await import("@intentius/chant/parallelism");
|
|
3070
|
-
return typeof m.mapLimited === "function" && typeof m.parallelismProblem === "function";
|
|
3071
|
-
} catch {
|
|
3072
|
-
return false;
|
|
3073
|
-
}
|
|
3074
|
-
})();
|
|
3075
|
-
return detected;
|
|
3076
|
-
}
|
|
3077
|
-
function setChant4625ForTests(has) {
|
|
3078
|
-
detected = has === "reset" ? void 0 : Promise.resolve(has);
|
|
3079
|
-
}
|
|
3080
2731
|
function secretReference(forge, secret) {
|
|
3081
2732
|
return forge === "gitlab" ? `$${secret}` : `\${{ secrets.${secret} }}`;
|
|
3082
2733
|
}
|
|
@@ -3099,7 +2750,7 @@ function groupName(environment, taken) {
|
|
|
3099
2750
|
taken.add(name);
|
|
3100
2751
|
return name;
|
|
3101
2752
|
}
|
|
3102
|
-
function withJobSettings(spec, s, forge
|
|
2753
|
+
function withJobSettings(spec, s, forge) {
|
|
3103
2754
|
const variables = jobVariables(forge, s);
|
|
3104
2755
|
const waves = (Array.isArray(spec.waves) ? spec.waves : []).map((wave) => {
|
|
3105
2756
|
const out = { ...wave };
|
|
@@ -3128,7 +2779,7 @@ function withJobSettings(spec, s, forge, opts) {
|
|
|
3128
2779
|
out.groups = [...byEnv.values()].map((g) => ({ name: groupName(g.environment, taken), runs: g.runs, ...g.environment ? { environment: { ...g.environment } } : {} }));
|
|
3129
2780
|
return out;
|
|
3130
2781
|
});
|
|
3131
|
-
return { ...spec, waves, ...
|
|
2782
|
+
return { ...spec, waves, ...s.parallelism !== void 0 && spec.step !== void 0 ? { parallelism: s.parallelism } : {} };
|
|
3132
2783
|
}
|
|
3133
2784
|
function prLoopRunners(runner) {
|
|
3134
2785
|
if (runner === void 0) return void 0;
|
|
@@ -3164,24 +2815,15 @@ function wantsChant4625(s, runner) {
|
|
|
3164
2815
|
}
|
|
3165
2816
|
async function prJobsFor(pipeline) {
|
|
3166
2817
|
if (!wantsChant4625(pipeline.jobs, pipeline.runner)) return { options: {}, flags: [], notes: [] };
|
|
3167
|
-
if (!await hasChant4625()) {
|
|
3168
|
-
const asked = [
|
|
3169
|
-
...pipeline.runner !== void 0 ? ["runners"] : [],
|
|
3170
|
-
...pipeline.jobs && Object.keys(pipeline.jobs.variables).length ? ["env"] : [],
|
|
3171
|
-
...pipeline.jobs && Object.keys(pipeline.jobs.secrets).length ? ["pass"] : [],
|
|
3172
|
-
...pipeline.jobs?.parallelism !== void 0 ? ["parallelism"] : []
|
|
3173
|
-
];
|
|
3174
|
-
return { options: {}, flags: [], notes: [`${asked.join(", ")} on the pull request jobs ${JOBS_NEED_CHANT}; the waves and drift jobs have ${asked.filter((k) => k !== "parallelism").join(", ") || "none of them"}`] };
|
|
3175
|
-
}
|
|
3176
2818
|
const { flags, notes } = prLoopJobFlags(pipeline.jobs, pipeline.runner);
|
|
3177
2819
|
return { options: prLoopJobOptions(pipeline.jobs, pipeline.runner), flags, notes };
|
|
3178
2820
|
}
|
|
3179
|
-
var
|
|
2821
|
+
var GROUP_NAME;
|
|
3180
2822
|
var init_jobs2 = __esm({
|
|
3181
2823
|
"src/jobs.ts"() {
|
|
3182
2824
|
"use strict";
|
|
2825
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
3183
2826
|
init_images2();
|
|
3184
|
-
JOBS_NEED_CHANT = "needs chant's runners, variables, passed secrets and parallelism on the pull request jobs (chant#4625), which this chant lacks";
|
|
3185
2827
|
GROUP_NAME = /[^a-z0-9_-]+/g;
|
|
3186
2828
|
}
|
|
3187
2829
|
});
|
|
@@ -3399,6 +3041,7 @@ var SKIP, MAX_DEPTH, ENV_ORDER, PROD_RANK, WORKFLOW_DIRS;
|
|
|
3399
3041
|
var init_detect = __esm({
|
|
3400
3042
|
"src/init/detect.ts"() {
|
|
3401
3043
|
"use strict";
|
|
3044
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
3402
3045
|
SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "vendor", "venv", ".venv", "__pycache__", "target", "bin", "obj"]);
|
|
3403
3046
|
MAX_DEPTH = 8;
|
|
3404
3047
|
ENV_ORDER = ["local", "dev", "development", "sandbox", "test", "testing", "qa", "ci", "stage", "staging", "preprod", "uat", "prod", "production"];
|
|
@@ -3418,6 +3061,7 @@ function fill(text, values) {
|
|
|
3418
3061
|
var init_templates = __esm({
|
|
3419
3062
|
"src/templates.ts"() {
|
|
3420
3063
|
"use strict";
|
|
3064
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
3421
3065
|
}
|
|
3422
3066
|
});
|
|
3423
3067
|
|
|
@@ -3547,6 +3191,7 @@ var WAVE_NAME, STRICTNESS, MEMBER_NAME, PULUMI_LEXICON;
|
|
|
3547
3191
|
var init_render = __esm({
|
|
3548
3192
|
"src/init/render.ts"() {
|
|
3549
3193
|
"use strict";
|
|
3194
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
3550
3195
|
init_templates();
|
|
3551
3196
|
init_words();
|
|
3552
3197
|
init_detect();
|
|
@@ -3600,11 +3245,28 @@ var REFUSALS;
|
|
|
3600
3245
|
var init_refusals = __esm({
|
|
3601
3246
|
"src/refusals.ts"() {
|
|
3602
3247
|
"use strict";
|
|
3248
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
3603
3249
|
init_words();
|
|
3604
3250
|
REFUSALS = ["waiting", "moved", "held", "superseded", "denied", "declined", "misplaced"];
|
|
3605
3251
|
}
|
|
3606
3252
|
});
|
|
3607
3253
|
|
|
3254
|
+
// src/review-stacks.ts
|
|
3255
|
+
function reviewSuffixOf(pattern) {
|
|
3256
|
+
const m = pattern ? /^pr-\{number\}-([a-z0-9][a-z0-9-]*)$/.exec(pattern) : null;
|
|
3257
|
+
return m ? m[1] : void 0;
|
|
3258
|
+
}
|
|
3259
|
+
var REVIEW_GATE, REVIEW_GATE_WORD, reviewOp;
|
|
3260
|
+
var init_review_stacks2 = __esm({
|
|
3261
|
+
"src/review-stacks.ts"() {
|
|
3262
|
+
"use strict";
|
|
3263
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
3264
|
+
REVIEW_GATE = "pr-env";
|
|
3265
|
+
REVIEW_GATE_WORD = "review";
|
|
3266
|
+
reviewOp = (pr) => `pr-${pr}`;
|
|
3267
|
+
}
|
|
3268
|
+
});
|
|
3269
|
+
|
|
3608
3270
|
// src/up.ts
|
|
3609
3271
|
import { existsSync as existsSync6, readFileSync as readFileSync7 } from "node:fs";
|
|
3610
3272
|
import { join as join6 } from "node:path";
|
|
@@ -3714,8 +3376,7 @@ function approvalStatusTarget(opts, doc, env) {
|
|
|
3714
3376
|
return { ...forge ? { forge } : {}, ...branch ? { branch } : {} };
|
|
3715
3377
|
}
|
|
3716
3378
|
async function refreshApprovalStatus(chant, spec, opts, io) {
|
|
3717
|
-
|
|
3718
|
-
const posts = chant.opWavesPostsApprovalStatus ? chant.opWavesPostsApprovalStatus(spec) : wavesTakePullRequestApproval(spec);
|
|
3379
|
+
const posts = chant.opWavesPostsApprovalStatus(spec);
|
|
3719
3380
|
if (!posts) return;
|
|
3720
3381
|
const path = findConfig(io.cwd);
|
|
3721
3382
|
const doc = path ? loadConfig(path).doc : void 0;
|
|
@@ -3731,7 +3392,6 @@ async function refreshApprovalStatus(chant, spec, opts, io) {
|
|
|
3731
3392
|
}
|
|
3732
3393
|
async function upTargetByPlan(chant, digest, opts = {}) {
|
|
3733
3394
|
const needsPr = `the ${PR_GATE} gate is a pull request's: name it with --pr <n> (or PR_NUMBER), or approve pr-<n>`;
|
|
3734
|
-
if (!hasChant4607(chant)) return { error: `${needsPr}. The installed chant cannot find a pull request from a plan digest (chant#4607)` };
|
|
3735
3395
|
let gates;
|
|
3736
3396
|
try {
|
|
3737
3397
|
gates = await chant.pendingGatesByPlan(digest, { pullRequestsOnly: true, ...opts.cwd ? { cwd: opts.cwd } : {} });
|
|
@@ -3861,8 +3521,6 @@ async function upBeforeMerge(number, args, json, table, io) {
|
|
|
3861
3521
|
const forge = forgeFlag.value ?? configured ?? "github";
|
|
3862
3522
|
if (!FORGES.includes(forge)) return usage(`--forge ${forge} is not one of ${FORGES.join(", ")}`, table, io);
|
|
3863
3523
|
return withChant(`/${PRODUCT} up`, table, io, async (chant) => {
|
|
3864
|
-
const note = requiresNotHonoured(chant, config2.doc);
|
|
3865
|
-
if (note) io.err(`${PRODUCT}: ${note}`);
|
|
3866
3524
|
const argv = [
|
|
3867
3525
|
"components",
|
|
3868
3526
|
"pr-apply",
|
|
@@ -3912,12 +3570,11 @@ var OVERRIDE_OP, WAVES_SPEC_FILE, UP_STEP_KIND, PR_OP, APPROVE_PASS, APPROVE_SWI
|
|
|
3912
3570
|
var init_up = __esm({
|
|
3913
3571
|
"src/up.ts"() {
|
|
3914
3572
|
"use strict";
|
|
3573
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
3915
3574
|
init_apply();
|
|
3916
|
-
init_chant_next();
|
|
3917
3575
|
init_config();
|
|
3918
3576
|
init_routing();
|
|
3919
3577
|
init_refusals();
|
|
3920
|
-
init_chant_product();
|
|
3921
3578
|
init_review_stacks2();
|
|
3922
3579
|
init_stages();
|
|
3923
3580
|
init_words();
|
|
@@ -4026,9 +3683,7 @@ async function drift2(args, json, table, io) {
|
|
|
4026
3683
|
}
|
|
4027
3684
|
}
|
|
4028
3685
|
if (respond === "pull-request" && results.some((r) => r.drifted > 0)) {
|
|
4029
|
-
io.err(
|
|
4030
|
-
hasChantAgent(chant) ? `${PRODUCT} drift: respond.drift is pull-request; in CI the agent job after this one opens or updates the pull request (agent.drift)` : `${PRODUCT} drift: respond.drift is pull-request; this chant has no agent job (chant#4616), so this run kept the issue only`
|
|
4031
|
-
);
|
|
3686
|
+
io.err(`${PRODUCT} drift: respond.drift is pull-request; in CI the agent job after this one opens or updates the pull request (agent.drift)`);
|
|
4032
3687
|
}
|
|
4033
3688
|
const notice = await driftNotice(chant, doc, results, envFlag.value, io);
|
|
4034
3689
|
const drifted = results.filter((r) => r.drifted > 0).map((r) => r.stack);
|
|
@@ -4066,12 +3721,12 @@ async function drift2(args, json, table, io) {
|
|
|
4066
3721
|
}
|
|
4067
3722
|
async function driftNotice(chant, doc, results, environment, io) {
|
|
4068
3723
|
const notify2 = chantNotifyOf(doc);
|
|
4069
|
-
|
|
3724
|
+
if (!notifiesDrift(notify2)) return void 0;
|
|
4070
3725
|
return sendDriftNotice({
|
|
4071
3726
|
notify: notify2,
|
|
4072
3727
|
results,
|
|
4073
3728
|
stackFingerprint: driftFingerprint,
|
|
4074
|
-
chant:
|
|
3729
|
+
chant: { ...await chantDriftNotify(), words: chant.assertProductWords(PRODUCT_WORDS) },
|
|
4075
3730
|
env: io.env,
|
|
4076
3731
|
...environment ? { environment } : {},
|
|
4077
3732
|
log: io.err,
|
|
@@ -4186,20 +3841,19 @@ function driftOpFile(doc, stacks2, schedule) {
|
|
|
4186
3841
|
}
|
|
4187
3842
|
function driftOpSpec(doc, forge, stacks2, schedule, chant, opts = {}) {
|
|
4188
3843
|
const respond = driftResponseOf(doc);
|
|
4189
|
-
const
|
|
4190
|
-
const findingMode = respond === "none" ? "report" : respond === "pull-request" && !agentJob ? forge === "gitlab" ? "merge-request" : "pull-request" : "issue";
|
|
3844
|
+
const findingMode = respond === "none" ? "report" : "issue";
|
|
4191
3845
|
const pipeline = toChantSettings({ doc }).pipeline;
|
|
4192
|
-
const
|
|
3846
|
+
const engine3 = chantPipelineOptions({ ...pipeline.image ? { image: pipeline.image } : {}, ...pipeline.engineSetup ? { engineSetup: pipeline.engineSetup } : {} });
|
|
4193
3847
|
return {
|
|
4194
|
-
ops: stacks2.map((s) => ({ name: driftOpName(s), trigger: { kind: "cron", schedule }, findingMode, ...driftOpFields(chant, doc, driftOpName(s), respond), ...withNotifyVariables(driftJobFields(forge, pipeline.jobs, s), driftNotifyVariables(forge, chantNotifyOf(doc))), ...opts.cloud ? driftCloudFields(doc, forge, s, respond, opts.env ?? "production"
|
|
3848
|
+
ops: stacks2.map((s) => ({ name: driftOpName(s), trigger: { kind: "cron", schedule }, findingMode, ...driftOpFields(chant, doc, driftOpName(s), respond), ...withNotifyVariables(driftJobFields(forge, pipeline.jobs, s), driftNotifyVariables(forge, chantNotifyOf(doc))), ...opts.cloud ? driftCloudFields(doc, forge, s, respond, opts.env ?? "production") : {} })),
|
|
4195
3849
|
options: {
|
|
4196
3850
|
runCommand: [...BIN.split(/\s+/), "drift", "--op", "{name}", "--forge", forge],
|
|
4197
|
-
beforeScript: [SNAPSHOTS_FETCH, ...
|
|
4198
|
-
...
|
|
3851
|
+
beforeScript: [SNAPSHOTS_FETCH, ...engine3.beforeScript ?? []],
|
|
3852
|
+
...engine3.image ? { image: engine3.image } : {}
|
|
4199
3853
|
}
|
|
4200
3854
|
};
|
|
4201
3855
|
}
|
|
4202
|
-
function driftCloudFields(doc, forge, stack, respond, env
|
|
3856
|
+
function driftCloudFields(doc, forge, stack, respond, env) {
|
|
4203
3857
|
const settings = toChantSettings({ doc });
|
|
4204
3858
|
const decl = settings.ci.identity?.cloud;
|
|
4205
3859
|
const ids = decl ? resolveTargetCloudIdentity(decl, env, stack, stack) : void 0;
|
|
@@ -4208,7 +3862,7 @@ function driftCloudFields(doc, forge, stack, respond, env, opts = {}) {
|
|
|
4208
3862
|
const waves = wavesHolding(doc, [stack]);
|
|
4209
3863
|
const command = waves === "all" ? `${BIN} up` : waves.map((k) => `${BIN} up --wave ${k}`).join(" && ");
|
|
4210
3864
|
const fields = driftJobFields(forge, settings.pipeline.jobs, stack);
|
|
4211
|
-
const permissions =
|
|
3865
|
+
const permissions = forge !== "gitlab" ? { permissions: { contents: "write" } } : {};
|
|
4212
3866
|
out.update = { command, ...ids?.apply ? { cloud: ids.apply } : {}, ...fields, ...permissions };
|
|
4213
3867
|
return out;
|
|
4214
3868
|
}
|
|
@@ -4242,7 +3896,7 @@ async function writeDriftJobs(chant, forge, doc, words, table, io, env = "produc
|
|
|
4242
3896
|
return table.done;
|
|
4243
3897
|
}
|
|
4244
3898
|
if (writeIfChanged2(io.cwd, DRIFT_OP_FILE, driftOpFile(doc, settings.stacks, settings.schedule))) io.err(`${PRODUCT} ci: wrote ${DRIFT_OP_FILE}; commit it with the workflow`);
|
|
4245
|
-
if (writeIfChanged2(io.cwd, DRIFT_SPEC_FILE, JSON.stringify(driftOpSpec(doc, forge, settings.stacks, settings.schedule, chant, { cloud: true,
|
|
3899
|
+
if (writeIfChanged2(io.cwd, DRIFT_SPEC_FILE, JSON.stringify(driftOpSpec(doc, forge, settings.stacks, settings.schedule, chant, { cloud: true, env }), null, 2) + "\n")) io.err(`${PRODUCT} ci: wrote ${DRIFT_SPEC_FILE}`);
|
|
4246
3900
|
const outDir = OP_OUTPUT_DIR2[forge];
|
|
4247
3901
|
const code = await chant.runChantInProcess(["run", "--generate", forge, "--spec", DRIFT_SPEC_FILE, "--output", outDir], words);
|
|
4248
3902
|
if (code !== table.done) return code;
|
|
@@ -4256,6 +3910,7 @@ var DRIFT_OP_FILE, DRIFT_SPEC_FILE, SNAPSHOTS_REF, SNAPSHOTS_FETCH, DRIFT_STATUS
|
|
|
4256
3910
|
var init_drift2 = __esm({
|
|
4257
3911
|
"src/drift.ts"() {
|
|
4258
3912
|
"use strict";
|
|
3913
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
4259
3914
|
init_chant_next();
|
|
4260
3915
|
init_images2();
|
|
4261
3916
|
init_chant_config();
|
|
@@ -4293,6 +3948,7 @@ var TIPS;
|
|
|
4293
3948
|
var init_tips = __esm({
|
|
4294
3949
|
"src/tips.ts"() {
|
|
4295
3950
|
"use strict";
|
|
3951
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
4296
3952
|
TIPS = [
|
|
4297
3953
|
{
|
|
4298
3954
|
id: "rename-without-aliases",
|
|
@@ -4409,9 +4065,6 @@ async function mcp(args, _json, table, io) {
|
|
|
4409
4065
|
if (report.missing) return usage("--report-url takes a URL", table, io);
|
|
4410
4066
|
if (report.rest.length) return usage(`mcp does not take ${report.rest.join(" ")}`, table, io);
|
|
4411
4067
|
return withChant(`${PRODUCT} mcp`, table, io, (chant) => {
|
|
4412
|
-
if (typeof chant.PRODUCT_MCP_HOOK_FLOOR !== "string") {
|
|
4413
|
-
io.err(`${PRODUCT} mcp: this chant has no product tool hook (chant#4555), so the server serves chant's read tools and none of ${PRODUCT}'s`);
|
|
4414
|
-
}
|
|
4415
4068
|
const argv = ["serve", "mcp", "--read-only", ...propose ? ["--propose"] : []];
|
|
4416
4069
|
return chant.runChantInProcess(argv, PRODUCT_WORDS, { mcp: plywoodMcpHook(report.value, propose) });
|
|
4417
4070
|
});
|
|
@@ -4447,7 +4100,7 @@ function reviewOpFile(doc) {
|
|
|
4447
4100
|
function reviewOpSpec(doc) {
|
|
4448
4101
|
const settings = toChantSettings({ doc });
|
|
4449
4102
|
const p = settings.pipeline;
|
|
4450
|
-
const
|
|
4103
|
+
const engine3 = chantPipelineOptions({ ...p.image ? { image: p.image } : {}, ...p.engineSetup ? { engineSetup: p.engineSetup } : {} });
|
|
4451
4104
|
return {
|
|
4452
4105
|
ops: [
|
|
4453
4106
|
{
|
|
@@ -4459,8 +4112,8 @@ function reviewOpSpec(doc) {
|
|
|
4459
4112
|
}
|
|
4460
4113
|
],
|
|
4461
4114
|
options: {
|
|
4462
|
-
...
|
|
4463
|
-
...
|
|
4115
|
+
...engine3.beforeScript ? { beforeScript: engine3.beforeScript } : {},
|
|
4116
|
+
...engine3.image ? { image: engine3.image } : {}
|
|
4464
4117
|
}
|
|
4465
4118
|
};
|
|
4466
4119
|
}
|
|
@@ -4490,6 +4143,7 @@ var PERSON_VERBS, READ_INSTRUCTIONS, REVIEW_OP, REVIEW_OP_FILE, REVIEW_SPEC_FILE
|
|
|
4490
4143
|
var init_agents2 = __esm({
|
|
4491
4144
|
"src/agents.ts"() {
|
|
4492
4145
|
"use strict";
|
|
4146
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
4493
4147
|
init_chant_config();
|
|
4494
4148
|
init_config_check();
|
|
4495
4149
|
init_agents();
|
|
@@ -4526,6 +4180,7 @@ var PROVIDERS_PACK_DIR, PROVIDERS_PACK_CONFIG, PROVIDERS_REGO_FILE, PROVIDERS_PO
|
|
|
4526
4180
|
var init_policy_presets = __esm({
|
|
4527
4181
|
"src/policy-presets.ts"() {
|
|
4528
4182
|
"use strict";
|
|
4183
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
4529
4184
|
PROVIDERS_PACK_DIR = "policy/plywood-providers";
|
|
4530
4185
|
PROVIDERS_PACK_CONFIG = "policy/plywood-providers.json";
|
|
4531
4186
|
PROVIDERS_REGO_FILE = "plywood_providers.rego";
|
|
@@ -4607,19 +4262,9 @@ function bucketTarget(url, env) {
|
|
|
4607
4262
|
}
|
|
4608
4263
|
return void 0;
|
|
4609
4264
|
}
|
|
4610
|
-
function needsBucketCheckMessage(builtChant) {
|
|
4611
|
-
return `the bucket check needs chant >= the release after 0.124.54, which carries @intentius/chant/bucket-check (chant#4558). This plywood was built with chant ${builtChant}, which lacks it, so the bucket was not checked.`;
|
|
4612
|
-
}
|
|
4613
4265
|
function chantBucketCheck() {
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
const m = await import("@intentius/chant/bucket-check");
|
|
4617
|
-
return typeof m.checkBucket === "function" ? m : void 0;
|
|
4618
|
-
} catch {
|
|
4619
|
-
return void 0;
|
|
4620
|
-
}
|
|
4621
|
-
})();
|
|
4622
|
-
return cached4;
|
|
4266
|
+
cached2 ??= import("@intentius/chant/bucket-check").then((m) => m);
|
|
4267
|
+
return cached2;
|
|
4623
4268
|
}
|
|
4624
4269
|
function bucketLines(report) {
|
|
4625
4270
|
const where = `${report.store === "azblob" ? "container" : "bucket"} ${report.bucket}`;
|
|
@@ -4632,47 +4277,38 @@ function bucketLines(report) {
|
|
|
4632
4277
|
}
|
|
4633
4278
|
return out;
|
|
4634
4279
|
}
|
|
4635
|
-
var
|
|
4280
|
+
var cached2;
|
|
4636
4281
|
var init_backend = __esm({
|
|
4637
4282
|
"src/init/backend.ts"() {
|
|
4638
4283
|
"use strict";
|
|
4284
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
4639
4285
|
}
|
|
4640
4286
|
});
|
|
4641
4287
|
|
|
4642
4288
|
// src/init/adopt.ts
|
|
4643
4289
|
function chantAdopt() {
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
return view;
|
|
4662
|
-
} catch (err) {
|
|
4663
|
-
const f = failure ? failure(err) : { message: err.message };
|
|
4664
|
-
const view = parse(f.stdout);
|
|
4665
|
-
if (view) return view;
|
|
4666
|
-
if (f.missingBinary) throw new Error("the plunge binary was not found on PATH");
|
|
4667
|
-
throw new Error((f.stderr?.trim() || f.message).split("\n").slice(-3).join("\n"));
|
|
4668
|
-
}
|
|
4290
|
+
cached3 ??= (async () => {
|
|
4291
|
+
const { plungeAdoptArgs, runAllowedPlunge, defaultPlungeRunner, pulumiRunFailure } = await import("@intentius/chant-lexicon-pulumi/run");
|
|
4292
|
+
const { parsePlungeAdoptView } = await import("@intentius/chant-lexicon-pulumi");
|
|
4293
|
+
const parse = (t) => parsePlungeAdoptView(t);
|
|
4294
|
+
return {
|
|
4295
|
+
async preview({ dir, stack, backend }) {
|
|
4296
|
+
try {
|
|
4297
|
+
const { stdout } = await runAllowedPlunge(defaultPlungeRunner, plungeAdoptArgs({ stack }), dir, { PULUMI_BACKEND_URL: backend });
|
|
4298
|
+
const view = parse(stdout);
|
|
4299
|
+
if (!view) throw new Error("plunge printed no adopt document");
|
|
4300
|
+
return view;
|
|
4301
|
+
} catch (err) {
|
|
4302
|
+
const f = pulumiRunFailure(err);
|
|
4303
|
+
const view = parse(f.stdout);
|
|
4304
|
+
if (view) return view;
|
|
4305
|
+
if (f.missingBinary) throw new Error("the plunge binary was not found on PATH");
|
|
4306
|
+
throw new Error((f.stderr?.trim() || f.message).split("\n").slice(-3).join("\n"));
|
|
4669
4307
|
}
|
|
4670
|
-
}
|
|
4671
|
-
}
|
|
4672
|
-
return void 0;
|
|
4673
|
-
}
|
|
4308
|
+
}
|
|
4309
|
+
};
|
|
4674
4310
|
})();
|
|
4675
|
-
return
|
|
4311
|
+
return cached3;
|
|
4676
4312
|
}
|
|
4677
4313
|
function adoptCommand(s, backend) {
|
|
4678
4314
|
const cd = s.dir === "." ? "" : `cd ${s.dir} && `;
|
|
@@ -4682,10 +4318,6 @@ async function adoptPreviews(stacks2, chant, root) {
|
|
|
4682
4318
|
const out = [];
|
|
4683
4319
|
for (const s of stacks2) {
|
|
4684
4320
|
const command = adoptCommand(s, s.url);
|
|
4685
|
-
if (!chant) {
|
|
4686
|
-
out.push({ key: s.key, backend: s.url, command, error: "not run" });
|
|
4687
|
-
continue;
|
|
4688
|
-
}
|
|
4689
4321
|
try {
|
|
4690
4322
|
const view = await chant.preview({ dir: s.dir === "." ? root : `${root}/${s.dir}`, stack: s.stack, backend: s.url });
|
|
4691
4323
|
out.push({ key: s.key, backend: s.url, command, view });
|
|
@@ -4700,9 +4332,8 @@ function fates(view) {
|
|
|
4700
4332
|
for (const r of view.resources) n[r.fate] = (n[r.fate] ?? 0) + 1;
|
|
4701
4333
|
return Object.entries(n).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([f, c]) => `${f} ${c}`).join(", ");
|
|
4702
4334
|
}
|
|
4703
|
-
function adoptLines(previews
|
|
4335
|
+
function adoptLines(previews) {
|
|
4704
4336
|
if (previews.length === 0) return [];
|
|
4705
|
-
if (!chant) return [NEEDS_ADOPT_MESSAGE, ...previews.map((p) => ` ${p.key}: ${p.command}`)];
|
|
4706
4337
|
const out = ["Adopt previews (plunge stack adopt --json, which writes nothing):"];
|
|
4707
4338
|
for (const p of previews) {
|
|
4708
4339
|
const v = p.view;
|
|
@@ -4768,14 +4399,14 @@ function adoptTodo(stacks2) {
|
|
|
4768
4399
|
`Adopt the stacks onto plunge before their first plunge update: npx chant run ${ADOPT_OP}, then npx chant approve ${ADOPT_OP} <gate> for each of ${gates.join(", ")} after reading its preview, then npx chant run ${ADOPT_OP} again`
|
|
4769
4400
|
];
|
|
4770
4401
|
}
|
|
4771
|
-
var ADOPT_OP, ADOPT_OP_FILE,
|
|
4402
|
+
var ADOPT_OP, ADOPT_OP_FILE, cached3;
|
|
4772
4403
|
var init_adopt = __esm({
|
|
4773
4404
|
"src/init/adopt.ts"() {
|
|
4774
4405
|
"use strict";
|
|
4406
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
4775
4407
|
init_detect();
|
|
4776
4408
|
ADOPT_OP = "plywood-adopt";
|
|
4777
4409
|
ADOPT_OP_FILE = "plywood.adopt.op.ts";
|
|
4778
|
-
NEEDS_ADOPT_MESSAGE = "the adopt previews and the adopt Op need @intentius/chant-lexicon-pulumi >= the release after 0.124.54 (chant#4562); run each preview by hand:";
|
|
4779
4410
|
}
|
|
4780
4411
|
});
|
|
4781
4412
|
|
|
@@ -4791,9 +4422,9 @@ function plungeUrlOf(url) {
|
|
|
4791
4422
|
const scheme = url.split("://")[0];
|
|
4792
4423
|
return { pointer: `is on ${url}, and plunge has no plunge+ form for ${scheme}. Move it to a file, s3, azblob or gs backend first: ${MOVE_A_STACK_GUIDE}` };
|
|
4793
4424
|
}
|
|
4794
|
-
function plungeStacks(
|
|
4795
|
-
return
|
|
4796
|
-
const project =
|
|
4425
|
+
function plungeStacks(detected, env) {
|
|
4426
|
+
return detected.stacks.map((s) => {
|
|
4427
|
+
const project = detected.projects.find((p) => p.dir === s.dir && p.name === s.project) ?? { backendUrl: void 0 };
|
|
4797
4428
|
const b = backendOf(project, {}, env);
|
|
4798
4429
|
const to = plungeUrlOf(b.url);
|
|
4799
4430
|
return {
|
|
@@ -4838,6 +4469,7 @@ var INIT_ENGINES, MOVE_A_STACK_GUIDE, PLUNGE_FORMS;
|
|
|
4838
4469
|
var init_engine2 = __esm({
|
|
4839
4470
|
"src/init/engine.ts"() {
|
|
4840
4471
|
"use strict";
|
|
4472
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
4841
4473
|
init_engine();
|
|
4842
4474
|
init_backend();
|
|
4843
4475
|
INIT_ENGINES = ["pulumi", "plunge"];
|
|
@@ -4861,6 +4493,7 @@ var PRESET_NAMES, DEFAULT_PRESET, PRESETS;
|
|
|
4861
4493
|
var init_presets = __esm({
|
|
4862
4494
|
"src/init/presets.ts"() {
|
|
4863
4495
|
"use strict";
|
|
4496
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
4864
4497
|
PRESET_NAMES = ["merge", "before-merge", "approve-all"];
|
|
4865
4498
|
DEFAULT_PRESET = "merge";
|
|
4866
4499
|
PRESETS = [
|
|
@@ -4886,26 +4519,17 @@ var init_presets = __esm({
|
|
|
4886
4519
|
// src/stack-order.ts
|
|
4887
4520
|
import { join as join9 } from "node:path";
|
|
4888
4521
|
function chantReferenceReader() {
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
return {
|
|
4899
|
-
pulumiStackReferences: lexicon.pulumiStackReferences,
|
|
4900
|
-
stackReferenceOwner: lexicon.stackReferenceOwner,
|
|
4901
|
-
...typeof run.memberExportOf === "function" ? { memberExportOf: run.memberExportOf } : {},
|
|
4902
|
-
...typeof lexicon.readMemberExport === "function" ? { readMemberExport: lexicon.readMemberExport } : {}
|
|
4903
|
-
};
|
|
4904
|
-
} catch {
|
|
4905
|
-
return void 0;
|
|
4906
|
-
}
|
|
4522
|
+
cached4 ??= (async () => {
|
|
4523
|
+
const lexicon = await import("@intentius/chant-lexicon-pulumi");
|
|
4524
|
+
const run = await import("@intentius/chant-lexicon-pulumi/run");
|
|
4525
|
+
return {
|
|
4526
|
+
pulumiStackReferences: lexicon.pulumiStackReferences,
|
|
4527
|
+
stackReferenceOwner: lexicon.stackReferenceOwner,
|
|
4528
|
+
memberExportOf: run.memberExportOf,
|
|
4529
|
+
readMemberExport: lexicon.readMemberExport
|
|
4530
|
+
};
|
|
4907
4531
|
})();
|
|
4908
|
-
return
|
|
4532
|
+
return cached4;
|
|
4909
4533
|
}
|
|
4910
4534
|
async function exportOf(reader, root, s) {
|
|
4911
4535
|
const dir = join9(root, s.dir);
|
|
@@ -4929,7 +4553,6 @@ async function stackOrder(root, stacks2, after = {}) {
|
|
|
4929
4553
|
for (const upstream of ups) if (keys.has(reader2) && keys.has(upstream)) add({ reader: reader2, upstream, because: "waves.after" });
|
|
4930
4554
|
}
|
|
4931
4555
|
const reader = await chantReferenceReader();
|
|
4932
|
-
if (!reader) return { edges, outside, reads: false };
|
|
4933
4556
|
const owners = stacks2.map((s) => ({ component: s.key, project: s.project, stack: s.stack }));
|
|
4934
4557
|
for (const s of stacks2) {
|
|
4935
4558
|
const exported = await exportOf(reader, root, s);
|
|
@@ -4940,7 +4563,7 @@ async function stackOrder(root, stacks2, after = {}) {
|
|
|
4940
4563
|
else outside.push({ stack: s.key, reference: ref.name, why: found.outside });
|
|
4941
4564
|
}
|
|
4942
4565
|
}
|
|
4943
|
-
return { edges, outside
|
|
4566
|
+
return { edges, outside };
|
|
4944
4567
|
}
|
|
4945
4568
|
function orderWaves(waves, edges, canary = []) {
|
|
4946
4569
|
const unmet = [];
|
|
@@ -4989,16 +4612,16 @@ function orderWaves(waves, edges, canary = []) {
|
|
|
4989
4612
|
}
|
|
4990
4613
|
function orderLines(order, unmet) {
|
|
4991
4614
|
const out = [];
|
|
4992
|
-
if (!order.reads) out.push("the installed @intentius/chant-lexicon-pulumi reads no StackReferences (chant#4547), so the waves follow waves.after alone");
|
|
4993
4615
|
for (const e of order.edges) out.push(`${e.reader} updates after ${e.upstream} (${e.because === "stack-reference" ? "its StackReference" : "waves.after"})`);
|
|
4994
4616
|
for (const o of order.outside) out.push(`${o.stack}'s StackReference ${o.reference} orders nothing: ${o.why}`);
|
|
4995
4617
|
for (const u of unmet) out.push(`${u.reader} should update after ${u.upstream}, and cannot: ${u.why}; list them in one wave, or reorder waves`);
|
|
4996
4618
|
return out;
|
|
4997
4619
|
}
|
|
4998
|
-
var
|
|
4620
|
+
var cached4;
|
|
4999
4621
|
var init_stack_order = __esm({
|
|
5000
4622
|
"src/stack-order.ts"() {
|
|
5001
4623
|
"use strict";
|
|
4624
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
5002
4625
|
}
|
|
5003
4626
|
});
|
|
5004
4627
|
|
|
@@ -5016,9 +4639,9 @@ function repoJoin(...parts) {
|
|
|
5016
4639
|
const out = posix2.normalize(parts.filter((p) => p !== "" && p !== ".").join("/") || ".").replace(/^\.\//, "").replace(/\/+$/, "");
|
|
5017
4640
|
return out === "" ? "." : out;
|
|
5018
4641
|
}
|
|
5019
|
-
function projectAt(
|
|
4642
|
+
function projectAt(detected, dir) {
|
|
5020
4643
|
const want = repoJoin(dir);
|
|
5021
|
-
return
|
|
4644
|
+
return detected.projects.find((p) => repoJoin(p.dir) === want);
|
|
5022
4645
|
}
|
|
5023
4646
|
function stackEntry(keys, key) {
|
|
5024
4647
|
const stacks2 = isObject(keys.stacks) ? keys.stacks : keys.stacks = {};
|
|
@@ -5053,6 +4676,7 @@ var IMPORT_SOURCES;
|
|
|
5053
4676
|
var init_types2 = __esm({
|
|
5054
4677
|
"src/import/types.ts"() {
|
|
5055
4678
|
"use strict";
|
|
4679
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
5056
4680
|
init_types();
|
|
5057
4681
|
IMPORT_SOURCES = ["pulumi-actions", "deployments", "spacelift", "env0"];
|
|
5058
4682
|
}
|
|
@@ -5061,10 +4685,10 @@ var init_types2 = __esm({
|
|
|
5061
4685
|
// src/import/deployments.ts
|
|
5062
4686
|
import { readdirSync as readdirSync6 } from "node:fs";
|
|
5063
4687
|
import { basename as basename2, dirname as dirname8, join as join11 } from "node:path";
|
|
5064
|
-
function deploymentFiles(root,
|
|
4688
|
+
function deploymentFiles(root, detected, named) {
|
|
5065
4689
|
if (named.length) return [...named];
|
|
5066
4690
|
const out = [];
|
|
5067
|
-
for (const p of
|
|
4691
|
+
for (const p of detected.projects) {
|
|
5068
4692
|
let names;
|
|
5069
4693
|
try {
|
|
5070
4694
|
names = readdirSync6(join11(root, p.dir)).sort();
|
|
@@ -5079,6 +4703,7 @@ var SETTINGS_FILE, NOT_CARRIED, importDeployments;
|
|
|
5079
4703
|
var init_deployments = __esm({
|
|
5080
4704
|
"src/import/deployments.ts"() {
|
|
5081
4705
|
"use strict";
|
|
4706
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
5082
4707
|
init_types();
|
|
5083
4708
|
init_detect();
|
|
5084
4709
|
init_types2();
|
|
@@ -5094,7 +4719,7 @@ var init_deployments = __esm({
|
|
|
5094
4719
|
"gitHub.deployPullRequest": "set apply.when: pull-request and update with /plywood up",
|
|
5095
4720
|
"gitHub.repository": "plywood runs in the repository it is set up in"
|
|
5096
4721
|
};
|
|
5097
|
-
importDeployments = (root,
|
|
4722
|
+
importDeployments = (root, detected, named) => {
|
|
5098
4723
|
let keys = {};
|
|
5099
4724
|
const unsupported = [];
|
|
5100
4725
|
const notes = [];
|
|
@@ -5102,7 +4727,7 @@ var init_deployments = __esm({
|
|
|
5102
4727
|
const not = (file, setting, why) => {
|
|
5103
4728
|
unsupported.push({ file, setting, why: why ?? NOT_CARRIED[setting] ?? "plywood has no setting for it" });
|
|
5104
4729
|
};
|
|
5105
|
-
for (const file of deploymentFiles(root,
|
|
4730
|
+
for (const file of deploymentFiles(root, detected, named)) {
|
|
5106
4731
|
const m = SETTINGS_FILE.exec(basename2(file));
|
|
5107
4732
|
if (!m) {
|
|
5108
4733
|
not(file, "(file)", "names no stack: call it Pulumi.<stack>.deploy.yaml (or .json) beside the project's Pulumi.yaml");
|
|
@@ -5121,7 +4746,7 @@ var init_deployments = __esm({
|
|
|
5121
4746
|
const fileDir = repoJoin(posixRel(root, dirname8(join11(root, file))));
|
|
5122
4747
|
const source = isObject(settings.sourceContext) && isObject(settings.sourceContext.git) ? settings.sourceContext.git : {};
|
|
5123
4748
|
const dir = typeof source.repoDir === "string" ? repoJoin(source.repoDir) : fileDir;
|
|
5124
|
-
const project = projectAt(
|
|
4749
|
+
const project = projectAt(detected, dir)?.name ?? projectAt(detected, fileDir)?.name;
|
|
5125
4750
|
if (!project) {
|
|
5126
4751
|
not(file, "sourceContext.git.repoDir", `${dir} holds no Pulumi.yaml`);
|
|
5127
4752
|
continue;
|
|
@@ -5176,9 +4801,9 @@ var init_deployments = __esm({
|
|
|
5176
4801
|
});
|
|
5177
4802
|
|
|
5178
4803
|
// src/import/env0.ts
|
|
5179
|
-
function stackFor(
|
|
4804
|
+
function stackFor(detected, names) {
|
|
5180
4805
|
for (const n of names) {
|
|
5181
|
-
const hits =
|
|
4806
|
+
const hits = detected.stacks.filter((s) => s.key === n || s.stack === n || detected.stacks.filter((x) => x.project === s.project).length === 1 && s.project === n);
|
|
5182
4807
|
if (hits.length === 1) return hits[0].key;
|
|
5183
4808
|
}
|
|
5184
4809
|
return void 0;
|
|
@@ -5187,11 +4812,12 @@ var ENV0_WORKFLOW_FILES, ENV0_FLOW_FILES, importEnv0;
|
|
|
5187
4812
|
var init_env0 = __esm({
|
|
5188
4813
|
"src/import/env0.ts"() {
|
|
5189
4814
|
"use strict";
|
|
4815
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
5190
4816
|
init_types();
|
|
5191
4817
|
init_types2();
|
|
5192
4818
|
ENV0_WORKFLOW_FILES = ["env0.workflow.yml", "env0.workflow.yaml"];
|
|
5193
4819
|
ENV0_FLOW_FILES = ["env0.yml", "env0.yaml"];
|
|
5194
|
-
importEnv0 = (root,
|
|
4820
|
+
importEnv0 = (root, detected, named) => {
|
|
5195
4821
|
const keys = {};
|
|
5196
4822
|
const unsupported = [];
|
|
5197
4823
|
const notes = [];
|
|
@@ -5209,7 +4835,7 @@ var init_env0 = __esm({
|
|
|
5209
4835
|
for (const [id, raw] of Object.entries(envs)) {
|
|
5210
4836
|
const e = isObject(raw) ? raw : {};
|
|
5211
4837
|
const names = [id, ...typeof e.name === "string" ? [e.name] : [], ...typeof e.templateName === "string" ? [e.templateName] : []];
|
|
5212
|
-
const key = stackFor(
|
|
4838
|
+
const key = stackFor(detected, names);
|
|
5213
4839
|
if (key) stackOf.set(id, key);
|
|
5214
4840
|
else unsupported.push({ file: workflow, setting: `environments.${id}`, why: `names no stack of this repository (${names.join(", ")}); list it under stacks and waves.after by hand` });
|
|
5215
4841
|
for (const k of Object.keys(e).filter((x) => !["name", "templateName", "needs"].includes(x))) {
|
|
@@ -5287,6 +4913,7 @@ var WORKFLOW_DIRS2, CARRIED, WHY, PULUMI_CLOUD, importPulumiActions;
|
|
|
5287
4913
|
var init_pulumi_actions = __esm({
|
|
5288
4914
|
"src/import/pulumi-actions.ts"() {
|
|
5289
4915
|
"use strict";
|
|
4916
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
5290
4917
|
init_types();
|
|
5291
4918
|
init_types2();
|
|
5292
4919
|
WORKFLOW_DIRS2 = [".github/workflows", ".forgejo/workflows", ".gitea/workflows"];
|
|
@@ -5303,7 +4930,7 @@ var init_pulumi_actions = __esm({
|
|
|
5303
4930
|
plan: "plywood's update applies the preview its approval names, through chant"
|
|
5304
4931
|
};
|
|
5305
4932
|
PULUMI_CLOUD = /^https:\/\/(?:app|api)\.pulumi\.com\/?$/;
|
|
5306
|
-
importPulumiActions = (root,
|
|
4933
|
+
importPulumiActions = (root, detected, named) => {
|
|
5307
4934
|
let keys = {};
|
|
5308
4935
|
const unsupported = [];
|
|
5309
4936
|
const notes = [];
|
|
@@ -5339,7 +4966,7 @@ var init_pulumi_actions = __esm({
|
|
|
5339
4966
|
const dir = repoJoin(typeof w["work-dir"] === "string" ? w["work-dir"] : ".");
|
|
5340
4967
|
const parts = stackName.split("/");
|
|
5341
4968
|
const stack = parts[parts.length - 1];
|
|
5342
|
-
const project = projectAt(
|
|
4969
|
+
const project = projectAt(detected, dir)?.name ?? (parts.length === 3 ? parts[1] : void 0);
|
|
5343
4970
|
if (!project) {
|
|
5344
4971
|
unsupported.push({ file, setting: `${where}.with.stack-name`, why: `${dir} holds no Pulumi.yaml, so ${stackName} names no project` });
|
|
5345
4972
|
return;
|
|
@@ -5392,11 +5019,12 @@ var SPACELIFT_FILES, CARRIED2, importSpacelift;
|
|
|
5392
5019
|
var init_spacelift = __esm({
|
|
5393
5020
|
"src/import/spacelift.ts"() {
|
|
5394
5021
|
"use strict";
|
|
5022
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
5395
5023
|
init_types();
|
|
5396
5024
|
init_types2();
|
|
5397
5025
|
SPACELIFT_FILES = [".spacelift/config.yml", ".spacelift/config.yaml"];
|
|
5398
5026
|
CARRIED2 = ["project_root", "runner_image"];
|
|
5399
|
-
importSpacelift = (root,
|
|
5027
|
+
importSpacelift = (root, detected, named) => {
|
|
5400
5028
|
const keys = {};
|
|
5401
5029
|
const unsupported = [];
|
|
5402
5030
|
const notes = [];
|
|
@@ -5423,7 +5051,7 @@ var init_spacelift = __esm({
|
|
|
5423
5051
|
const own = isObject(raw) ? raw : {};
|
|
5424
5052
|
const s = { ...defaults, ...own };
|
|
5425
5053
|
const dir = repoJoin(typeof s.project_root === "string" ? s.project_root : ".");
|
|
5426
|
-
const project = projectAt(
|
|
5054
|
+
const project = projectAt(detected, dir);
|
|
5427
5055
|
if (!project) {
|
|
5428
5056
|
unsupported.push({ file, setting: `stacks.${id}.project_root`, why: `${dir} holds no Pulumi.yaml` });
|
|
5429
5057
|
continue;
|
|
@@ -5472,9 +5100,9 @@ function parseFrom(value) {
|
|
|
5472
5100
|
if (!source) return { error: `--from ${value} is not one of ${IMPORT_SOURCES.join(", ")}, and its path names none; give <source>=${value}` };
|
|
5473
5101
|
return { source, files: [file] };
|
|
5474
5102
|
}
|
|
5475
|
-
function runImport(root,
|
|
5103
|
+
function runImport(root, detected, from) {
|
|
5476
5104
|
try {
|
|
5477
|
-
return IMPORTERS[from.source](root,
|
|
5105
|
+
return IMPORTERS[from.source](root, detected, from.files);
|
|
5478
5106
|
} catch (e) {
|
|
5479
5107
|
const file = from.files[0] ?? "(its files)";
|
|
5480
5108
|
return { source: from.source, files: [], keys: {}, unsupported: [{ file, setting: "(file)", why: `could not be read: ${e.message}` }], notes: [] };
|
|
@@ -5484,6 +5112,7 @@ var IMPORTERS;
|
|
|
5484
5112
|
var init_import = __esm({
|
|
5485
5113
|
"src/import/index.ts"() {
|
|
5486
5114
|
"use strict";
|
|
5115
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
5487
5116
|
init_deployments();
|
|
5488
5117
|
init_env0();
|
|
5489
5118
|
init_pulumi_actions();
|
|
@@ -5533,11 +5162,11 @@ function parseInitArgs(args) {
|
|
|
5533
5162
|
};
|
|
5534
5163
|
const preset2 = take("--preset");
|
|
5535
5164
|
const gate = take("--gate");
|
|
5536
|
-
const
|
|
5165
|
+
const engine3 = take("--engine");
|
|
5537
5166
|
const forge = take("--forge");
|
|
5538
5167
|
const env = take("--env");
|
|
5539
5168
|
const fromFlag = take("--from");
|
|
5540
|
-
const bad = [preset2, gate,
|
|
5169
|
+
const bad = [preset2, gate, engine3, forge, env, fromFlag].find((t) => t.error);
|
|
5541
5170
|
if (bad) return { error: bad.error };
|
|
5542
5171
|
const flags = /* @__PURE__ */ new Set(["--no-bucket-check", "--dry-run", "--yes", "-y"]);
|
|
5543
5172
|
const extra = rest.filter((a) => !flags.has(a));
|
|
@@ -5545,7 +5174,7 @@ function parseInitArgs(args) {
|
|
|
5545
5174
|
if (preset2.value !== void 0 && !presetByName(preset2.value)) return { error: `--preset ${preset2.value} is not one of ${PRESET_NAMES.join(", ")}` };
|
|
5546
5175
|
const g = gate.value !== void 0 ? gateFlag(gate.value) : void 0;
|
|
5547
5176
|
if (gate.value !== void 0 && !g) return { error: `--gate ${gate.value} is not one of always, on-destructive (or destroy), never` };
|
|
5548
|
-
if (
|
|
5177
|
+
if (engine3.value !== void 0 && !INIT_ENGINES.includes(engine3.value)) return { error: `--engine ${engine3.value} is not one of ${INIT_ENGINES.join(", ")}` };
|
|
5549
5178
|
if (forge.value !== void 0 && !FORGES.includes(forge.value)) return { error: `--forge ${forge.value} is not one of ${FORGES.join(", ")}` };
|
|
5550
5179
|
const from = fromFlag.value !== void 0 ? parseFrom(fromFlag.value) : void 0;
|
|
5551
5180
|
if (from && "error" in from) return { error: from.error };
|
|
@@ -5553,7 +5182,7 @@ function parseInitArgs(args) {
|
|
|
5553
5182
|
...from ? { from } : {},
|
|
5554
5183
|
...preset2.value ? { preset: preset2.value } : {},
|
|
5555
5184
|
...g ? { gate: g } : {},
|
|
5556
|
-
...
|
|
5185
|
+
...engine3.value ? { engine: engine3.value } : {},
|
|
5557
5186
|
...forge.value ? { forge: forge.value } : {},
|
|
5558
5187
|
env: env.value ?? "production",
|
|
5559
5188
|
bucketCheck: !rest.includes("--no-bucket-check"),
|
|
@@ -5574,7 +5203,7 @@ function put(root, rel, text, dryRun, note) {
|
|
|
5574
5203
|
function existingConfig(root) {
|
|
5575
5204
|
return CONFIG_NAMES.find((n) => existsSync10(join13(root, n)));
|
|
5576
5205
|
}
|
|
5577
|
-
async function newConfigKeys(
|
|
5206
|
+
async function newConfigKeys(detected, opts, io) {
|
|
5578
5207
|
const notes = [];
|
|
5579
5208
|
let presetName = opts.preset;
|
|
5580
5209
|
if (!presetName && io.ask && !opts.yes) {
|
|
@@ -5584,31 +5213,31 @@ async function newConfigKeys(detected2, opts, io) {
|
|
|
5584
5213
|
const preset2 = presetByName(presetName ?? DEFAULT_PRESET);
|
|
5585
5214
|
const keys = structuredClone(preset2.keys);
|
|
5586
5215
|
if (opts.gate) keys.gate = opts.gate;
|
|
5587
|
-
const
|
|
5588
|
-
Object.assign(keys,
|
|
5589
|
-
notes.push(...
|
|
5590
|
-
const engineBackends =
|
|
5216
|
+
const engine3 = engineStep({ engine: opts.engine, detected, env: io.env });
|
|
5217
|
+
Object.assign(keys, engine3.keys);
|
|
5218
|
+
notes.push(...engine3.notes);
|
|
5219
|
+
const engineBackends = engine3.keys.backend !== void 0 || engine3.keys.stacks !== void 0;
|
|
5591
5220
|
let forge;
|
|
5592
5221
|
if (opts.forge) {
|
|
5593
5222
|
forge = { kind: opts.forge, because: "--forge" };
|
|
5594
|
-
if (
|
|
5595
|
-
else if (
|
|
5596
|
-
} else if (
|
|
5597
|
-
forge = { kind:
|
|
5598
|
-
if (
|
|
5223
|
+
if (detected.forge?.kind !== opts.forge) keys.forge = { kind: opts.forge };
|
|
5224
|
+
else if (detected.forge.url) keys.forge = { kind: opts.forge, url: detected.forge.url };
|
|
5225
|
+
} else if (detected.forge) {
|
|
5226
|
+
forge = { kind: detected.forge.kind, because: detected.forge.because };
|
|
5227
|
+
if (detected.forge.url) keys.forge = { kind: detected.forge.kind, url: detected.forge.url };
|
|
5599
5228
|
} else {
|
|
5600
5229
|
const kind = io.ask && !opts.yes ? await io.ask("Forge", FORGES, "github") : "github";
|
|
5601
5230
|
forge = { kind, because: io.ask && !opts.yes ? "you chose it" : "origin names no forge init knows, so github; change forge.kind in plywood.yml" };
|
|
5602
5231
|
keys.forge = { kind };
|
|
5603
5232
|
}
|
|
5604
5233
|
const fromEnv = io.env.PULUMI_BACKEND_URL?.trim();
|
|
5605
|
-
if (fromEnv && !engineBackends &&
|
|
5234
|
+
if (fromEnv && !engineBackends && detected.projects.some((p) => !p.backendUrl)) {
|
|
5606
5235
|
keys.backend = fromEnv;
|
|
5607
5236
|
notes.push(`backend ${fromEnv} came from PULUMI_BACKEND_URL; it is in plywood.yml so the jobs use it too`);
|
|
5608
5237
|
}
|
|
5609
5238
|
return { keys, notes, forge };
|
|
5610
5239
|
}
|
|
5611
|
-
function initStacks(
|
|
5240
|
+
function initStacks(detected, doc, members = []) {
|
|
5612
5241
|
const listed = stacksOf(doc);
|
|
5613
5242
|
const pinned = new Map(members.filter((m) => m.engineBin && m.engineVersion).map((m) => [m.name, m]));
|
|
5614
5243
|
const out = [];
|
|
@@ -5616,8 +5245,8 @@ function initStacks(detected2, doc, members = []) {
|
|
|
5616
5245
|
const add = (key, project, stack, dir) => {
|
|
5617
5246
|
const own = listed[key] ?? {};
|
|
5618
5247
|
const backend = own.backend ?? (typeof doc.backend === "string" ? doc.backend : void 0);
|
|
5619
|
-
const
|
|
5620
|
-
const grain = lockGrainFor(doc,
|
|
5248
|
+
const engine3 = engineOf(doc, backend)?.engine;
|
|
5249
|
+
const grain = lockGrainFor(doc, engine3);
|
|
5621
5250
|
const stackDir = own.dir ?? dir;
|
|
5622
5251
|
const packs = packsForStack(stepPolicyPacksOf(doc), stackDir);
|
|
5623
5252
|
const pin = pinned.get(key);
|
|
@@ -5626,7 +5255,7 @@ function initStacks(detected2, doc, members = []) {
|
|
|
5626
5255
|
project,
|
|
5627
5256
|
stack,
|
|
5628
5257
|
dir: stackDir,
|
|
5629
|
-
...
|
|
5258
|
+
...engine3 ? { engine: engine3 } : {},
|
|
5630
5259
|
// plywood#22: the step's backend, so a preview and an update read this stack's store, not the job's PULUMI_BACKEND_URL.
|
|
5631
5260
|
...backend ? { backend } : {},
|
|
5632
5261
|
...pin ? { engineVersion: pin.engineVersion, ...pin.engineRoot ? { engineRoot: pin.engineRoot } : {} } : {},
|
|
@@ -5638,7 +5267,7 @@ function initStacks(detected2, doc, members = []) {
|
|
|
5638
5267
|
});
|
|
5639
5268
|
seen.add(key);
|
|
5640
5269
|
};
|
|
5641
|
-
for (const s of
|
|
5270
|
+
for (const s of detected.stacks) add(s.key, s.project, s.stack, s.dir);
|
|
5642
5271
|
for (const [key, own] of Object.entries(listed)) {
|
|
5643
5272
|
if (seen.has(key)) continue;
|
|
5644
5273
|
const [project, stack] = key.split("/");
|
|
@@ -5646,30 +5275,22 @@ function initStacks(detected2, doc, members = []) {
|
|
|
5646
5275
|
}
|
|
5647
5276
|
return out;
|
|
5648
5277
|
}
|
|
5649
|
-
function forgeFor2(doc,
|
|
5278
|
+
function forgeFor2(doc, detected, opts) {
|
|
5650
5279
|
if (opts.forge) return { kind: opts.forge, because: "--forge" };
|
|
5651
5280
|
const kind = isObject(doc.forge) ? doc.forge.kind : void 0;
|
|
5652
5281
|
if (kind) return { kind, because: "plywood.yml says forge.kind" };
|
|
5653
|
-
if (
|
|
5282
|
+
if (detected.forge) return { kind: detected.forge.kind, because: detected.forge.because };
|
|
5654
5283
|
return { kind: "github", because: "origin names no forge init knows, so github" };
|
|
5655
5284
|
}
|
|
5656
|
-
async function chantWriteCiHook() {
|
|
5657
|
-
try {
|
|
5658
|
-
const m = await import("@intentius/chant/components/check-hooks");
|
|
5659
|
-
return typeof m.writeCiHook === "function" ? m.writeCiHook : void 0;
|
|
5660
|
-
} catch {
|
|
5661
|
-
return void 0;
|
|
5662
|
-
}
|
|
5663
|
-
}
|
|
5664
5285
|
async function runInit(opts, table, io) {
|
|
5665
|
-
const
|
|
5666
|
-
const { root } =
|
|
5286
|
+
const detected = detect(io.cwd);
|
|
5287
|
+
const { root } = detected;
|
|
5667
5288
|
const files = [];
|
|
5668
5289
|
const notes = [];
|
|
5669
5290
|
const todo = [];
|
|
5670
5291
|
const buckets = [];
|
|
5671
5292
|
const adopt = [];
|
|
5672
|
-
const imported = opts.from ? runImport(root,
|
|
5293
|
+
const imported = opts.from ? runImport(root, detected, opts.from) : void 0;
|
|
5673
5294
|
if (imported && imported.files.length === 0) notes.push(`--from ${imported.source} found no file to read${imported.notes.length ? `: ${imported.notes.join("; ")}` : ""}`);
|
|
5674
5295
|
const fail = (message, forge2 = { kind: "github", because: "" }) => ({
|
|
5675
5296
|
ok: false,
|
|
@@ -5684,8 +5305,8 @@ async function runInit(opts, table, io) {
|
|
|
5684
5305
|
adopt,
|
|
5685
5306
|
...imported ? { imported } : {}
|
|
5686
5307
|
});
|
|
5687
|
-
if (
|
|
5688
|
-
if (
|
|
5308
|
+
if (detected.projects.length === 0) return fail(`no Pulumi.yaml under ${root}; plywood init sets up a repository that has a Pulumi program`);
|
|
5309
|
+
if (detected.stacks.length === 0) notes.push("no Pulumi.<stack>.yaml beside any Pulumi.yaml, so no stack is set up; run pulumi stack init, then plywood init again");
|
|
5689
5310
|
let config2;
|
|
5690
5311
|
let forge;
|
|
5691
5312
|
const kept = existingConfig(root);
|
|
@@ -5699,10 +5320,10 @@ async function runInit(opts, table, io) {
|
|
|
5699
5320
|
const onPlunge = opts.engine === "plunge" && engineOf(config2.doc)?.engine === "plunge";
|
|
5700
5321
|
const ignored = [opts.preset && "--preset", opts.gate && "--gate", opts.engine && !onPlunge && "--engine", imported && "--from"].filter(Boolean);
|
|
5701
5322
|
if (ignored.length) notes.push(`${kept} is kept as it is, so ${ignored.join(", ")} changed nothing; set the keys there`);
|
|
5702
|
-
if (opts.engine === "plunge" && !onPlunge) notes.push(...keptPlungeLines(kept,
|
|
5703
|
-
forge = forgeFor2(config2.doc,
|
|
5323
|
+
if (opts.engine === "plunge" && !onPlunge) notes.push(...keptPlungeLines(kept, detected, io.env));
|
|
5324
|
+
forge = forgeFor2(config2.doc, detected, opts);
|
|
5704
5325
|
} else {
|
|
5705
|
-
const made = await newConfigKeys(
|
|
5326
|
+
const made = await newConfigKeys(detected, opts, io);
|
|
5706
5327
|
notes.push(...made.notes);
|
|
5707
5328
|
forge = made.forge;
|
|
5708
5329
|
const text = plywoodYml(imported ? mergeKeys(made.keys, imported.keys) : made.keys);
|
|
@@ -5715,15 +5336,13 @@ async function runInit(opts, table, io) {
|
|
|
5715
5336
|
if (problems.length) return fail(problems.map((p) => `${kept}: ${p.key} ${p.message}`).join("\n"), forge);
|
|
5716
5337
|
const doc = config2.doc;
|
|
5717
5338
|
const settings = toChantSettings(config2);
|
|
5718
|
-
const
|
|
5719
|
-
|
|
5720
|
-
const reviewSuffix = reviewStacks2 ? reviewSuffixOf(settings.pipeline.reviewStackName) : void 0;
|
|
5721
|
-
const stacks2 = initStacks(detected2, doc, settings.members);
|
|
5339
|
+
const reviewSuffix = config2.doc.review_stacks !== void 0 ? reviewSuffixOf(settings.pipeline.reviewStackName) : void 0;
|
|
5340
|
+
const stacks2 = initStacks(detected, doc, settings.members);
|
|
5722
5341
|
const order = await stackOrder(root, stacks2, settings.pipeline.after);
|
|
5723
5342
|
const ordered = orderWaves(wavesOf(stacks2, settings.pipeline.waves), order.edges, settings.pipeline.canary);
|
|
5724
5343
|
const waves = ordered.waves;
|
|
5725
|
-
if (order.edges.length || order.outside.length || ordered.unmet.length
|
|
5726
|
-
const declaration = declarationStep(root,
|
|
5344
|
+
if (order.edges.length || order.outside.length || ordered.unmet.length) notes.push(...orderLines(order, ordered.unmet));
|
|
5345
|
+
const declaration = declarationStep(root, detected, settings, opts.dryRun);
|
|
5727
5346
|
files.push(declaration.file);
|
|
5728
5347
|
if (existsSync10(join13(root, CHANT_CONFIG_FILE))) {
|
|
5729
5348
|
const text = readFileSync12(join13(root, CHANT_CONFIG_FILE), "utf8");
|
|
@@ -5733,19 +5352,16 @@ async function runInit(opts, table, io) {
|
|
|
5733
5352
|
} else files.push(put(root, CHANT_CONFIG_FILE, chantConfigTs(), opts.dryRun));
|
|
5734
5353
|
files.push(put(root, COMPONENTS_FILE, componentsFile(stacks2, settings.pipeline.after, reviewSuffix ? { reviewSuffix } : {}), opts.dryRun));
|
|
5735
5354
|
files.push(...providersPresetStep(root, doc, opts.dryRun, notes));
|
|
5736
|
-
if (
|
|
5737
|
-
for (const wf of
|
|
5355
|
+
if (detected.preCommit) files.push(await preCommitStep(root, detected.preCommit, opts.dryRun));
|
|
5356
|
+
for (const wf of detected.pulumiActions) {
|
|
5738
5357
|
notes.push(`${wf} runs pulumi/actions. init leaves it as it is; once plywood's previews and updates replace it, remove it, or keep it for the stacks plywood does not run`);
|
|
5739
5358
|
}
|
|
5740
5359
|
const chant = await chantProduct();
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
files.push({ file: WAVES_SPEC_FILE2, status: "skipped", note: message }, { file: WORKFLOW_FILE[forge.kind], status: "skipped", note: "needs the same chant" });
|
|
5744
|
-
} else {
|
|
5745
|
-
let spec = await wavesSpecStep(chant, stacks2, doc, settings, detected2.defaultBranch, waves, forge.kind);
|
|
5360
|
+
{
|
|
5361
|
+
let spec = await wavesSpecStep(chant, stacks2, doc, settings, detected.defaultBranch, waves, forge.kind);
|
|
5746
5362
|
if (settings.pipeline.jobs) {
|
|
5747
5363
|
try {
|
|
5748
|
-
spec = withJobSettings(spec, settings.pipeline.jobs, forge.kind
|
|
5364
|
+
spec = withJobSettings(spec, settings.pipeline.jobs, forge.kind);
|
|
5749
5365
|
} catch (e) {
|
|
5750
5366
|
files.push({ file: WAVES_SPEC_FILE2, status: "failed", note: e.message });
|
|
5751
5367
|
spec = void 0;
|
|
@@ -5757,10 +5373,10 @@ async function runInit(opts, table, io) {
|
|
|
5757
5373
|
}
|
|
5758
5374
|
if (spec) files.push(...await workflowStep(chant, root, doc, settings, forge.kind, opts, stacks2.length > 0 ? spec : void 0, table, io));
|
|
5759
5375
|
}
|
|
5760
|
-
if (opts.bucketCheck) notes.push(...await bucketStep(
|
|
5761
|
-
adopt.push(...await adoptStep(root,
|
|
5762
|
-
todo.push(...todoLines(
|
|
5763
|
-
if (forge.kind === "gitlab" &&
|
|
5376
|
+
if (opts.bucketCheck) notes.push(...await bucketStep(detected, doc, io.env, buckets));
|
|
5377
|
+
adopt.push(...await adoptStep(root, detected, doc, io.env, declaration.doc, opts.dryRun, files, notes, todo));
|
|
5378
|
+
todo.push(...todoLines(detected, doc, forge.kind, io.env));
|
|
5379
|
+
if (forge.kind === "gitlab" && stacks2.length > 0 && previewOf(doc)) {
|
|
5764
5380
|
todo.push(`Add a ${GITLAB_READ_TOKEN} CI/CD variable (a project access token with the read_api scope, masked and protected) so a wave can read the merge request it applies; without it the wave refuses`);
|
|
5765
5381
|
}
|
|
5766
5382
|
const failed = files.find((f) => f.status === "failed");
|
|
@@ -5778,9 +5394,9 @@ async function runInit(opts, table, io) {
|
|
|
5778
5394
|
...imported ? { imported } : {}
|
|
5779
5395
|
};
|
|
5780
5396
|
}
|
|
5781
|
-
function declarationStep(root,
|
|
5397
|
+
function declarationStep(root, detected, settings, dryRun) {
|
|
5782
5398
|
if (existsSync10(join13(root, "chant.workspace.jsonc"))) {
|
|
5783
|
-
return { file: { file: "chant.workspace.jsonc", status: "kept", note: `add a member of kind pulumi for each of ${
|
|
5399
|
+
return { file: { file: "chant.workspace.jsonc", status: "kept", note: `add a member of kind pulumi for each of ${detected.projects.map((p) => p.dir).join(", ")}, and pin ${PULUMI_LEXICON}@${CHANT_PIN}` } };
|
|
5784
5400
|
}
|
|
5785
5401
|
let existing;
|
|
5786
5402
|
const file = join13(root, DECLARATION_FILE);
|
|
@@ -5793,7 +5409,7 @@ function declarationStep(root, detected2, settings, dryRun) {
|
|
|
5793
5409
|
return { file: { file: DECLARATION_FILE, status: "failed", note: `is not JSON init can merge into (${e.message})` } };
|
|
5794
5410
|
}
|
|
5795
5411
|
}
|
|
5796
|
-
const doc = workspaceDeclaration(existing, { name: repoName(root), projects:
|
|
5412
|
+
const doc = workspaceDeclaration(existing, { name: repoName(root), projects: detected.projects, ci: settings.ci, lexiconVersion: CHANT_PIN });
|
|
5797
5413
|
if (existing && JSON.stringify(existing) === JSON.stringify(doc)) return { file: { file: DECLARATION_FILE, status: "unchanged" }, doc };
|
|
5798
5414
|
return { file: put(root, DECLARATION_FILE, jsonText(doc), dryRun), doc };
|
|
5799
5415
|
}
|
|
@@ -5813,10 +5429,7 @@ function providersPresetStep(root, doc, dryRun, notes) {
|
|
|
5813
5429
|
return out;
|
|
5814
5430
|
}
|
|
5815
5431
|
async function preCommitStep(root, config2, dryRun) {
|
|
5816
|
-
const write = await
|
|
5817
|
-
if (!write) {
|
|
5818
|
-
return { file: DECLARATION_FILE, status: "skipped", note: `${config2} found; running it as a check hook needs chant >= the release after 0.124.54 (chant#4556's writeCiHook). This plywood was built with chant ${BUILT_CHANT}` };
|
|
5819
|
-
}
|
|
5432
|
+
const { writeCiHook: write } = await import("@intentius/chant/components/check-hooks");
|
|
5820
5433
|
if (dryRun || !existsSync10(join13(root, DECLARATION_FILE))) return { file: DECLARATION_FILE, status: "skipped", note: `${config2} would run as the check hook pre-commit` };
|
|
5821
5434
|
const before = readFileSync12(join13(root, DECLARATION_FILE), "utf8");
|
|
5822
5435
|
try {
|
|
@@ -5828,7 +5441,7 @@ async function preCommitStep(root, config2, dryRun) {
|
|
|
5828
5441
|
return { file: DECLARATION_FILE, status: after === before ? "unchanged" : "written", note: `${config2} runs as the check hook pre-commit; the check job's image needs pre-commit` };
|
|
5829
5442
|
}
|
|
5830
5443
|
async function wavesSpecStep(chant, stacks2, doc, settings, branch, ordered, forge) {
|
|
5831
|
-
const runner = waveRunner(chantPipelineOptions(settings.pipeline
|
|
5444
|
+
const runner = waveRunner(chantPipelineOptions(settings.pipeline).runner);
|
|
5832
5445
|
const rego = isObject(doc.policy) && (typeof doc.policy.rego === "string" || doc.policy.source !== void 0 || doc.policy.command !== void 0);
|
|
5833
5446
|
const base = wavesSpec({
|
|
5834
5447
|
...rego ? { policy: true } : {},
|
|
@@ -5838,11 +5451,10 @@ async function wavesSpecStep(chant, stacks2, doc, settings, branch, ordered, for
|
|
|
5838
5451
|
branch,
|
|
5839
5452
|
...runner !== void 0 ? { runner } : {},
|
|
5840
5453
|
...settings.pipeline.quorum?.length ? { quorum: settings.pipeline.quorum } : {},
|
|
5841
|
-
|
|
5842
|
-
...settings.pipeline.notify ? { notify: { ...notifyForChant(settings.pipeline.notify, chant) } } : {}
|
|
5454
|
+
...settings.pipeline.notify ? { notify: { ...settings.pipeline.notify } } : {}
|
|
5843
5455
|
});
|
|
5844
|
-
const named = chant.productWavesSpec
|
|
5845
|
-
return previewOf(doc)
|
|
5456
|
+
const named = chant.productWavesSpec(base, PRODUCT_WORDS);
|
|
5457
|
+
return previewOf(doc) ? withWavePreview(named, forge) : named;
|
|
5846
5458
|
}
|
|
5847
5459
|
async function workflowStep(chant, root, doc, settings, forge, opts, spec, table, io) {
|
|
5848
5460
|
const output = WORKFLOW_FILE[forge];
|
|
@@ -5851,17 +5463,16 @@ async function workflowStep(chant, root, doc, settings, forge, opts, spec, table
|
|
|
5851
5463
|
try {
|
|
5852
5464
|
const { noteControlTarget: noteControlTarget2 } = await Promise.resolve().then(() => (init_control2(), control_exports));
|
|
5853
5465
|
const control3 = await noteControlTarget2(doc, root);
|
|
5854
|
-
plan = workflowPlan(chant, doc, forge, opts.env, spec, chantBuildFlags(chantPipelineOptions(settings.pipeline
|
|
5466
|
+
plan = workflowPlan(chant, doc, forge, opts.env, spec, chantBuildFlags(chantPipelineOptions(settings.pipeline)), { prJobs, ...control3 ? { control: control3 } : {} });
|
|
5855
5467
|
} catch (e) {
|
|
5856
5468
|
return [{ file: output, status: "failed", note: e.message }];
|
|
5857
5469
|
}
|
|
5858
5470
|
if (opts.dryRun) return [{ file: output, status: "skipped", note: "--dry-run: chant's generator was not run" }];
|
|
5859
5471
|
const argv = plan.argv.map((a, i) => i === 1 && a === "." ? root : a);
|
|
5860
|
-
const written = await writeWorkflow(chant, { root, forge, output, argv, words: plan.prWords
|
|
5472
|
+
const written = await writeWorkflow(chant, { root, forge, output, argv, words: plan.prWords });
|
|
5861
5473
|
if (written.status === "failed") return [{ file: output, status: "failed", note: `chant's generator exited ${written.code}; the old file is back as it was` }];
|
|
5862
5474
|
const notes = [
|
|
5863
5475
|
...written.kept.length ? [`kept your own jobs: ${written.kept.join(", ")}`] : [],
|
|
5864
|
-
...doc.review_stacks !== void 0 && !plan.argv.includes("--pr-env") ? [REVIEW_STACKS_NEED_CHANT] : [],
|
|
5865
5476
|
...prJobs.notes,
|
|
5866
5477
|
...plan.notes,
|
|
5867
5478
|
...writtenNotes(plan, written.file)
|
|
@@ -5873,18 +5484,17 @@ async function workflowStep(chant, root, doc, settings, forge, opts, spec, table
|
|
|
5873
5484
|
}
|
|
5874
5485
|
return out;
|
|
5875
5486
|
}
|
|
5876
|
-
async function bucketStep(
|
|
5487
|
+
async function bucketStep(detected, doc, env, reports) {
|
|
5877
5488
|
const listed = stacksOf(doc);
|
|
5878
5489
|
const urls = /* @__PURE__ */ new Set();
|
|
5879
|
-
for (const s of
|
|
5880
|
-
const project =
|
|
5490
|
+
for (const s of detected.stacks) {
|
|
5491
|
+
const project = detected.projects.find((p) => p.dir === s.dir && p.name === s.project) ?? { backendUrl: void 0 };
|
|
5881
5492
|
const b = backendOf(project, { ...listed[s.key]?.backend ? { stack: listed[s.key].backend } : {}, ...typeof doc.backend === "string" ? { all: doc.backend } : {} }, env);
|
|
5882
5493
|
if (b.url && !isPulumiCloud(b.url)) urls.add(b.url);
|
|
5883
5494
|
}
|
|
5884
5495
|
const targets = [...urls].map((u) => bucketTarget(u, env)).filter((t) => t !== void 0);
|
|
5885
5496
|
if (targets.length === 0) return [];
|
|
5886
5497
|
const check2 = await chantBucketCheck();
|
|
5887
|
-
if (!check2) return [needsBucketCheckMessage(BUILT_CHANT)];
|
|
5888
5498
|
const lines2 = ["State buckets:"];
|
|
5889
5499
|
for (const t of targets) {
|
|
5890
5500
|
if (!t.ok) {
|
|
@@ -5901,12 +5511,12 @@ async function bucketStep(detected2, doc, env, reports) {
|
|
|
5901
5511
|
}
|
|
5902
5512
|
return lines2;
|
|
5903
5513
|
}
|
|
5904
|
-
async function adoptStep(root,
|
|
5514
|
+
async function adoptStep(root, detected, doc, env, declaration, dryRun, files, notes, todo) {
|
|
5905
5515
|
const listed = stacksOf(doc);
|
|
5906
5516
|
const all = typeof doc.backend === "string" ? doc.backend : void 0;
|
|
5907
5517
|
const targets = [];
|
|
5908
|
-
for (const s of
|
|
5909
|
-
const project =
|
|
5518
|
+
for (const s of detected.stacks) {
|
|
5519
|
+
const project = detected.projects.find((p) => p.dir === s.dir && p.name === s.project) ?? { backendUrl: void 0 };
|
|
5910
5520
|
const own = listed[s.key];
|
|
5911
5521
|
const b = backendOf(project, { ...own?.backend ? { stack: own.backend } : {}, ...all ? { all } : {} }, env);
|
|
5912
5522
|
if (b.url && /^plunge\+/.test(b.url)) targets.push({ key: s.key, project: s.project, projectDir: s.dir, dir: own?.dir ?? s.dir, stack: s.stack, url: b.url });
|
|
@@ -5914,11 +5524,7 @@ async function adoptStep(root, detected2, doc, env, declaration, dryRun, files,
|
|
|
5914
5524
|
if (targets.length === 0) return [];
|
|
5915
5525
|
const chant = await chantAdopt();
|
|
5916
5526
|
const previews = await adoptPreviews(targets, chant, root);
|
|
5917
|
-
notes.push(...adoptLines(previews
|
|
5918
|
-
if (!chant?.hasOp) {
|
|
5919
|
-
files.push({ file: ADOPT_OP_FILE, status: "skipped", note: `the adopt Op needs @intentius/chant-lexicon-pulumi >= the release after 0.124.54 (chant#4562's PulumiAdoptOp). This plywood was built with chant ${BUILT_CHANT}` });
|
|
5920
|
-
return previews;
|
|
5921
|
-
}
|
|
5527
|
+
notes.push(...adoptLines(previews));
|
|
5922
5528
|
const left = stacksToAdopt(targets, previews);
|
|
5923
5529
|
if (left.length === 0) {
|
|
5924
5530
|
if (existsSync10(join13(root, ADOPT_OP_FILE))) files.push({ file: ADOPT_OP_FILE, status: "kept", note: "every stack is adopted; delete it" });
|
|
@@ -5929,17 +5535,17 @@ async function adoptStep(root, detected2, doc, env, declaration, dryRun, files,
|
|
|
5929
5535
|
todo.push(...adoptTodo(rows));
|
|
5930
5536
|
return previews;
|
|
5931
5537
|
}
|
|
5932
|
-
function keptPlungeLines(kept,
|
|
5933
|
-
const step = engineStep({ engine: "plunge", detected
|
|
5538
|
+
function keptPlungeLines(kept, detected, env) {
|
|
5539
|
+
const step = engineStep({ engine: "plunge", detected, env });
|
|
5934
5540
|
const entries = Object.entries(step.keys);
|
|
5935
5541
|
if (entries.length === 0) return step.notes;
|
|
5936
5542
|
const yaml = entries.map(([k, v]) => emitYAMLEntry2(k, v)).join("\n").split("\n").filter((l) => l !== "");
|
|
5937
5543
|
return [...step.notes, `To move to plunge, set these keys in ${kept}:`, ...yaml.map((l) => ` ${l}`)];
|
|
5938
5544
|
}
|
|
5939
|
-
function todoLines(
|
|
5545
|
+
function todoLines(detected, doc, forge, env) {
|
|
5940
5546
|
const out = [];
|
|
5941
5547
|
const all = typeof doc.backend === "string" ? doc.backend : void 0;
|
|
5942
|
-
const cloud =
|
|
5548
|
+
const cloud = detected.projects.some((p) => isPulumiCloud(backendOf(p, all ? { all } : {}, env).url));
|
|
5943
5549
|
const where = forge === "gitlab" ? "CI/CD variables" : "repository secrets";
|
|
5944
5550
|
const traded = identityClouds(doc);
|
|
5945
5551
|
if (cloud && traded.includes("pulumi")) {
|
|
@@ -5947,9 +5553,9 @@ function todoLines(detected2, doc, forge, env) {
|
|
|
5947
5553
|
} else if (cloud) {
|
|
5948
5554
|
out.push(`Set pulumi_cloud.org and identity.preview.pulumi and identity.up.pulumi in plywood.yml, so each job trades its OIDC token for its own Pulumi Cloud token, or add PULUMI_ACCESS_TOKEN_PREVIEW and PULUMI_ACCESS_TOKEN_UP to the ${where}`);
|
|
5949
5555
|
}
|
|
5950
|
-
if (traded.some((c) => c !== "pulumi")) out.push(`Let each role identity names trust this repository's OIDC tokens: the preview role for pull requests, the up role for ${
|
|
5556
|
+
if (traded.some((c) => c !== "pulumi")) out.push(`Let each role identity names trust this repository's OIDC tokens: the preview role for pull requests, the up role for ${detected.defaultBranch} only`);
|
|
5951
5557
|
else if (!cloud) out.push("Give the preview job and the update job their cloud access, each its own role through OIDC (identity in plywood.yml); the backend bucket's read and write go with them");
|
|
5952
|
-
out.push(`Make ${PRODUCT}/preview a required status on ${
|
|
5558
|
+
out.push(`Make ${PRODUCT}/preview a required status on ${detected.defaultBranch}`);
|
|
5953
5559
|
return out;
|
|
5954
5560
|
}
|
|
5955
5561
|
function initLines(r) {
|
|
@@ -5985,11 +5591,11 @@ var DECLARATION_FILE, CHANT_CONFIG_FILE, COMPONENTS_FILE, WAVES_SPEC_FILE2, CHAN
|
|
|
5985
5591
|
var init_init = __esm({
|
|
5986
5592
|
"src/init.ts"() {
|
|
5987
5593
|
"use strict";
|
|
5594
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
5988
5595
|
init_package();
|
|
5989
5596
|
init_chant_product();
|
|
5990
5597
|
init_chant_config();
|
|
5991
5598
|
init_identity();
|
|
5992
|
-
init_observe();
|
|
5993
5599
|
init_policy();
|
|
5994
5600
|
init_policy_presets();
|
|
5995
5601
|
init_approval();
|
|
@@ -6028,13 +5634,11 @@ var init_init = __esm({
|
|
|
6028
5634
|
var control_exports = {};
|
|
6029
5635
|
__export(control_exports, {
|
|
6030
5636
|
CONTROLLED_KEYS: () => CONTROLLED_KEYS,
|
|
6031
|
-
CONTROL_NEEDS_CHANT: () => CONTROL_NEEDS_CHANT,
|
|
6032
5637
|
chantControl: () => chantControl,
|
|
6033
5638
|
control: () => control2,
|
|
6034
5639
|
controlGenerator: () => controlGenerator,
|
|
6035
5640
|
controlTarget: () => controlTarget,
|
|
6036
5641
|
controlledConfigText: () => controlledConfigText,
|
|
6037
|
-
hasWavesControl: () => hasWavesControl,
|
|
6038
5642
|
noteControlTarget: () => noteControlTarget,
|
|
6039
5643
|
projectSettings: () => projectSettings,
|
|
6040
5644
|
setChantControlForTests: () => setChantControlForTests,
|
|
@@ -6047,15 +5651,10 @@ import { createRequire } from "node:module";
|
|
|
6047
5651
|
import { dirname as dirname10, join as join14 } from "node:path";
|
|
6048
5652
|
import { pathToFileURL } from "node:url";
|
|
6049
5653
|
import { emitYAMLEntry as emitYAMLEntry3, parseYAML as parseYAML4 } from "@intentius/chant/yaml";
|
|
6050
|
-
function hasWavesControl(chant) {
|
|
6051
|
-
return typeof chant.recordControlAppliedFromEnv === "function";
|
|
6052
|
-
}
|
|
6053
5654
|
async function wavesControl(cwd) {
|
|
6054
5655
|
const file = findConfig(cwd);
|
|
6055
5656
|
if (!file || controlOf(loadConfig(file).doc) === void 0) return { none: true };
|
|
6056
5657
|
const chant = await chantControl();
|
|
6057
|
-
if (!chant) return { missing: CONTROL_NEEDS_CHANT };
|
|
6058
|
-
if (!hasWavesControl(chant)) return { missing: "the waves workflow tells the control repo with a chant that has chant#4617" };
|
|
6059
5658
|
const target = await controlTarget(chant, cwd);
|
|
6060
5659
|
if ("error" in target) return target;
|
|
6061
5660
|
const settings = { ...controlOf(loadConfig(file).doc), member: target.member };
|
|
@@ -6067,7 +5666,7 @@ async function noteControlTarget(doc, root) {
|
|
|
6067
5666
|
const settings = controlOf(doc);
|
|
6068
5667
|
if (!settings) return void 0;
|
|
6069
5668
|
const chant = await chantControl();
|
|
6070
|
-
if (
|
|
5669
|
+
if (chant.controlSettingsProblems(settings).length > 0) return void 0;
|
|
6071
5670
|
try {
|
|
6072
5671
|
return chant.resolveControlTarget(settings, repoName(root));
|
|
6073
5672
|
} catch {
|
|
@@ -6075,18 +5674,11 @@ async function noteControlTarget(doc, root) {
|
|
|
6075
5674
|
}
|
|
6076
5675
|
}
|
|
6077
5676
|
function chantControl() {
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
const m = await import("@intentius/chant/ci-control");
|
|
6081
|
-
return REQUIRED3.every((n) => typeof m[n] === "function") ? m : void 0;
|
|
6082
|
-
} catch {
|
|
6083
|
-
return void 0;
|
|
6084
|
-
}
|
|
6085
|
-
})();
|
|
6086
|
-
return cached7;
|
|
5677
|
+
cached5 ??= import("@intentius/chant/ci-control").then((m) => m);
|
|
5678
|
+
return cached5;
|
|
6087
5679
|
}
|
|
6088
5680
|
function setChantControlForTests(fake) {
|
|
6089
|
-
|
|
5681
|
+
cached5 = fake === "reset" ? void 0 : Promise.resolve(fake);
|
|
6090
5682
|
}
|
|
6091
5683
|
function projectSettings(defaults, own) {
|
|
6092
5684
|
return { ...defaults ?? {}, ...own };
|
|
@@ -6132,7 +5724,6 @@ async function control2(args, json, table, io) {
|
|
|
6132
5724
|
const subs = ["init", "put", "import", "list", "retire", "reconcile", "dispatch"];
|
|
6133
5725
|
if (!sub || !subs.includes(sub)) return usage(`control takes one of ${subs.join(", ")}`, table, io);
|
|
6134
5726
|
const chant = await chantControl();
|
|
6135
|
-
if (!chant) return usage(`${CONTROL_NEEDS_CHANT}. Upgrade plywood once that chant is out, or build against a local chant with 'just chant-local'.`, table, io);
|
|
6136
5727
|
const emit = (envelope, lines2) => {
|
|
6137
5728
|
if (json) io.out(JSON.stringify({ command: `control ${sub}`, ...envelope }));
|
|
6138
5729
|
else for (const l of lines2) io.out(l);
|
|
@@ -6180,8 +5771,8 @@ async function controlInit(chant, args, emit, table, io) {
|
|
|
6180
5771
|
const before = existsSync11(path) ? readFileSync13(path, "utf8") : void 0;
|
|
6181
5772
|
const status = before === file.content ? "unchanged" : "written";
|
|
6182
5773
|
if (!dryRun && status === "written") {
|
|
6183
|
-
const { mkdirSync:
|
|
6184
|
-
|
|
5774
|
+
const { mkdirSync: mkdirSync7 } = await import("node:fs");
|
|
5775
|
+
mkdirSync7(dirname10(path), { recursive: true });
|
|
6185
5776
|
writeFileSync7(path, file.content);
|
|
6186
5777
|
}
|
|
6187
5778
|
emit({ ok: true, exitCode: table.done, forge, file: file.path, status, dryRun, notes: file.notes }, [
|
|
@@ -6316,10 +5907,11 @@ async function controlReconcile(chant, args, emit, table, io) {
|
|
|
6316
5907
|
emit({ ok: !failed, exitCode: code, mode, outcomes }, chant.describeReconcile(outcomes, mode).split("\n"));
|
|
6317
5908
|
return code;
|
|
6318
5909
|
}
|
|
6319
|
-
var CONTROLLED_KEYS,
|
|
5910
|
+
var CONTROLLED_KEYS, WAVES_CONTROL_KEYS, cached5, validateControlSettings;
|
|
6320
5911
|
var init_control2 = __esm({
|
|
6321
5912
|
"src/control.ts"() {
|
|
6322
5913
|
"use strict";
|
|
5914
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
6323
5915
|
init_config();
|
|
6324
5916
|
init_control();
|
|
6325
5917
|
init_routing();
|
|
@@ -6328,9 +5920,7 @@ var init_control2 = __esm({
|
|
|
6328
5920
|
init_stages();
|
|
6329
5921
|
init_words();
|
|
6330
5922
|
CONTROLLED_KEYS = ["gate", "approval", "approval_rules", "pulumi", "plunge", "image"];
|
|
6331
|
-
CONTROL_NEEDS_CHANT = "the control repo needs chant >= the release after 0.124.54, which carries @intentius/chant/ci-control (chant#4568)";
|
|
6332
5923
|
WAVES_CONTROL_KEYS = ["repo", "op", "forge", "member", "tokenSecret", "readTokenSecret", "ref", "apiBase"];
|
|
6333
|
-
REQUIRED3 = ["controlDispatchRequest", "putControlEntry", "reconcileProjects"];
|
|
6334
5924
|
validateControlSettings = (_entry, _project, settings) => {
|
|
6335
5925
|
const out = [];
|
|
6336
5926
|
for (const key of Object.keys(settings)) {
|
|
@@ -6364,13 +5954,8 @@ function positionals(args) {
|
|
|
6364
5954
|
}
|
|
6365
5955
|
return out;
|
|
6366
5956
|
}
|
|
6367
|
-
async function withChant(
|
|
6368
|
-
|
|
6369
|
-
if (!chant) {
|
|
6370
|
-
io.err(`${PRODUCT}: ${needsChantMessage(what)}`);
|
|
6371
|
-
return table.failed;
|
|
6372
|
-
}
|
|
6373
|
-
return run(chant);
|
|
5957
|
+
async function withChant(_what, _table, _io, run) {
|
|
5958
|
+
return run(await chantProduct());
|
|
6374
5959
|
}
|
|
6375
5960
|
function passthrough(argv, table, io) {
|
|
6376
5961
|
if (argv[0] === "run" && argv[1] === "wave") return runWave(argv, table, io);
|
|
@@ -6433,7 +6018,6 @@ async function preview(args, json, table, io) {
|
|
|
6433
6018
|
const gate = rest.includes("--gate") ? [] : ["--gate", PR_GATE];
|
|
6434
6019
|
const argv = ["components", "pr-plan", "--base", at, "--pr", number, ...gate, ...stack ? ["--member", stack] : [], ...rest, ...json ? ["--json"] : []];
|
|
6435
6020
|
return withChant(`${PRODUCT} preview`, table, io, async (chant) => {
|
|
6436
|
-
if (targeted && !hasChant4620(chant)) return usage(PREVIEW_TARGET_NEEDS_CHANT, table, io);
|
|
6437
6021
|
return chant.runChantInProcess(argv, PRODUCT_WORDS);
|
|
6438
6022
|
});
|
|
6439
6023
|
}
|
|
@@ -6472,12 +6056,6 @@ async function ci(args, json, table, io) {
|
|
|
6472
6056
|
const env = envFlag.value ?? "production";
|
|
6473
6057
|
const output = outFlag.value ?? WORKFLOW_FILE[forge];
|
|
6474
6058
|
return withChant(`${PRODUCT} ci`, table, io, async (chant) => {
|
|
6475
|
-
const reviewStacks2 = await chantReviewStacks();
|
|
6476
|
-
if (config2.doc.review_stacks !== void 0 && !reviewStacks2) io.err(`${PRODUCT}: ${REVIEW_STACKS_NEED_CHANT}`);
|
|
6477
|
-
if (applyWhenOf(config2.doc) === "pull-request") {
|
|
6478
|
-
const note = requiresNotHonoured(chant, config2.doc);
|
|
6479
|
-
if (note) io.err(`${PRODUCT}: ${note}`);
|
|
6480
|
-
}
|
|
6481
6059
|
let waves;
|
|
6482
6060
|
try {
|
|
6483
6061
|
waves = readWavesSpec(io.cwd);
|
|
@@ -6485,12 +6063,12 @@ async function ci(args, json, table, io) {
|
|
|
6485
6063
|
return usage(`.chant/op-waves/${OP_NAME}.spec.json: ${e.message}`, table, io);
|
|
6486
6064
|
}
|
|
6487
6065
|
const pipeline = toChantSettings(config2).pipeline;
|
|
6488
|
-
const { prJobsFor: prJobsFor2,
|
|
6066
|
+
const { prJobsFor: prJobsFor2, withJobSettings: withJobSettings2 } = await Promise.resolve().then(() => (init_jobs2(), jobs_exports));
|
|
6489
6067
|
const prJobs = await prJobsFor2(pipeline);
|
|
6490
6068
|
for (const note of prJobs.notes) io.err(`${PRODUCT}: ${note}`);
|
|
6491
6069
|
if (waves !== void 0 && pipeline.jobs) {
|
|
6492
6070
|
try {
|
|
6493
|
-
waves = withJobSettings2(waves, pipeline.jobs, forge
|
|
6071
|
+
waves = withJobSettings2(waves, pipeline.jobs, forge);
|
|
6494
6072
|
} catch (e) {
|
|
6495
6073
|
io.err(`${PRODUCT}: ${e.message}`);
|
|
6496
6074
|
return table.failed;
|
|
@@ -6500,7 +6078,8 @@ async function ci(args, json, table, io) {
|
|
|
6500
6078
|
const noteControl = await noteControlTarget2(config2.doc, io.cwd);
|
|
6501
6079
|
let plan;
|
|
6502
6080
|
try {
|
|
6503
|
-
|
|
6081
|
+
const engine3 = chantBuildFlags(chantPipelineOptions(pipeline));
|
|
6082
|
+
plan = workflowPlan(chant, config2.doc, forge, env, waves, engine3, { prJobs, ...noteControl ? { control: noteControl } : {} });
|
|
6504
6083
|
} catch (e) {
|
|
6505
6084
|
io.err(`${PRODUCT}: ${e.message}`);
|
|
6506
6085
|
return table.failed;
|
|
@@ -6541,9 +6120,8 @@ async function ci(args, json, table, io) {
|
|
|
6541
6120
|
if (jobs2.action) io.err(` action ${jobs2.action.command} (${jobs2.action.comment} <stack> <name>, or a dispatch with inputs ${jobs2.action.inputs.member} and ${jobs2.action.inputs.action})`);
|
|
6542
6121
|
}
|
|
6543
6122
|
if (!json && "control" in control3) io.err(` control after the last wave, tells ${control3.repo} how the waves ended`);
|
|
6544
|
-
else if (!json && "missing" in control3) io.err(` control the waves workflow sends no dispatch: ${control3.missing}; a job of your own after the last wave runs '${PRODUCT} control dispatch'`);
|
|
6545
6123
|
if (dryRun) return table.done;
|
|
6546
|
-
const written = await writeWorkflow(chant, { root: io.cwd, forge, output, argv: plan.argv, words: plan.prWords
|
|
6124
|
+
const written = await writeWorkflow(chant, { root: io.cwd, forge, output, argv: plan.argv, words: plan.prWords });
|
|
6547
6125
|
if (!json && written.kept.length) io.err(` kept your own jobs: ${written.kept.join(", ")}`);
|
|
6548
6126
|
if (written.code === table.done) for (const note of writtenNotes(plan, written.file)) io.err(`${PRODUCT}: ${note}`);
|
|
6549
6127
|
if (json) io.out(JSON.stringify({ command: "ci", ok: written.code === 0, exitCode: written.code, dryRun, ...summary, status: written.status, kept: written.kept }));
|
|
@@ -6568,21 +6146,20 @@ function workflowPlan(chant, doc, forge, env, waves, extra = [], opts = {}) {
|
|
|
6568
6146
|
const shape = pipelineShape(doc, forge, waves);
|
|
6569
6147
|
const notes = [];
|
|
6570
6148
|
const pipeline = toChantSettings({ doc }).pipeline;
|
|
6571
|
-
const respond = respondOptions(
|
|
6149
|
+
const respond = respondOptions(pipeline, notes);
|
|
6572
6150
|
const applyMerge = shape.applyBeforeMerge ? pipeline.applyMerge : void 0;
|
|
6573
|
-
if (approvalOf(doc) === "pr-review" && shape.waves && hasChantApprovalStatus(chant) && !hasChantPullRequestApprovalStatus(chant)) notes.push(APPROVAL_STATUS_NOTE);
|
|
6574
6151
|
const actionJob = actionJobFor(doc, forge, notes);
|
|
6575
6152
|
const jobsWanted = actionJob ? [...shape.jobs, "action"] : shape.jobs;
|
|
6576
6153
|
const words = wordsForJobs(jobsWanted);
|
|
6577
6154
|
const requires = shape.applyBeforeMerge ? requiresArgv(chant, doc) : [];
|
|
6578
|
-
const previewComment = hasChant4620(chant);
|
|
6579
6155
|
const jobs2 = chant.productPipelineJobs(forge, env, {
|
|
6580
6156
|
words,
|
|
6581
6157
|
...shape.applyBeforeMerge ? { applyBeforeMerge: true } : {},
|
|
6582
6158
|
...shape.locks ? { locks: shape.locks } : {},
|
|
6583
6159
|
...shape.waves ? { waves } : {},
|
|
6584
6160
|
...requires.length ? { applyRequires: requires[1].split(",") } : {},
|
|
6585
|
-
|
|
6161
|
+
// chant#4620: `/plywood preview` previews the pull request again.
|
|
6162
|
+
previewComment: true,
|
|
6586
6163
|
// chant#4619: the note job's read of the control repo comes back as post.control.
|
|
6587
6164
|
...opts.control !== void 0 ? { control: { target: opts.control } } : {},
|
|
6588
6165
|
// plywood#29: runners, env, pass and parallelism on the pull request jobs, with chant#4625 (src/jobs.ts).
|
|
@@ -6608,12 +6185,14 @@ function workflowPlan(chant, doc, forge, env, waves, extra = [], opts = {}) {
|
|
|
6608
6185
|
...shape.applyBeforeMerge ? ["--apply-before-merge", ...requires] : [],
|
|
6609
6186
|
...applyMerge ? applyMergeArgv(applyMerge) : [],
|
|
6610
6187
|
...respond.argv,
|
|
6611
|
-
|
|
6188
|
+
"--preview-comment",
|
|
6612
6189
|
"--stale-notes",
|
|
6613
6190
|
...actionJob ? ["--action-job"] : [],
|
|
6614
|
-
// Review stacks (plywood#15): chant's pr-env jobs
|
|
6615
|
-
...doc.review_stacks !== void 0
|
|
6191
|
+
// Review stacks (plywood#15): chant's pr-env jobs.
|
|
6192
|
+
...doc.review_stacks !== void 0 ? ["--pr-env"] : [],
|
|
6616
6193
|
...opts.prJobs?.flags ?? [],
|
|
6194
|
+
// chant#4772: the control repo, so the note job the build writes reads its records (plywood#28).
|
|
6195
|
+
...opts.control !== void 0 ? controlBuildArgv(doc, opts.control.member) : [],
|
|
6617
6196
|
...extra
|
|
6618
6197
|
];
|
|
6619
6198
|
return { jobs: jobs2, words, prWords, argv, notes, ...applyMerge ? { applyMerge } : {} };
|
|
@@ -6626,15 +6205,11 @@ function actionJobFor(doc, forge, notes) {
|
|
|
6626
6205
|
function applyMergeArgv(m) {
|
|
6627
6206
|
return ["--merge", "auto", "--merge-token-env", m.tokenEnv, ...m.method ? ["--merge-method", m.method] : []];
|
|
6628
6207
|
}
|
|
6629
|
-
function respondOptions(
|
|
6208
|
+
function respondOptions(pipeline, notes) {
|
|
6630
6209
|
const r = pipeline.respond;
|
|
6631
6210
|
if (pipeline.decide && r?.description !== "check") notes.push(DECIDE_LEFT_OFF);
|
|
6632
6211
|
if (!r || Object.keys(r).length === 0) return { options: {}, argv: [] };
|
|
6633
6212
|
const keys = Object.keys(r).map((e) => e.replace("-", "_"));
|
|
6634
|
-
if (!hasChantRespond(chant)) {
|
|
6635
|
-
notes.push(`respond.${keys.join(", respond.")} ${keys.length === 1 ? "needs" : "need"} chant's responses to pipeline events (chant#4629), which this chant lacks; chant's defaults hold`);
|
|
6636
|
-
return { options: {}, argv: [] };
|
|
6637
|
-
}
|
|
6638
6213
|
const responses = { ...r };
|
|
6639
6214
|
const check2 = responses.description === "check";
|
|
6640
6215
|
const decide = check2 ? pipeline.decide : void 0;
|
|
@@ -6643,14 +6218,7 @@ function respondOptions(chant, pipeline, applyBeforeMerge, notes) {
|
|
|
6643
6218
|
notes.push("respond.description is check and decide names no backend and model, so the description check is left off");
|
|
6644
6219
|
}
|
|
6645
6220
|
const options = { respond: responses, ...decide ? { decide: { ...decide } } : {} };
|
|
6646
|
-
|
|
6647
|
-
const prSide = [
|
|
6648
|
-
...responses.description === "check" ? ["respond.description"] : [],
|
|
6649
|
-
...applyBeforeMerge && responses["apply-failed"] === "off" ? ["respond.apply_failed"] : [],
|
|
6650
|
-
...applyBeforeMerge && responses["wave-refused"] === "off" ? ["respond.wave_refused"] : []
|
|
6651
|
-
];
|
|
6652
|
-
if (prSide.length) notes.push(`${prSide.join(", ")} ${prSide.length === 1 ? "reaches" : "reach"} no pull request job yet: chant's build --pr-loop writes no responses into them, so the note and the update before merge keep chant's defaults; the waves workflow takes wave_refused and apply_failed`);
|
|
6653
|
-
return { options, argv: [] };
|
|
6221
|
+
return { options, argv: respondArgv(responses, decide) };
|
|
6654
6222
|
}
|
|
6655
6223
|
function respondArgv(responses, decide) {
|
|
6656
6224
|
const list = Object.keys(CHANT_RESPOND_DEFAULTS).filter((e) => responses[e] !== void 0 && responses[e] !== CHANT_RESPOND_DEFAULTS[e]).map((e) => `${e}=${responses[e]}`).join(",");
|
|
@@ -6659,35 +6227,30 @@ function respondArgv(responses, decide) {
|
|
|
6659
6227
|
}
|
|
6660
6228
|
function requiresArgv(chant, doc) {
|
|
6661
6229
|
const listed = applyRequiresOf(doc);
|
|
6662
|
-
if (listed.length === APPLY_REQUIRES.length
|
|
6230
|
+
if (listed.length === APPLY_REQUIRES.length) return [];
|
|
6663
6231
|
const parsed = chant.parseRequiresList(listed);
|
|
6664
6232
|
return "checks" in parsed ? ["--requires", parsed.checks.join(",")] : [];
|
|
6665
6233
|
}
|
|
6666
|
-
function requiresNotHonoured(chant, doc) {
|
|
6667
|
-
const listed = applyRequiresOf(doc);
|
|
6668
|
-
if (listed.length === APPLY_REQUIRES.length || hasChant4607(chant)) return void 0;
|
|
6669
|
-
return `apply.requires lists ${listed.join(", ")}, and the installed chant has no pr-apply --requires (chant#4607), so the update before merge asks all four checks: ${APPLY_REQUIRES.join(", ")}`;
|
|
6670
|
-
}
|
|
6671
6234
|
function usage(message, table, io) {
|
|
6672
6235
|
io.err(`${PRODUCT}: ${message}`);
|
|
6673
6236
|
return table.failed;
|
|
6674
6237
|
}
|
|
6675
|
-
var CHANT_PASSTHROUGH, VALUED,
|
|
6238
|
+
var CHANT_PASSTHROUGH, VALUED, WORKFLOW_FILE, ACTIONS_NOT_ON_GITLAB, CHANT_RESPOND_DEFAULTS, DECIDE_LEFT_OFF;
|
|
6676
6239
|
var init_stages = __esm({
|
|
6677
6240
|
"src/stages.ts"() {
|
|
6678
6241
|
"use strict";
|
|
6242
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
6679
6243
|
init_chant_product();
|
|
6680
6244
|
init_chant_config();
|
|
6681
|
-
init_chant_next();
|
|
6682
|
-
init_approval();
|
|
6683
6245
|
init_config_check();
|
|
6684
6246
|
init_apply();
|
|
6685
6247
|
init_config();
|
|
6248
|
+
init_control();
|
|
6686
6249
|
init_locks();
|
|
6687
6250
|
init_stacks();
|
|
6688
6251
|
init_routing();
|
|
6252
|
+
init_images();
|
|
6689
6253
|
init_workflow();
|
|
6690
|
-
init_review_stacks2();
|
|
6691
6254
|
init_waves_workflow();
|
|
6692
6255
|
init_drift2();
|
|
6693
6256
|
init_refusals();
|
|
@@ -6696,20 +6259,22 @@ var init_stages = __esm({
|
|
|
6696
6259
|
init_words();
|
|
6697
6260
|
CHANT_PASSTHROUGH = ["components", "run", "lock", "unlock", "query", "ledger", "audit-log"];
|
|
6698
6261
|
VALUED = /* @__PURE__ */ new Set(["--base", "--pr", "--forge", "--env", "--output", "-o", "--stack", "--file", "--head", "--gate", "--branch", "--report-url", "--target"]);
|
|
6699
|
-
PREVIEW_TARGET_NEEDS_CHANT = "--target needs a chant whose pr-plan passes it to the pulumi-up step (chant#4620), which this chant lacks";
|
|
6700
6262
|
WORKFLOW_FILE = {
|
|
6701
6263
|
github: ".github/workflows/plywood.yml",
|
|
6702
6264
|
forgejo: ".forgejo/workflows/plywood.yml",
|
|
6703
6265
|
gitlab: ".gitlab-ci.yml"
|
|
6704
6266
|
};
|
|
6705
6267
|
ACTIONS_NOT_ON_GITLAB = "plywood.yml declares actions, and a GitLab pipeline starts on no comment or dispatch with inputs, so it has no action job; run 'plywood action <stack> <name>' from a pipeline of your own";
|
|
6706
|
-
APPROVAL_STATUS_NOTE = `approval is pr-review, and chant posts its approval status only for waves that wait on pr-review; plywood's waves wait on the pull request's own approval, so no ${PRODUCT}/approval status is posted before merge`;
|
|
6707
6268
|
CHANT_RESPOND_DEFAULTS = Object.freeze({ "wave-refused": "diff", "apply-failed": "triage", description: "off" });
|
|
6708
6269
|
DECIDE_LEFT_OFF = "decide is set and respond.description is not check, so plywood leaves the decide service off the pull request jobs";
|
|
6709
6270
|
}
|
|
6710
6271
|
});
|
|
6711
6272
|
|
|
6273
|
+
// src/bin.ts
|
|
6274
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
6275
|
+
|
|
6712
6276
|
// src/chant-resolve.ts
|
|
6277
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
6713
6278
|
import * as nodeModule from "node:module";
|
|
6714
6279
|
var CHANT_SPECIFIER = /^@intentius\/chant(?:-lexicon-[a-z0-9-]+)?(?:\/|$)/;
|
|
6715
6280
|
function chantResolveHook(from) {
|
|
@@ -6735,50 +6300,22 @@ function installChantResolution(from = import.meta.url) {
|
|
|
6735
6300
|
}
|
|
6736
6301
|
|
|
6737
6302
|
// src/cli.ts
|
|
6303
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
6738
6304
|
init_config_check();
|
|
6739
6305
|
|
|
6740
6306
|
// src/exit-codes.ts
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
drift: 2,
|
|
6745
|
-
waiting: 3,
|
|
6746
|
-
moved: 4,
|
|
6747
|
-
held: 5,
|
|
6748
|
-
superseded: 6,
|
|
6749
|
-
denied: 7,
|
|
6750
|
-
declined: 8,
|
|
6751
|
-
misplaced: 9
|
|
6752
|
-
});
|
|
6753
|
-
var NAMES = Object.keys(FALLBACK_EXIT).filter((n) => n !== "misplaced");
|
|
6754
|
-
function isExitTable(t) {
|
|
6755
|
-
if (!t || typeof t !== "object") return false;
|
|
6756
|
-
const r = t;
|
|
6757
|
-
return NAMES.every((n) => Number.isInteger(r[n])) && (r.misplaced === void 0 || Number.isInteger(r.misplaced));
|
|
6758
|
-
}
|
|
6759
|
-
function withMisplaced(t) {
|
|
6760
|
-
return Number.isInteger(t.misplaced) ? t : Object.freeze({ ...t, misplaced: FALLBACK_EXIT.misplaced });
|
|
6761
|
-
}
|
|
6762
|
-
var cached;
|
|
6763
|
-
function exitCodes() {
|
|
6764
|
-
cached ??= (async () => {
|
|
6765
|
-
try {
|
|
6766
|
-
const m = await import("@intentius/chant/exit-codes");
|
|
6767
|
-
const table = m.CHANT_EXIT;
|
|
6768
|
-
if (isExitTable(table)) return { table: withMisplaced(table), source: "chant" };
|
|
6769
|
-
} catch {
|
|
6770
|
-
}
|
|
6771
|
-
return { table: FALLBACK_EXIT, source: "fallback" };
|
|
6772
|
-
})();
|
|
6773
|
-
return cached;
|
|
6774
|
-
}
|
|
6307
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
6308
|
+
import { CHANT_EXIT } from "@intentius/chant/exit-codes";
|
|
6309
|
+
var EXIT = CHANT_EXIT;
|
|
6775
6310
|
|
|
6776
6311
|
// src/actions.ts
|
|
6312
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
6777
6313
|
init_chant_product();
|
|
6778
6314
|
init_stacks();
|
|
6779
6315
|
init_stages();
|
|
6780
6316
|
|
|
6781
6317
|
// src/stack-ops.ts
|
|
6318
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
6782
6319
|
init_chant_config();
|
|
6783
6320
|
init_identity();
|
|
6784
6321
|
init_config();
|
|
@@ -6787,28 +6324,18 @@ init_adopt();
|
|
|
6787
6324
|
init_detect();
|
|
6788
6325
|
init_refusals();
|
|
6789
6326
|
init_stages();
|
|
6790
|
-
init_chant_product();
|
|
6791
6327
|
init_words();
|
|
6792
6328
|
import { existsSync as existsSync13 } from "node:fs";
|
|
6793
6329
|
import { dirname as dirname11, isAbsolute as isAbsolute2, join as join16, resolve as resolve2 } from "node:path";
|
|
6794
6330
|
var STACK_OPS_OP = "pulumi-stack-ops";
|
|
6795
|
-
var REQUIRED4 = ["gatedStackExport", "gatedStackCancel", "gatedStackMove"];
|
|
6796
6331
|
var cachedLocks;
|
|
6797
6332
|
function chantStackLockReader() {
|
|
6798
|
-
cachedLocks ??= (
|
|
6799
|
-
try {
|
|
6800
|
-
const m = await import("@intentius/chant-lexicon-pulumi");
|
|
6801
|
-
return typeof m.pulumiStackLockIds === "function" ? m.pulumiStackLockIds : void 0;
|
|
6802
|
-
} catch {
|
|
6803
|
-
return void 0;
|
|
6804
|
-
}
|
|
6805
|
-
})();
|
|
6333
|
+
cachedLocks ??= import("@intentius/chant-lexicon-pulumi").then((m) => m.pulumiStackLockIds);
|
|
6806
6334
|
return cachedLocks;
|
|
6807
6335
|
}
|
|
6808
6336
|
async function lockToCancel(given, stack, env) {
|
|
6809
6337
|
if (given) return { lockId: given, read: false };
|
|
6810
6338
|
const reader = await chantStackLockReader();
|
|
6811
|
-
if (!reader) return { error: STACK_OP_WORDS.needsLock };
|
|
6812
6339
|
let locks2;
|
|
6813
6340
|
try {
|
|
6814
6341
|
locks2 = await reader({ dir: stack.dir, stack: stack.stack, ...stack.project ? { project: stack.project } : {}, ...stack.engine ? { engine: stack.engine } : {}, env });
|
|
@@ -6821,34 +6348,10 @@ async function lockToCancel(given, stack, env) {
|
|
|
6821
6348
|
return { lockId: locks2.ids[0], read: true };
|
|
6822
6349
|
}
|
|
6823
6350
|
var cachedOps;
|
|
6824
|
-
var cachedRollback;
|
|
6825
6351
|
function chantStackOps() {
|
|
6826
|
-
cachedOps ??= (
|
|
6827
|
-
try {
|
|
6828
|
-
const m = await import("@intentius/chant-lexicon-pulumi");
|
|
6829
|
-
return REQUIRED4.every((name) => typeof m[name] === "function") ? m : void 0;
|
|
6830
|
-
} catch {
|
|
6831
|
-
return void 0;
|
|
6832
|
-
}
|
|
6833
|
-
})();
|
|
6352
|
+
cachedOps ??= import("@intentius/chant-lexicon-pulumi").then((m) => m);
|
|
6834
6353
|
return cachedOps;
|
|
6835
6354
|
}
|
|
6836
|
-
function chantStackRollback() {
|
|
6837
|
-
cachedRollback ??= (async () => {
|
|
6838
|
-
try {
|
|
6839
|
-
const m = await import("@intentius/chant-lexicon-pulumi");
|
|
6840
|
-
if (typeof m.pulumiReleaseInputs !== "function") return false;
|
|
6841
|
-
const copy2 = await import("@intentius/chant-lexicon-pulumi/release-copy");
|
|
6842
|
-
return typeof copy2.prepareReleaseCopy === "function";
|
|
6843
|
-
} catch {
|
|
6844
|
-
return false;
|
|
6845
|
-
}
|
|
6846
|
-
})();
|
|
6847
|
-
return cachedRollback;
|
|
6848
|
-
}
|
|
6849
|
-
function needsStackOpsMessage(what, feature, after = "0.124.54") {
|
|
6850
|
-
return `${what} needs @intentius/chant-lexicon-pulumi >= the release after ${after}, which carries ${feature}. This plywood was built with chant ${BUILT_CHANT}, which lacks it. Upgrade plywood once that chant is out, or build against a local chant with 'just chant-local'.`;
|
|
6851
|
-
}
|
|
6852
6355
|
function readConfig(cwd) {
|
|
6853
6356
|
const path = findConfig(cwd);
|
|
6854
6357
|
if (!path) return { error: `no plywood.yml in ${cwd} or above it; run '${PRODUCT} init' first` };
|
|
@@ -6944,10 +6447,6 @@ function reportOutcome(verb, outcome, gate, rules, done, json, table, io) {
|
|
|
6944
6447
|
}
|
|
6945
6448
|
async function runRequest(verb, json, table, io, call) {
|
|
6946
6449
|
const ops = await chantStackOps();
|
|
6947
|
-
if (!ops) {
|
|
6948
|
-
io.err(`${PRODUCT}: ${needsStackOpsMessage(`${PRODUCT} ${verb}`, "the gated stack operations (chant#4578)")}`);
|
|
6949
|
-
return table.failed;
|
|
6950
|
-
}
|
|
6951
6450
|
try {
|
|
6952
6451
|
return await call(ops);
|
|
6953
6452
|
} catch (e) {
|
|
@@ -7058,7 +6557,7 @@ async function moveStack(args, json, table, io) {
|
|
|
7058
6557
|
if (backend) return usage(backend, table, io);
|
|
7059
6558
|
const rules = requestRules(read.config);
|
|
7060
6559
|
const destName = destStackName(source, dest, read.config);
|
|
7061
|
-
const
|
|
6560
|
+
const engine3 = source.engine ?? dest.engine;
|
|
7062
6561
|
return runRequest("move", json, table, io, async (ops) => {
|
|
7063
6562
|
const outcome = await ops.gatedStackMove({
|
|
7064
6563
|
by: requesterOf(common.by, io.env),
|
|
@@ -7069,13 +6568,13 @@ async function moveStack(args, json, table, io) {
|
|
|
7069
6568
|
source: { member: source.key, dir: source.dir, stack: source.stack },
|
|
7070
6569
|
dest: { member: dest.key, dir: dest.dir, stack: destName },
|
|
7071
6570
|
urns,
|
|
7072
|
-
...
|
|
6571
|
+
...engine3 ? { engine: engine3 } : {}
|
|
7073
6572
|
});
|
|
7074
6573
|
return reportOutcome("move", outcome, `move:${source.stack}:${destName}`, rules, (r) => STACK_OP_WORDS.moved(source.key, dest.key, r), json, table, io);
|
|
7075
6574
|
});
|
|
7076
6575
|
}
|
|
7077
|
-
async function moveOntoPlunge(
|
|
7078
|
-
if (
|
|
6576
|
+
async function moveOntoPlunge(engine3, args, json, table, io) {
|
|
6577
|
+
if (engine3 !== "plunge") return usage(`--onto takes plunge, not '${engine3}'; a move between backends is an export and an import (see '${PRODUCT} export')`, table, io);
|
|
7079
6578
|
const extra = args.filter((a) => a.startsWith("-"));
|
|
7080
6579
|
if (extra.length) return usage(`move --onto does not take ${extra.join(" ")}`, table, io);
|
|
7081
6580
|
const named = positionals(args);
|
|
@@ -7125,10 +6624,6 @@ async function rollbackStack(args, json, table, io) {
|
|
|
7125
6624
|
argv = ["components", "rollback", env.value ?? environmentOf(stack.stack), "--component", slug(stack.key), ...digest.value ? ["--digest", digest.value] : []];
|
|
7126
6625
|
}
|
|
7127
6626
|
argv.push(...actor.value ? ["--actor", actor.value] : [], ...switches, ...json ? ["--json"] : []);
|
|
7128
|
-
if (!await chantStackRollback()) {
|
|
7129
|
-
io.err(`${PRODUCT}: ${needsStackOpsMessage(`${PRODUCT} rollback`, "rollback by release for stacks (chant#4542)")}`);
|
|
7130
|
-
return table.failed;
|
|
7131
|
-
}
|
|
7132
6627
|
return withChant(`${PRODUCT} rollback`, table, io, async (chant) => {
|
|
7133
6628
|
const code = await chant.runChantInProcess(argv, PRODUCT_WORDS);
|
|
7134
6629
|
if (code === table.waiting && !json) io.err(STACK_OP_WORDS.rollbackWaiting);
|
|
@@ -7146,7 +6641,6 @@ function chantStackAction() {
|
|
|
7146
6641
|
}
|
|
7147
6642
|
async function fromEvent(env) {
|
|
7148
6643
|
const chant = await chantProduct();
|
|
7149
|
-
if (!chant) return { error: `${PRODUCT} action --from-event reads the action job's comment or inputs through chant's productActionFromEnv, and chant's product entry did not load` };
|
|
7150
6644
|
const words = chant.assertProductWords({ ...PRODUCT_WORDS, commands: { ...PRODUCT_WORDS.commands, ...ACTION_COMMAND } });
|
|
7151
6645
|
const asked = chant.productActionFromEnv(words, env);
|
|
7152
6646
|
if (asked.kind === "refused") return { error: `the action job was asked for no action: ${asked.reason}` };
|
|
@@ -7217,6 +6711,7 @@ async function action(args, json, table, io) {
|
|
|
7217
6711
|
}
|
|
7218
6712
|
|
|
7219
6713
|
// src/audit.ts
|
|
6714
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
7220
6715
|
init_detect();
|
|
7221
6716
|
init_stages();
|
|
7222
6717
|
init_words();
|
|
@@ -7275,6 +6770,7 @@ init_agents2();
|
|
|
7275
6770
|
init_drift2();
|
|
7276
6771
|
|
|
7277
6772
|
// src/estate.ts
|
|
6773
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
7278
6774
|
init_chant_product();
|
|
7279
6775
|
init_chant_config();
|
|
7280
6776
|
init_config();
|
|
@@ -7284,19 +6780,12 @@ import { dirname as dirname12 } from "node:path";
|
|
|
7284
6780
|
var estateModule;
|
|
7285
6781
|
function chantEstate() {
|
|
7286
6782
|
estateModule ??= (async () => {
|
|
7287
|
-
|
|
7288
|
-
|
|
7289
|
-
|
|
7290
|
-
if (!["readEstateLedger", "buildLedgerEstate", "writeEstate"].every((f) => typeof m[f] === "function")) return void 0;
|
|
7291
|
-
if (typeof r.parseReportUrl !== "function" || typeof r.reportPut !== "function") return void 0;
|
|
7292
|
-
return { ...m, parseReportUrl: r.parseReportUrl, reportPut: r.reportPut };
|
|
7293
|
-
} catch {
|
|
7294
|
-
return void 0;
|
|
7295
|
-
}
|
|
6783
|
+
const m = await import("@intentius/chant/ci-estate");
|
|
6784
|
+
const r = await import("@intentius/chant/pr-report-url");
|
|
6785
|
+
return { readEstateLedger: m.readEstateLedger, buildLedgerEstate: m.buildLedgerEstate, writeEstate: m.writeEstate, parseReportUrl: r.parseReportUrl, reportPut: r.reportPut };
|
|
7296
6786
|
})();
|
|
7297
6787
|
return estateModule;
|
|
7298
6788
|
}
|
|
7299
|
-
var ESTATE_NEEDS_CHANT = `${PRODUCT} estate needs chant >= the release after 0.124.54, which carries @intentius/chant/ci-estate (chant#4612). Run '${PRODUCT} audit' for the ledger's rows on this chant.`;
|
|
7300
6789
|
function estateSchedules(config2) {
|
|
7301
6790
|
const drift3 = toChantSettings(config2).pipeline.drift;
|
|
7302
6791
|
const driftOps = (drift3?.stacks ?? []).map(driftOpName);
|
|
@@ -7315,11 +6804,6 @@ async function estate(args, json, table, io) {
|
|
|
7315
6804
|
}
|
|
7316
6805
|
if (rest.length) return usage(`estate does not take ${rest.join(" ")}`, table, io);
|
|
7317
6806
|
const chant = await chantEstate();
|
|
7318
|
-
if (!chant) {
|
|
7319
|
-
if (json) io.out(JSON.stringify({ command: "estate", ok: false, exitCode: table.failed, error: ESTATE_NEEDS_CHANT }));
|
|
7320
|
-
else io.err(`${PRODUCT}: ${ESTATE_NEEDS_CHANT}`);
|
|
7321
|
-
return table.failed;
|
|
7322
|
-
}
|
|
7323
6807
|
const path = flags["--file"] ?? findConfig(io.cwd);
|
|
7324
6808
|
let config2;
|
|
7325
6809
|
if (path) {
|
|
@@ -7335,8 +6819,7 @@ async function estate(args, json, table, io) {
|
|
|
7335
6819
|
}
|
|
7336
6820
|
}
|
|
7337
6821
|
const { schedules, driftOps } = config2 ? estateSchedules(config2) : { schedules: [], driftOps: [] };
|
|
7338
|
-
const
|
|
7339
|
-
const words = product ? product.assertProductWords(PRODUCT_WORDS) : PRODUCT_WORDS;
|
|
6822
|
+
const words = (await chantProduct()).assertProductWords(PRODUCT_WORDS);
|
|
7340
6823
|
const root = path ? dirname12(path) : io.cwd;
|
|
7341
6824
|
const load = await chant.readEstateLedger(root);
|
|
7342
6825
|
const built = chant.buildLedgerEstate({ load, words, schedules, ...driftOps.length ? { driftOps } : {} });
|
|
@@ -7361,79 +6844,37 @@ async function estate(args, json, table, io) {
|
|
|
7361
6844
|
}
|
|
7362
6845
|
|
|
7363
6846
|
// src/notify.ts
|
|
6847
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
7364
6848
|
init_stages();
|
|
7365
6849
|
init_words();
|
|
7366
|
-
var RELAY_NEEDS_CHANT = `${PRODUCT} notify relay needs chant >= the release after 0.124.54, which carries chant notify relay (chant#4611).`;
|
|
7367
6850
|
async function notify(args, json, table, io) {
|
|
7368
6851
|
if (args[0] !== "relay") return usage(`unknown notify command '${args[0] ?? ""}'. Run '${PRODUCT} notify relay'.`, table, io);
|
|
7369
|
-
return withChant(`${PRODUCT} notify relay`, table, io, (chant) =>
|
|
7370
|
-
if (typeof chant.startRelayServer !== "function") {
|
|
7371
|
-
if (json) io.out(JSON.stringify({ command: "notify relay", ok: false, exitCode: table.failed, error: RELAY_NEEDS_CHANT }));
|
|
7372
|
-
else io.err(`${PRODUCT}: ${RELAY_NEEDS_CHANT}`);
|
|
7373
|
-
return Promise.resolve(table.failed);
|
|
7374
|
-
}
|
|
7375
|
-
return chant.runChantInProcess(["notify", ...args], PRODUCT_WORDS);
|
|
7376
|
-
});
|
|
6852
|
+
return withChant(`${PRODUCT} notify relay`, table, io, (chant) => chant.runChantInProcess(["notify", ...args], PRODUCT_WORDS));
|
|
7377
6853
|
}
|
|
7378
6854
|
|
|
7379
|
-
// src/cli.ts
|
|
7380
|
-
init_policy();
|
|
7381
|
-
|
|
7382
6855
|
// src/guard.ts
|
|
6856
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
7383
6857
|
init_stages();
|
|
7384
6858
|
init_words();
|
|
7385
|
-
import { chmodSync, copyFileSync, existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "node:fs";
|
|
7386
6859
|
import { join as join17 } from "node:path";
|
|
7387
|
-
|
|
7388
|
-
|
|
7389
|
-
|
|
7390
|
-
};
|
|
7391
|
-
|
|
7392
|
-
|
|
7393
|
-
|
|
7394
|
-
|
|
7395
|
-
}
|
|
7396
|
-
function guardVerdict(line) {
|
|
7397
|
-
for (const words of segments(line)) {
|
|
7398
|
-
let i = 0;
|
|
7399
|
-
while (i < words.length && (LAUNCHERS.has(words[i]) || /^[A-Za-z_][A-Za-z0-9_]*=/.test(words[i]) || words[i - 1] === "env" && words[i].startsWith("-"))) i++;
|
|
7400
|
-
const bin = (words[i] ?? "").split("/").pop();
|
|
7401
|
-
const refused = GUARDED[bin];
|
|
7402
|
-
if (!refused) continue;
|
|
7403
|
-
for (let j = i + 1; j < words.length; j++) {
|
|
7404
|
-
const w = words[j];
|
|
7405
|
-
if (w.startsWith("-")) {
|
|
7406
|
-
if (VALUED_GLOBALS.has(w)) j++;
|
|
7407
|
-
continue;
|
|
7408
|
-
}
|
|
7409
|
-
if (refused.includes(w)) {
|
|
7410
|
-
return {
|
|
7411
|
-
engine: bin,
|
|
7412
|
-
subcommand: w,
|
|
7413
|
-
message: `${PRODUCT} guard: an agent session does not run '${bin} ${w}'. Open a pull request; ${PRODUCT} previews it, and the update runs after a person approves the digest.`
|
|
7414
|
-
};
|
|
7415
|
-
}
|
|
7416
|
-
break;
|
|
7417
|
-
}
|
|
6860
|
+
import { agentGuard, installAgentGuard } from "@intentius/chant/ci-product";
|
|
6861
|
+
var engine2 = { refuse: ["up", "update", "destroy"], valuedFlags: ["-C", "--cwd", "-s", "--stack", "--color", "--config-file", "--logflow", "-v", "--verbose", "--tracing", "--profiling", "--memprofilerate"] };
|
|
6862
|
+
var GUARD = {
|
|
6863
|
+
words: { product: PRODUCT, refusal: `an agent session does not run '{what}'. Open a pull request; ${PRODUCT} previews it, and the update runs after a person approves the digest.` },
|
|
6864
|
+
rules: {
|
|
6865
|
+
engines: { pulumi: engine2, plunge: engine2 },
|
|
6866
|
+
product: { commands: [["up"], ["resume"], ["approve"], ["override"], ["cancel"], ["move"], ["rollback"], ["action"]], flagValues: [["--respond", "up"]] },
|
|
6867
|
+
wrapperExit: 1
|
|
7418
6868
|
}
|
|
7419
|
-
|
|
7420
|
-
}
|
|
7421
|
-
var GUARD_TEMPLATES = new URL("../templates/agent-guard/", import.meta.url);
|
|
7422
|
-
var HOOK_COMMAND = 'node "$CLAUDE_PROJECT_DIR/.claude/hooks/plywood-guard.mjs"';
|
|
7423
|
-
function withGuardHook(settings) {
|
|
7424
|
-
const hooks = typeof settings.hooks === "object" && settings.hooks !== null ? settings.hooks : {};
|
|
7425
|
-
const pre = Array.isArray(hooks.PreToolUse) ? [...hooks.PreToolUse] : [];
|
|
7426
|
-
const has = JSON.stringify(pre).includes("plywood-guard.mjs");
|
|
7427
|
-
if (!has) pre.push({ matcher: "Bash", hooks: [{ type: "command", command: HOOK_COMMAND }] });
|
|
7428
|
-
return { ...settings, hooks: { ...hooks, PreToolUse: pre } };
|
|
7429
|
-
}
|
|
6869
|
+
};
|
|
7430
6870
|
async function guard(args, json, table, io) {
|
|
7431
6871
|
const [sub, ...rest] = args;
|
|
7432
6872
|
if (sub === "check") {
|
|
7433
6873
|
const line = (rest[0] === "--" ? rest.slice(1) : rest).join(" ");
|
|
7434
6874
|
if (!line.trim()) return usage("guard check takes the command line to check", table, io);
|
|
7435
|
-
const v =
|
|
7436
|
-
|
|
6875
|
+
const v = agentGuard(GUARD, { repo: io.cwd }).checkCommand(line, io.cwd);
|
|
6876
|
+
const refused = v ? { engine: v.bin, subcommand: v.command, message: v.message } : null;
|
|
6877
|
+
if (json) io.out(JSON.stringify({ command: "guard check", ok: !v, exitCode: v ? table.denied : table.done, refused }));
|
|
7437
6878
|
else io.out(v ? v.message : `${PRODUCT} guard: '${line}' runs`);
|
|
7438
6879
|
return v ? table.denied : table.done;
|
|
7439
6880
|
}
|
|
@@ -7442,34 +6883,16 @@ async function guard(args, json, table, io) {
|
|
|
7442
6883
|
if (dirFlag.missing) return usage("--dir takes a directory", table, io);
|
|
7443
6884
|
if (dirFlag.rest.length) return usage(`guard install does not take ${dirFlag.rest.join(" ")}`, table, io);
|
|
7444
6885
|
const root = dirFlag.value ? join17(io.cwd, dirFlag.value) : io.cwd;
|
|
7445
|
-
|
|
7446
|
-
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
|
|
7450
|
-
const settingsFile = join17(root, ".claude/settings.json");
|
|
7451
|
-
let settings = {};
|
|
7452
|
-
if (existsSync14(settingsFile)) {
|
|
7453
|
-
try {
|
|
7454
|
-
settings = JSON.parse(readFileSync15(settingsFile, "utf8"));
|
|
7455
|
-
} catch (e) {
|
|
7456
|
-
return usage(`.claude/settings.json is not JSON (${e.message}); fix it, then run this again`, table, io);
|
|
7457
|
-
}
|
|
7458
|
-
}
|
|
7459
|
-
writeFileSync8(settingsFile, JSON.stringify(withGuardHook(settings), null, 2) + "\n");
|
|
7460
|
-
written.push(".claude/settings.json");
|
|
7461
|
-
const binDir = join17(root, ".plywood/agent-bin");
|
|
7462
|
-
mkdirSync7(binDir, { recursive: true });
|
|
7463
|
-
for (const name of Object.keys(GUARDED)) {
|
|
7464
|
-
const to = join17(binDir, name);
|
|
7465
|
-
copyFileSync(new URL(`bin/${name}`, GUARD_TEMPLATES), to);
|
|
7466
|
-
chmodSync(to, 493);
|
|
7467
|
-
written.push(`.plywood/agent-bin/${name}`);
|
|
6886
|
+
let written;
|
|
6887
|
+
try {
|
|
6888
|
+
written = installAgentGuard({ ...GUARD, repo: root, install: "project" }).written;
|
|
6889
|
+
} catch (e) {
|
|
6890
|
+
return usage(e.message, table, io);
|
|
7468
6891
|
}
|
|
7469
6892
|
if (json) io.out(JSON.stringify({ command: "guard install", ok: true, exitCode: table.done, written }));
|
|
7470
6893
|
else {
|
|
7471
6894
|
for (const f of written) io.err(`${PRODUCT} guard: wrote ${f}`);
|
|
7472
|
-
io.err(`${PRODUCT} guard: an agent session that runs outside Claude Code puts .plywood/agent-bin first on PATH
|
|
6895
|
+
io.err(`${PRODUCT} guard: an agent session that runs outside Claude Code puts .plywood/agent-bin first on PATH`);
|
|
7473
6896
|
}
|
|
7474
6897
|
return table.done;
|
|
7475
6898
|
}
|
|
@@ -7477,6 +6900,7 @@ async function guard(args, json, table, io) {
|
|
|
7477
6900
|
}
|
|
7478
6901
|
|
|
7479
6902
|
// src/identities.ts
|
|
6903
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
7480
6904
|
init_jobs2();
|
|
7481
6905
|
init_chant_config();
|
|
7482
6906
|
init_identity();
|
|
@@ -7484,7 +6908,7 @@ init_config();
|
|
|
7484
6908
|
init_stacks();
|
|
7485
6909
|
init_stages();
|
|
7486
6910
|
init_words();
|
|
7487
|
-
import { existsSync as
|
|
6911
|
+
import { existsSync as existsSync14, readFileSync as readFileSync15 } from "node:fs";
|
|
7488
6912
|
import { dirname as dirname13, join as join18 } from "node:path";
|
|
7489
6913
|
import { targetGlobMatch } from "@intentius/chant/ci-identity";
|
|
7490
6914
|
function identityRows(config2, env) {
|
|
@@ -7547,28 +6971,21 @@ function configIdentities(args, json, table, io) {
|
|
|
7547
6971
|
else for (const line of identityLines(config2, environment, rows)) io.out(line);
|
|
7548
6972
|
return table.done;
|
|
7549
6973
|
}
|
|
7550
|
-
var FORGE_CHECK_MESSAGE = `${PRODUCT} forge check holds the identities next to the forge's environments, protected branches and each cloud's trust policy. The read is chant's (@intentius/chant/forge-check, chant#4608), and the installed chant lacks it; it comes with the chant release after 0.124.54. Run '${PRODUCT} config identities' for what the jobs will hold.`;
|
|
7551
6974
|
var FORGES2 = ["github", "gitlab", "forgejo"];
|
|
7552
|
-
var FORGE_CHECK_FUNCTIONS = ["forgeCheck", "forgeCheckExit", "describeForgeCheck", "forgeCheckTarget", "pipelineContract"];
|
|
7553
6975
|
var forgeCheckModule;
|
|
7554
6976
|
function chantForgeCheck() {
|
|
7555
6977
|
forgeCheckModule ??= (async () => {
|
|
7556
|
-
|
|
7557
|
-
|
|
7558
|
-
|
|
7559
|
-
if (!FORGE_CHECK_FUNCTIONS.every((f) => typeof fc[f] === "function") || typeof id.resolveCloudIdentity !== "function") return void 0;
|
|
7560
|
-
return { ...fc, resolveCloudIdentity: id.resolveCloudIdentity };
|
|
7561
|
-
} catch {
|
|
7562
|
-
return void 0;
|
|
7563
|
-
}
|
|
6978
|
+
const fc = await import("@intentius/chant/forge-check");
|
|
6979
|
+
const { resolveCloudIdentity } = await import("@intentius/chant/ci-identity");
|
|
6980
|
+
return { ...fc, resolveCloudIdentity };
|
|
7564
6981
|
})();
|
|
7565
6982
|
return forgeCheckModule;
|
|
7566
6983
|
}
|
|
7567
6984
|
function workspaceMembers(root) {
|
|
7568
6985
|
const file = join18(root, "chant.workspace.json");
|
|
7569
|
-
if (!
|
|
6986
|
+
if (!existsSync14(file)) return [];
|
|
7570
6987
|
try {
|
|
7571
|
-
const doc = JSON.parse(
|
|
6988
|
+
const doc = JSON.parse(readFileSync15(file, "utf8"));
|
|
7572
6989
|
return (doc.members ?? []).map((m) => m.name).filter((n) => typeof n === "string");
|
|
7573
6990
|
} catch {
|
|
7574
6991
|
return [];
|
|
@@ -7597,11 +7014,6 @@ function contractPipelines(fc, cloud, env, members, runner, jobs2) {
|
|
|
7597
7014
|
async function forgeCheck(args, json, table, io) {
|
|
7598
7015
|
if (args[0] !== "check") return usage(`unknown forge command '${args[0] ?? ""}'. Run '${PRODUCT} forge check'.`, table, io);
|
|
7599
7016
|
const fc = await chantForgeCheck();
|
|
7600
|
-
if (!fc) {
|
|
7601
|
-
if (json) io.out(JSON.stringify({ command: "forge check", ok: false, exitCode: table.failed, error: FORGE_CHECK_MESSAGE }));
|
|
7602
|
-
else io.err(`${PRODUCT}: ${FORGE_CHECK_MESSAGE}`);
|
|
7603
|
-
return table.failed;
|
|
7604
|
-
}
|
|
7605
7017
|
const requireClouds = args.includes("--require-clouds");
|
|
7606
7018
|
let rest = args.slice(1).filter((a) => a !== "--require-clouds");
|
|
7607
7019
|
const flags = {};
|
|
@@ -7671,6 +7083,7 @@ async function forgeCheck(args, json, table, io) {
|
|
|
7671
7083
|
init_control2();
|
|
7672
7084
|
|
|
7673
7085
|
// src/telemetry.ts
|
|
7086
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
7674
7087
|
init_chant_product();
|
|
7675
7088
|
init_observe();
|
|
7676
7089
|
init_config();
|
|
@@ -7699,8 +7112,7 @@ async function applyTelemetry(cwd, env = process.env) {
|
|
|
7699
7112
|
return {};
|
|
7700
7113
|
}
|
|
7701
7114
|
Object.assign(env, added);
|
|
7702
|
-
|
|
7703
|
-
if (chant && typeof chant.configureOtlp === "function") chant.configureOtlp({ prefix: PRODUCT, env });
|
|
7115
|
+
(await chantProduct()).configureOtlp({ prefix: PRODUCT, env });
|
|
7704
7116
|
return added;
|
|
7705
7117
|
}
|
|
7706
7118
|
|
|
@@ -7708,16 +7120,16 @@ async function applyTelemetry(cwd, env = process.env) {
|
|
|
7708
7120
|
init_init();
|
|
7709
7121
|
init_stages();
|
|
7710
7122
|
init_tips();
|
|
7711
|
-
init_images();
|
|
7712
7123
|
init_up();
|
|
7713
7124
|
|
|
7714
7125
|
// src/upgrade.ts
|
|
7126
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
7715
7127
|
init_config();
|
|
7716
7128
|
init_images();
|
|
7717
7129
|
init_stages();
|
|
7718
7130
|
init_words();
|
|
7719
7131
|
import { execFile } from "node:child_process";
|
|
7720
|
-
import { existsSync as
|
|
7132
|
+
import { existsSync as existsSync15, readFileSync as readFileSync16, writeFileSync as writeFileSync8 } from "node:fs";
|
|
7721
7133
|
import { dirname as dirname14, join as join19, relative as relative2, resolve as resolve3 } from "node:path";
|
|
7722
7134
|
var PLYWOOD_PACKAGE = "@intentius/plywood";
|
|
7723
7135
|
var CHANT_PACKAGE = "@intentius/chant";
|
|
@@ -7750,9 +7162,9 @@ function findPlywoodPackage(dir) {
|
|
|
7750
7162
|
let cur = resolve3(dir);
|
|
7751
7163
|
for (let depth = 0; depth < 32; depth++) {
|
|
7752
7164
|
const file = join19(cur, "package.json");
|
|
7753
|
-
if (
|
|
7165
|
+
if (existsSync15(file)) {
|
|
7754
7166
|
try {
|
|
7755
|
-
const pkg = JSON.parse(
|
|
7167
|
+
const pkg = JSON.parse(readFileSync16(file, "utf8"));
|
|
7756
7168
|
if (DEP_FIELDS.some((f) => isObject2(pkg[f]) && typeof pkg[f][PLYWOOD_PACKAGE] === "string")) return file;
|
|
7757
7169
|
} catch {
|
|
7758
7170
|
}
|
|
@@ -7850,10 +7262,10 @@ async function upgrade(args, json, table, io) {
|
|
|
7850
7262
|
const release2 = await viewRelease(exec, pkgDir, io.env, opts.version);
|
|
7851
7263
|
if ("error" in release2) return fail(release2.error);
|
|
7852
7264
|
const chant = opts.chant ?? release2.chant;
|
|
7853
|
-
const pkgText =
|
|
7265
|
+
const pkgText = readFileSync16(pkgFile, "utf8");
|
|
7854
7266
|
const pkgMove = movePackagePins(pkgText, rel(pkgFile), release2.version, chant);
|
|
7855
7267
|
const declFile = join19(configDir, "chant.workspace.json");
|
|
7856
|
-
const declText =
|
|
7268
|
+
const declText = existsSync15(declFile) ? readFileSync16(declFile, "utf8") : void 0;
|
|
7857
7269
|
let declMove;
|
|
7858
7270
|
if (declText !== void 0) {
|
|
7859
7271
|
try {
|
|
@@ -7863,7 +7275,7 @@ async function upgrade(args, json, table, io) {
|
|
|
7863
7275
|
}
|
|
7864
7276
|
}
|
|
7865
7277
|
const notes = [...pkgMove.left];
|
|
7866
|
-
if (
|
|
7278
|
+
if (existsSync15(join19(configDir, "chant.workspace.jsonc"))) notes.push(`chant.workspace.jsonc is yours: move its @intentius/chant* pins to ${chant} by hand`);
|
|
7867
7279
|
if (configFile) {
|
|
7868
7280
|
try {
|
|
7869
7281
|
const note = imageNote(loadConfig(configFile).doc, release2.version);
|
|
@@ -7892,8 +7304,8 @@ async function upgrade(args, json, table, io) {
|
|
|
7892
7304
|
if (json) io.out(JSON.stringify({ ...envelope, ok: true, exitCode: table.done, steps }));
|
|
7893
7305
|
return table.done;
|
|
7894
7306
|
}
|
|
7895
|
-
if (pkgMove.text !== pkgText)
|
|
7896
|
-
if (declMove && declText !== void 0 && declMove.text !== declText)
|
|
7307
|
+
if (pkgMove.text !== pkgText) writeFileSync8(pkgFile, pkgMove.text);
|
|
7308
|
+
if (declMove && declText !== void 0 && declMove.text !== declText) writeFileSync8(declFile, declMove.text);
|
|
7897
7309
|
const files = [...new Set(moves.map((m) => m.file))];
|
|
7898
7310
|
if (!opts.install) {
|
|
7899
7311
|
say(` left to run: ${steps.install.command} (in ${steps.install.cwd}), then ${steps.ci.command} (in ${steps.ci.cwd})`);
|
|
@@ -7919,7 +7331,7 @@ ${installed2.stderr.trim()}`);
|
|
|
7919
7331
|
for (const n of notes) say(` note: ${n}`);
|
|
7920
7332
|
const ok = ci2 === void 0 || ci2.code === 0;
|
|
7921
7333
|
const lockFile = join19(pkgDir, "package-lock.json");
|
|
7922
|
-
const changed = [...files, ...
|
|
7334
|
+
const changed = [...files, ...existsSync15(lockFile) ? [rel(lockFile)] : []];
|
|
7923
7335
|
if (ok) say(` Open one pull request with ${changed.join(", ")} and the files ${PRODUCT} ci wrote.`);
|
|
7924
7336
|
else say(` ${PRODUCT} ci failed (exit ${ci2?.code}); the pins are moved and installed, so fix plywood.yml and run '${steps.ci.command}'`);
|
|
7925
7337
|
const code = ok ? table.done : ci2?.code ?? table.failed;
|
|
@@ -7928,7 +7340,7 @@ ${installed2.stderr.trim()}`);
|
|
|
7928
7340
|
}
|
|
7929
7341
|
function installedVersion(dir) {
|
|
7930
7342
|
try {
|
|
7931
|
-
const pkg = JSON.parse(
|
|
7343
|
+
const pkg = JSON.parse(readFileSync16(join19(dir, "node_modules", PLYWOOD_PACKAGE, "package.json"), "utf8"));
|
|
7932
7344
|
return typeof pkg.version === "string" ? pkg.version : void 0;
|
|
7933
7345
|
} catch {
|
|
7934
7346
|
return void 0;
|
|
@@ -7951,7 +7363,7 @@ var execChild = (bin, args, opts) => new Promise((done) => {
|
|
|
7951
7363
|
|
|
7952
7364
|
// src/cli.ts
|
|
7953
7365
|
init_words();
|
|
7954
|
-
var VERSION3 = true ? "0.3.
|
|
7366
|
+
var VERSION3 = true ? "0.3.5" : "0.0.0-dev";
|
|
7955
7367
|
var stdio = {
|
|
7956
7368
|
out: (s) => process.stdout.write(s + "\n"),
|
|
7957
7369
|
err: (s) => process.stderr.write(s + "\n")
|
|
@@ -8042,7 +7454,7 @@ function helpText() {
|
|
|
8042
7454
|
].join("\n");
|
|
8043
7455
|
}
|
|
8044
7456
|
async function main(argv, io = stdio) {
|
|
8045
|
-
const
|
|
7457
|
+
const table = EXIT;
|
|
8046
7458
|
const json = argv.includes("--json");
|
|
8047
7459
|
const args = argv.filter((a) => a !== "--json");
|
|
8048
7460
|
const verb = args[0];
|
|
@@ -8055,11 +7467,9 @@ async function main(argv, io = stdio) {
|
|
|
8055
7467
|
return table.done;
|
|
8056
7468
|
}
|
|
8057
7469
|
if (verb === "--version" || verb === "-v") {
|
|
8058
|
-
emit({ command: "version", ok: true, exitCode: table.done, version: VERSION3, exitCodes:
|
|
7470
|
+
emit({ command: "version", ok: true, exitCode: table.done, version: VERSION3, exitCodes: "chant" }, VERSION3);
|
|
8059
7471
|
return table.done;
|
|
8060
7472
|
}
|
|
8061
|
-
await useChantInstallLines();
|
|
8062
|
-
await useChantPolicySource();
|
|
8063
7473
|
const stageIo = { out: io.out, err: io.err, cwd: process.cwd(), env: process.env };
|
|
8064
7474
|
if (verb !== "config" && verb !== "init" && verb !== "tips" && verb !== "upgrade") await applyTelemetry(stageIo.cwd, stageIo.env);
|
|
8065
7475
|
if (verb === "audit") return audit2(args.slice(1), json, table, stageIo);
|