@intentius/plywood 0.3.1 → 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 +471 -1038
- package/dist/plywood.mjs.map +4 -4
- package/package.json +7 -7
- 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,8 +444,9 @@ 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
|
-
TAGLINE = "
|
|
449
|
+
TAGLINE = "Pulumi ops and governance.";
|
|
436
450
|
OP_NAME = "pulumi-up";
|
|
437
451
|
PR_GATE = "up";
|
|
438
452
|
VERBS = [
|
|
@@ -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.1-pulumi3.272.0": "sha256:acdadb5000ade3afea20ab2f555946312281066e1df288b1007bff096514ebfd",
|
|
617
|
-
"ghcr.io/intentius/plywood-plunge:0.3.1-plunge0.2.0": "sha256:adc42bc97564ecb2074b3ee4d97f84ddb986e0f30f6a26f35b8721106c177456"
|
|
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.59" : "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
|
|
|
@@ -3503,6 +3147,7 @@ function wavesSpec(input) {
|
|
|
3503
3147
|
step: { kind: "pulumi-up" },
|
|
3504
3148
|
...input.quorum && input.quorum.length > 0 ? { approvals: { quorum: input.quorum } } : {},
|
|
3505
3149
|
...input.notify ? { notify: input.notify } : {},
|
|
3150
|
+
...input.policy ? { holds: { policy: true } } : {},
|
|
3506
3151
|
waves: waves.map((w) => {
|
|
3507
3152
|
const gate = w.stacks.map((s) => s.gate ?? input.gate).reduce((a, b) => STRICTNESS[b] > STRICTNESS[a] ? b : a);
|
|
3508
3153
|
return { name: w.name, gate, runs: w.stacks.map((s) => ({ target: s.key, input: stepInput(s) })) };
|
|
@@ -3546,6 +3191,7 @@ var WAVE_NAME, STRICTNESS, MEMBER_NAME, PULUMI_LEXICON;
|
|
|
3546
3191
|
var init_render = __esm({
|
|
3547
3192
|
"src/init/render.ts"() {
|
|
3548
3193
|
"use strict";
|
|
3194
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
3549
3195
|
init_templates();
|
|
3550
3196
|
init_words();
|
|
3551
3197
|
init_detect();
|
|
@@ -3599,11 +3245,28 @@ var REFUSALS;
|
|
|
3599
3245
|
var init_refusals = __esm({
|
|
3600
3246
|
"src/refusals.ts"() {
|
|
3601
3247
|
"use strict";
|
|
3248
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
3602
3249
|
init_words();
|
|
3603
3250
|
REFUSALS = ["waiting", "moved", "held", "superseded", "denied", "declined", "misplaced"];
|
|
3604
3251
|
}
|
|
3605
3252
|
});
|
|
3606
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
|
+
|
|
3607
3270
|
// src/up.ts
|
|
3608
3271
|
import { existsSync as existsSync6, readFileSync as readFileSync7 } from "node:fs";
|
|
3609
3272
|
import { join as join6 } from "node:path";
|
|
@@ -3713,8 +3376,7 @@ function approvalStatusTarget(opts, doc, env) {
|
|
|
3713
3376
|
return { ...forge ? { forge } : {}, ...branch ? { branch } : {} };
|
|
3714
3377
|
}
|
|
3715
3378
|
async function refreshApprovalStatus(chant, spec, opts, io) {
|
|
3716
|
-
|
|
3717
|
-
const posts = chant.opWavesPostsApprovalStatus ? chant.opWavesPostsApprovalStatus(spec) : wavesTakePullRequestApproval(spec);
|
|
3379
|
+
const posts = chant.opWavesPostsApprovalStatus(spec);
|
|
3718
3380
|
if (!posts) return;
|
|
3719
3381
|
const path = findConfig(io.cwd);
|
|
3720
3382
|
const doc = path ? loadConfig(path).doc : void 0;
|
|
@@ -3730,7 +3392,6 @@ async function refreshApprovalStatus(chant, spec, opts, io) {
|
|
|
3730
3392
|
}
|
|
3731
3393
|
async function upTargetByPlan(chant, digest, opts = {}) {
|
|
3732
3394
|
const needsPr = `the ${PR_GATE} gate is a pull request's: name it with --pr <n> (or PR_NUMBER), or approve pr-<n>`;
|
|
3733
|
-
if (!hasChant4607(chant)) return { error: `${needsPr}. The installed chant cannot find a pull request from a plan digest (chant#4607)` };
|
|
3734
3395
|
let gates;
|
|
3735
3396
|
try {
|
|
3736
3397
|
gates = await chant.pendingGatesByPlan(digest, { pullRequestsOnly: true, ...opts.cwd ? { cwd: opts.cwd } : {} });
|
|
@@ -3860,8 +3521,6 @@ async function upBeforeMerge(number, args, json, table, io) {
|
|
|
3860
3521
|
const forge = forgeFlag.value ?? configured ?? "github";
|
|
3861
3522
|
if (!FORGES.includes(forge)) return usage(`--forge ${forge} is not one of ${FORGES.join(", ")}`, table, io);
|
|
3862
3523
|
return withChant(`/${PRODUCT} up`, table, io, async (chant) => {
|
|
3863
|
-
const note = requiresNotHonoured(chant, config2.doc);
|
|
3864
|
-
if (note) io.err(`${PRODUCT}: ${note}`);
|
|
3865
3524
|
const argv = [
|
|
3866
3525
|
"components",
|
|
3867
3526
|
"pr-apply",
|
|
@@ -3911,12 +3570,11 @@ var OVERRIDE_OP, WAVES_SPEC_FILE, UP_STEP_KIND, PR_OP, APPROVE_PASS, APPROVE_SWI
|
|
|
3911
3570
|
var init_up = __esm({
|
|
3912
3571
|
"src/up.ts"() {
|
|
3913
3572
|
"use strict";
|
|
3573
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
3914
3574
|
init_apply();
|
|
3915
|
-
init_chant_next();
|
|
3916
3575
|
init_config();
|
|
3917
3576
|
init_routing();
|
|
3918
3577
|
init_refusals();
|
|
3919
|
-
init_chant_product();
|
|
3920
3578
|
init_review_stacks2();
|
|
3921
3579
|
init_stages();
|
|
3922
3580
|
init_words();
|
|
@@ -3999,7 +3657,10 @@ async function drift2(args, json, table, io) {
|
|
|
3999
3657
|
try {
|
|
4000
3658
|
const attribute = await attributionFor(doc, io);
|
|
4001
3659
|
const root = dirname6(path);
|
|
4002
|
-
for (const stack of stacks2)
|
|
3660
|
+
for (const stack of stacks2) {
|
|
3661
|
+
const backend = stacksOf(doc)[stack]?.backend ?? (typeof doc.backend === "string" ? doc.backend : void 0);
|
|
3662
|
+
results.push(await withBackend(backend, () => readStack(chant, stack, driftMember(root, doc, stack), dir, { resource: resource.value, env: envFlag.value, ...attribute ? { attribute } : {} })));
|
|
3663
|
+
}
|
|
4003
3664
|
} finally {
|
|
4004
3665
|
rmSync(dir, { recursive: true, force: true });
|
|
4005
3666
|
}
|
|
@@ -4022,9 +3683,7 @@ async function drift2(args, json, table, io) {
|
|
|
4022
3683
|
}
|
|
4023
3684
|
}
|
|
4024
3685
|
if (respond === "pull-request" && results.some((r) => r.drifted > 0)) {
|
|
4025
|
-
io.err(
|
|
4026
|
-
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`
|
|
4027
|
-
);
|
|
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)`);
|
|
4028
3687
|
}
|
|
4029
3688
|
const notice = await driftNotice(chant, doc, results, envFlag.value, io);
|
|
4030
3689
|
const drifted = results.filter((r) => r.drifted > 0).map((r) => r.stack);
|
|
@@ -4062,12 +3721,12 @@ async function drift2(args, json, table, io) {
|
|
|
4062
3721
|
}
|
|
4063
3722
|
async function driftNotice(chant, doc, results, environment, io) {
|
|
4064
3723
|
const notify2 = chantNotifyOf(doc);
|
|
4065
|
-
|
|
3724
|
+
if (!notifiesDrift(notify2)) return void 0;
|
|
4066
3725
|
return sendDriftNotice({
|
|
4067
3726
|
notify: notify2,
|
|
4068
3727
|
results,
|
|
4069
3728
|
stackFingerprint: driftFingerprint,
|
|
4070
|
-
chant:
|
|
3729
|
+
chant: { ...await chantDriftNotify(), words: chant.assertProductWords(PRODUCT_WORDS) },
|
|
4071
3730
|
env: io.env,
|
|
4072
3731
|
...environment ? { environment } : {},
|
|
4073
3732
|
log: io.err,
|
|
@@ -4106,9 +3765,26 @@ async function readStack(chant, stack, member, dir, opts) {
|
|
|
4106
3765
|
return { stack, ok: false, drifted: 0, rows: [], error: doc?.error ? `${doc.error.code}: ${doc.error.message}` : `chant workspace drift exited ${code}` };
|
|
4107
3766
|
}
|
|
4108
3767
|
const rows = driftRows(doc);
|
|
3768
|
+
const unread = (doc.records ?? []).filter((r) => r.status === "not-observed");
|
|
3769
|
+
if (rows.length === 0 && unread.length > 0) {
|
|
3770
|
+
const first = unread[0];
|
|
3771
|
+
const why = first.unobserved?.detail ?? first.unobserved?.reason ?? "no reason given";
|
|
3772
|
+
return { stack, ok: false, drifted: 0, rows: [], error: `${unread.length} resource${unread.length === 1 ? "" : "s"} could not be read, so the stack's drift is unknown: ${first.address}: ${why}` };
|
|
3773
|
+
}
|
|
4109
3774
|
const attribution = rows.length && opts.attribute ? await attributeStack(opts.attribute, stack, doc) : void 0;
|
|
4110
3775
|
return { stack, ok: true, drifted: rows.length, rows, observedAt: observedAt(doc), ...attribution ? { attribution } : {} };
|
|
4111
3776
|
}
|
|
3777
|
+
async function withBackend(backend, read) {
|
|
3778
|
+
if (backend === void 0) return read();
|
|
3779
|
+
const before = process.env.PULUMI_BACKEND_URL;
|
|
3780
|
+
process.env.PULUMI_BACKEND_URL = backend;
|
|
3781
|
+
try {
|
|
3782
|
+
return await read();
|
|
3783
|
+
} finally {
|
|
3784
|
+
if (before === void 0) delete process.env.PULUMI_BACKEND_URL;
|
|
3785
|
+
else process.env.PULUMI_BACKEND_URL = before;
|
|
3786
|
+
}
|
|
3787
|
+
}
|
|
4112
3788
|
async function attributionFor(doc, io) {
|
|
4113
3789
|
const logs = driftSettingsOf(doc).auditLogs;
|
|
4114
3790
|
if (!logs) return void 0;
|
|
@@ -4165,20 +3841,19 @@ function driftOpFile(doc, stacks2, schedule) {
|
|
|
4165
3841
|
}
|
|
4166
3842
|
function driftOpSpec(doc, forge, stacks2, schedule, chant, opts = {}) {
|
|
4167
3843
|
const respond = driftResponseOf(doc);
|
|
4168
|
-
const
|
|
4169
|
-
const findingMode = respond === "none" ? "report" : respond === "pull-request" && !agentJob ? forge === "gitlab" ? "merge-request" : "pull-request" : "issue";
|
|
3844
|
+
const findingMode = respond === "none" ? "report" : "issue";
|
|
4170
3845
|
const pipeline = toChantSettings({ doc }).pipeline;
|
|
4171
|
-
const
|
|
3846
|
+
const engine3 = chantPipelineOptions({ ...pipeline.image ? { image: pipeline.image } : {}, ...pipeline.engineSetup ? { engineSetup: pipeline.engineSetup } : {} });
|
|
4172
3847
|
return {
|
|
4173
|
-
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") : {} })),
|
|
4174
3849
|
options: {
|
|
4175
3850
|
runCommand: [...BIN.split(/\s+/), "drift", "--op", "{name}", "--forge", forge],
|
|
4176
|
-
beforeScript: [SNAPSHOTS_FETCH, ...
|
|
4177
|
-
...
|
|
3851
|
+
beforeScript: [SNAPSHOTS_FETCH, ...engine3.beforeScript ?? []],
|
|
3852
|
+
...engine3.image ? { image: engine3.image } : {}
|
|
4178
3853
|
}
|
|
4179
3854
|
};
|
|
4180
3855
|
}
|
|
4181
|
-
function driftCloudFields(doc, forge, stack, respond, env
|
|
3856
|
+
function driftCloudFields(doc, forge, stack, respond, env) {
|
|
4182
3857
|
const settings = toChantSettings({ doc });
|
|
4183
3858
|
const decl = settings.ci.identity?.cloud;
|
|
4184
3859
|
const ids = decl ? resolveTargetCloudIdentity(decl, env, stack, stack) : void 0;
|
|
@@ -4187,7 +3862,7 @@ function driftCloudFields(doc, forge, stack, respond, env, opts = {}) {
|
|
|
4187
3862
|
const waves = wavesHolding(doc, [stack]);
|
|
4188
3863
|
const command = waves === "all" ? `${BIN} up` : waves.map((k) => `${BIN} up --wave ${k}`).join(" && ");
|
|
4189
3864
|
const fields = driftJobFields(forge, settings.pipeline.jobs, stack);
|
|
4190
|
-
const permissions =
|
|
3865
|
+
const permissions = forge !== "gitlab" ? { permissions: { contents: "write" } } : {};
|
|
4191
3866
|
out.update = { command, ...ids?.apply ? { cloud: ids.apply } : {}, ...fields, ...permissions };
|
|
4192
3867
|
return out;
|
|
4193
3868
|
}
|
|
@@ -4221,7 +3896,7 @@ async function writeDriftJobs(chant, forge, doc, words, table, io, env = "produc
|
|
|
4221
3896
|
return table.done;
|
|
4222
3897
|
}
|
|
4223
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`);
|
|
4224
|
-
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}`);
|
|
4225
3900
|
const outDir = OP_OUTPUT_DIR2[forge];
|
|
4226
3901
|
const code = await chant.runChantInProcess(["run", "--generate", forge, "--spec", DRIFT_SPEC_FILE, "--output", outDir], words);
|
|
4227
3902
|
if (code !== table.done) return code;
|
|
@@ -4235,6 +3910,7 @@ var DRIFT_OP_FILE, DRIFT_SPEC_FILE, SNAPSHOTS_REF, SNAPSHOTS_FETCH, DRIFT_STATUS
|
|
|
4235
3910
|
var init_drift2 = __esm({
|
|
4236
3911
|
"src/drift.ts"() {
|
|
4237
3912
|
"use strict";
|
|
3913
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
4238
3914
|
init_chant_next();
|
|
4239
3915
|
init_images2();
|
|
4240
3916
|
init_chant_config();
|
|
@@ -4272,6 +3948,7 @@ var TIPS;
|
|
|
4272
3948
|
var init_tips = __esm({
|
|
4273
3949
|
"src/tips.ts"() {
|
|
4274
3950
|
"use strict";
|
|
3951
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
4275
3952
|
TIPS = [
|
|
4276
3953
|
{
|
|
4277
3954
|
id: "rename-without-aliases",
|
|
@@ -4388,9 +4065,6 @@ async function mcp(args, _json, table, io) {
|
|
|
4388
4065
|
if (report.missing) return usage("--report-url takes a URL", table, io);
|
|
4389
4066
|
if (report.rest.length) return usage(`mcp does not take ${report.rest.join(" ")}`, table, io);
|
|
4390
4067
|
return withChant(`${PRODUCT} mcp`, table, io, (chant) => {
|
|
4391
|
-
if (typeof chant.PRODUCT_MCP_HOOK_FLOOR !== "string") {
|
|
4392
|
-
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`);
|
|
4393
|
-
}
|
|
4394
4068
|
const argv = ["serve", "mcp", "--read-only", ...propose ? ["--propose"] : []];
|
|
4395
4069
|
return chant.runChantInProcess(argv, PRODUCT_WORDS, { mcp: plywoodMcpHook(report.value, propose) });
|
|
4396
4070
|
});
|
|
@@ -4426,7 +4100,7 @@ function reviewOpFile(doc) {
|
|
|
4426
4100
|
function reviewOpSpec(doc) {
|
|
4427
4101
|
const settings = toChantSettings({ doc });
|
|
4428
4102
|
const p = settings.pipeline;
|
|
4429
|
-
const
|
|
4103
|
+
const engine3 = chantPipelineOptions({ ...p.image ? { image: p.image } : {}, ...p.engineSetup ? { engineSetup: p.engineSetup } : {} });
|
|
4430
4104
|
return {
|
|
4431
4105
|
ops: [
|
|
4432
4106
|
{
|
|
@@ -4438,8 +4112,8 @@ function reviewOpSpec(doc) {
|
|
|
4438
4112
|
}
|
|
4439
4113
|
],
|
|
4440
4114
|
options: {
|
|
4441
|
-
...
|
|
4442
|
-
...
|
|
4115
|
+
...engine3.beforeScript ? { beforeScript: engine3.beforeScript } : {},
|
|
4116
|
+
...engine3.image ? { image: engine3.image } : {}
|
|
4443
4117
|
}
|
|
4444
4118
|
};
|
|
4445
4119
|
}
|
|
@@ -4469,6 +4143,7 @@ var PERSON_VERBS, READ_INSTRUCTIONS, REVIEW_OP, REVIEW_OP_FILE, REVIEW_SPEC_FILE
|
|
|
4469
4143
|
var init_agents2 = __esm({
|
|
4470
4144
|
"src/agents.ts"() {
|
|
4471
4145
|
"use strict";
|
|
4146
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
4472
4147
|
init_chant_config();
|
|
4473
4148
|
init_config_check();
|
|
4474
4149
|
init_agents();
|
|
@@ -4505,6 +4180,7 @@ var PROVIDERS_PACK_DIR, PROVIDERS_PACK_CONFIG, PROVIDERS_REGO_FILE, PROVIDERS_PO
|
|
|
4505
4180
|
var init_policy_presets = __esm({
|
|
4506
4181
|
"src/policy-presets.ts"() {
|
|
4507
4182
|
"use strict";
|
|
4183
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
4508
4184
|
PROVIDERS_PACK_DIR = "policy/plywood-providers";
|
|
4509
4185
|
PROVIDERS_PACK_CONFIG = "policy/plywood-providers.json";
|
|
4510
4186
|
PROVIDERS_REGO_FILE = "plywood_providers.rego";
|
|
@@ -4586,19 +4262,9 @@ function bucketTarget(url, env) {
|
|
|
4586
4262
|
}
|
|
4587
4263
|
return void 0;
|
|
4588
4264
|
}
|
|
4589
|
-
function needsBucketCheckMessage(builtChant) {
|
|
4590
|
-
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.`;
|
|
4591
|
-
}
|
|
4592
4265
|
function chantBucketCheck() {
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
const m = await import("@intentius/chant/bucket-check");
|
|
4596
|
-
return typeof m.checkBucket === "function" ? m : void 0;
|
|
4597
|
-
} catch {
|
|
4598
|
-
return void 0;
|
|
4599
|
-
}
|
|
4600
|
-
})();
|
|
4601
|
-
return cached4;
|
|
4266
|
+
cached2 ??= import("@intentius/chant/bucket-check").then((m) => m);
|
|
4267
|
+
return cached2;
|
|
4602
4268
|
}
|
|
4603
4269
|
function bucketLines(report) {
|
|
4604
4270
|
const where = `${report.store === "azblob" ? "container" : "bucket"} ${report.bucket}`;
|
|
@@ -4611,47 +4277,38 @@ function bucketLines(report) {
|
|
|
4611
4277
|
}
|
|
4612
4278
|
return out;
|
|
4613
4279
|
}
|
|
4614
|
-
var
|
|
4280
|
+
var cached2;
|
|
4615
4281
|
var init_backend = __esm({
|
|
4616
4282
|
"src/init/backend.ts"() {
|
|
4617
4283
|
"use strict";
|
|
4284
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
4618
4285
|
}
|
|
4619
4286
|
});
|
|
4620
4287
|
|
|
4621
4288
|
// src/init/adopt.ts
|
|
4622
4289
|
function chantAdopt() {
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
return view;
|
|
4641
|
-
} catch (err) {
|
|
4642
|
-
const f = failure ? failure(err) : { message: err.message };
|
|
4643
|
-
const view = parse(f.stdout);
|
|
4644
|
-
if (view) return view;
|
|
4645
|
-
if (f.missingBinary) throw new Error("the plunge binary was not found on PATH");
|
|
4646
|
-
throw new Error((f.stderr?.trim() || f.message).split("\n").slice(-3).join("\n"));
|
|
4647
|
-
}
|
|
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"));
|
|
4648
4307
|
}
|
|
4649
|
-
}
|
|
4650
|
-
}
|
|
4651
|
-
return void 0;
|
|
4652
|
-
}
|
|
4308
|
+
}
|
|
4309
|
+
};
|
|
4653
4310
|
})();
|
|
4654
|
-
return
|
|
4311
|
+
return cached3;
|
|
4655
4312
|
}
|
|
4656
4313
|
function adoptCommand(s, backend) {
|
|
4657
4314
|
const cd = s.dir === "." ? "" : `cd ${s.dir} && `;
|
|
@@ -4661,10 +4318,6 @@ async function adoptPreviews(stacks2, chant, root) {
|
|
|
4661
4318
|
const out = [];
|
|
4662
4319
|
for (const s of stacks2) {
|
|
4663
4320
|
const command = adoptCommand(s, s.url);
|
|
4664
|
-
if (!chant) {
|
|
4665
|
-
out.push({ key: s.key, backend: s.url, command, error: "not run" });
|
|
4666
|
-
continue;
|
|
4667
|
-
}
|
|
4668
4321
|
try {
|
|
4669
4322
|
const view = await chant.preview({ dir: s.dir === "." ? root : `${root}/${s.dir}`, stack: s.stack, backend: s.url });
|
|
4670
4323
|
out.push({ key: s.key, backend: s.url, command, view });
|
|
@@ -4679,9 +4332,8 @@ function fates(view) {
|
|
|
4679
4332
|
for (const r of view.resources) n[r.fate] = (n[r.fate] ?? 0) + 1;
|
|
4680
4333
|
return Object.entries(n).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([f, c]) => `${f} ${c}`).join(", ");
|
|
4681
4334
|
}
|
|
4682
|
-
function adoptLines(previews
|
|
4335
|
+
function adoptLines(previews) {
|
|
4683
4336
|
if (previews.length === 0) return [];
|
|
4684
|
-
if (!chant) return [NEEDS_ADOPT_MESSAGE, ...previews.map((p) => ` ${p.key}: ${p.command}`)];
|
|
4685
4337
|
const out = ["Adopt previews (plunge stack adopt --json, which writes nothing):"];
|
|
4686
4338
|
for (const p of previews) {
|
|
4687
4339
|
const v = p.view;
|
|
@@ -4747,14 +4399,14 @@ function adoptTodo(stacks2) {
|
|
|
4747
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`
|
|
4748
4400
|
];
|
|
4749
4401
|
}
|
|
4750
|
-
var ADOPT_OP, ADOPT_OP_FILE,
|
|
4402
|
+
var ADOPT_OP, ADOPT_OP_FILE, cached3;
|
|
4751
4403
|
var init_adopt = __esm({
|
|
4752
4404
|
"src/init/adopt.ts"() {
|
|
4753
4405
|
"use strict";
|
|
4406
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
4754
4407
|
init_detect();
|
|
4755
4408
|
ADOPT_OP = "plywood-adopt";
|
|
4756
4409
|
ADOPT_OP_FILE = "plywood.adopt.op.ts";
|
|
4757
|
-
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:";
|
|
4758
4410
|
}
|
|
4759
4411
|
});
|
|
4760
4412
|
|
|
@@ -4770,9 +4422,9 @@ function plungeUrlOf(url) {
|
|
|
4770
4422
|
const scheme = url.split("://")[0];
|
|
4771
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}` };
|
|
4772
4424
|
}
|
|
4773
|
-
function plungeStacks(
|
|
4774
|
-
return
|
|
4775
|
-
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 };
|
|
4776
4428
|
const b = backendOf(project, {}, env);
|
|
4777
4429
|
const to = plungeUrlOf(b.url);
|
|
4778
4430
|
return {
|
|
@@ -4817,6 +4469,7 @@ var INIT_ENGINES, MOVE_A_STACK_GUIDE, PLUNGE_FORMS;
|
|
|
4817
4469
|
var init_engine2 = __esm({
|
|
4818
4470
|
"src/init/engine.ts"() {
|
|
4819
4471
|
"use strict";
|
|
4472
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
4820
4473
|
init_engine();
|
|
4821
4474
|
init_backend();
|
|
4822
4475
|
INIT_ENGINES = ["pulumi", "plunge"];
|
|
@@ -4840,6 +4493,7 @@ var PRESET_NAMES, DEFAULT_PRESET, PRESETS;
|
|
|
4840
4493
|
var init_presets = __esm({
|
|
4841
4494
|
"src/init/presets.ts"() {
|
|
4842
4495
|
"use strict";
|
|
4496
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
4843
4497
|
PRESET_NAMES = ["merge", "before-merge", "approve-all"];
|
|
4844
4498
|
DEFAULT_PRESET = "merge";
|
|
4845
4499
|
PRESETS = [
|
|
@@ -4865,26 +4519,17 @@ var init_presets = __esm({
|
|
|
4865
4519
|
// src/stack-order.ts
|
|
4866
4520
|
import { join as join9 } from "node:path";
|
|
4867
4521
|
function chantReferenceReader() {
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
return {
|
|
4878
|
-
pulumiStackReferences: lexicon.pulumiStackReferences,
|
|
4879
|
-
stackReferenceOwner: lexicon.stackReferenceOwner,
|
|
4880
|
-
...typeof run.memberExportOf === "function" ? { memberExportOf: run.memberExportOf } : {},
|
|
4881
|
-
...typeof lexicon.readMemberExport === "function" ? { readMemberExport: lexicon.readMemberExport } : {}
|
|
4882
|
-
};
|
|
4883
|
-
} catch {
|
|
4884
|
-
return void 0;
|
|
4885
|
-
}
|
|
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
|
+
};
|
|
4886
4531
|
})();
|
|
4887
|
-
return
|
|
4532
|
+
return cached4;
|
|
4888
4533
|
}
|
|
4889
4534
|
async function exportOf(reader, root, s) {
|
|
4890
4535
|
const dir = join9(root, s.dir);
|
|
@@ -4908,7 +4553,6 @@ async function stackOrder(root, stacks2, after = {}) {
|
|
|
4908
4553
|
for (const upstream of ups) if (keys.has(reader2) && keys.has(upstream)) add({ reader: reader2, upstream, because: "waves.after" });
|
|
4909
4554
|
}
|
|
4910
4555
|
const reader = await chantReferenceReader();
|
|
4911
|
-
if (!reader) return { edges, outside, reads: false };
|
|
4912
4556
|
const owners = stacks2.map((s) => ({ component: s.key, project: s.project, stack: s.stack }));
|
|
4913
4557
|
for (const s of stacks2) {
|
|
4914
4558
|
const exported = await exportOf(reader, root, s);
|
|
@@ -4919,7 +4563,7 @@ async function stackOrder(root, stacks2, after = {}) {
|
|
|
4919
4563
|
else outside.push({ stack: s.key, reference: ref.name, why: found.outside });
|
|
4920
4564
|
}
|
|
4921
4565
|
}
|
|
4922
|
-
return { edges, outside
|
|
4566
|
+
return { edges, outside };
|
|
4923
4567
|
}
|
|
4924
4568
|
function orderWaves(waves, edges, canary = []) {
|
|
4925
4569
|
const unmet = [];
|
|
@@ -4968,16 +4612,16 @@ function orderWaves(waves, edges, canary = []) {
|
|
|
4968
4612
|
}
|
|
4969
4613
|
function orderLines(order, unmet) {
|
|
4970
4614
|
const out = [];
|
|
4971
|
-
if (!order.reads) out.push("the installed @intentius/chant-lexicon-pulumi reads no StackReferences (chant#4547), so the waves follow waves.after alone");
|
|
4972
4615
|
for (const e of order.edges) out.push(`${e.reader} updates after ${e.upstream} (${e.because === "stack-reference" ? "its StackReference" : "waves.after"})`);
|
|
4973
4616
|
for (const o of order.outside) out.push(`${o.stack}'s StackReference ${o.reference} orders nothing: ${o.why}`);
|
|
4974
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`);
|
|
4975
4618
|
return out;
|
|
4976
4619
|
}
|
|
4977
|
-
var
|
|
4620
|
+
var cached4;
|
|
4978
4621
|
var init_stack_order = __esm({
|
|
4979
4622
|
"src/stack-order.ts"() {
|
|
4980
4623
|
"use strict";
|
|
4624
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
4981
4625
|
}
|
|
4982
4626
|
});
|
|
4983
4627
|
|
|
@@ -4995,9 +4639,9 @@ function repoJoin(...parts) {
|
|
|
4995
4639
|
const out = posix2.normalize(parts.filter((p) => p !== "" && p !== ".").join("/") || ".").replace(/^\.\//, "").replace(/\/+$/, "");
|
|
4996
4640
|
return out === "" ? "." : out;
|
|
4997
4641
|
}
|
|
4998
|
-
function projectAt(
|
|
4642
|
+
function projectAt(detected, dir) {
|
|
4999
4643
|
const want = repoJoin(dir);
|
|
5000
|
-
return
|
|
4644
|
+
return detected.projects.find((p) => repoJoin(p.dir) === want);
|
|
5001
4645
|
}
|
|
5002
4646
|
function stackEntry(keys, key) {
|
|
5003
4647
|
const stacks2 = isObject(keys.stacks) ? keys.stacks : keys.stacks = {};
|
|
@@ -5032,6 +4676,7 @@ var IMPORT_SOURCES;
|
|
|
5032
4676
|
var init_types2 = __esm({
|
|
5033
4677
|
"src/import/types.ts"() {
|
|
5034
4678
|
"use strict";
|
|
4679
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
5035
4680
|
init_types();
|
|
5036
4681
|
IMPORT_SOURCES = ["pulumi-actions", "deployments", "spacelift", "env0"];
|
|
5037
4682
|
}
|
|
@@ -5040,10 +4685,10 @@ var init_types2 = __esm({
|
|
|
5040
4685
|
// src/import/deployments.ts
|
|
5041
4686
|
import { readdirSync as readdirSync6 } from "node:fs";
|
|
5042
4687
|
import { basename as basename2, dirname as dirname8, join as join11 } from "node:path";
|
|
5043
|
-
function deploymentFiles(root,
|
|
4688
|
+
function deploymentFiles(root, detected, named) {
|
|
5044
4689
|
if (named.length) return [...named];
|
|
5045
4690
|
const out = [];
|
|
5046
|
-
for (const p of
|
|
4691
|
+
for (const p of detected.projects) {
|
|
5047
4692
|
let names;
|
|
5048
4693
|
try {
|
|
5049
4694
|
names = readdirSync6(join11(root, p.dir)).sort();
|
|
@@ -5058,6 +4703,7 @@ var SETTINGS_FILE, NOT_CARRIED, importDeployments;
|
|
|
5058
4703
|
var init_deployments = __esm({
|
|
5059
4704
|
"src/import/deployments.ts"() {
|
|
5060
4705
|
"use strict";
|
|
4706
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
5061
4707
|
init_types();
|
|
5062
4708
|
init_detect();
|
|
5063
4709
|
init_types2();
|
|
@@ -5073,7 +4719,7 @@ var init_deployments = __esm({
|
|
|
5073
4719
|
"gitHub.deployPullRequest": "set apply.when: pull-request and update with /plywood up",
|
|
5074
4720
|
"gitHub.repository": "plywood runs in the repository it is set up in"
|
|
5075
4721
|
};
|
|
5076
|
-
importDeployments = (root,
|
|
4722
|
+
importDeployments = (root, detected, named) => {
|
|
5077
4723
|
let keys = {};
|
|
5078
4724
|
const unsupported = [];
|
|
5079
4725
|
const notes = [];
|
|
@@ -5081,7 +4727,7 @@ var init_deployments = __esm({
|
|
|
5081
4727
|
const not = (file, setting, why) => {
|
|
5082
4728
|
unsupported.push({ file, setting, why: why ?? NOT_CARRIED[setting] ?? "plywood has no setting for it" });
|
|
5083
4729
|
};
|
|
5084
|
-
for (const file of deploymentFiles(root,
|
|
4730
|
+
for (const file of deploymentFiles(root, detected, named)) {
|
|
5085
4731
|
const m = SETTINGS_FILE.exec(basename2(file));
|
|
5086
4732
|
if (!m) {
|
|
5087
4733
|
not(file, "(file)", "names no stack: call it Pulumi.<stack>.deploy.yaml (or .json) beside the project's Pulumi.yaml");
|
|
@@ -5100,7 +4746,7 @@ var init_deployments = __esm({
|
|
|
5100
4746
|
const fileDir = repoJoin(posixRel(root, dirname8(join11(root, file))));
|
|
5101
4747
|
const source = isObject(settings.sourceContext) && isObject(settings.sourceContext.git) ? settings.sourceContext.git : {};
|
|
5102
4748
|
const dir = typeof source.repoDir === "string" ? repoJoin(source.repoDir) : fileDir;
|
|
5103
|
-
const project = projectAt(
|
|
4749
|
+
const project = projectAt(detected, dir)?.name ?? projectAt(detected, fileDir)?.name;
|
|
5104
4750
|
if (!project) {
|
|
5105
4751
|
not(file, "sourceContext.git.repoDir", `${dir} holds no Pulumi.yaml`);
|
|
5106
4752
|
continue;
|
|
@@ -5155,9 +4801,9 @@ var init_deployments = __esm({
|
|
|
5155
4801
|
});
|
|
5156
4802
|
|
|
5157
4803
|
// src/import/env0.ts
|
|
5158
|
-
function stackFor(
|
|
4804
|
+
function stackFor(detected, names) {
|
|
5159
4805
|
for (const n of names) {
|
|
5160
|
-
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);
|
|
5161
4807
|
if (hits.length === 1) return hits[0].key;
|
|
5162
4808
|
}
|
|
5163
4809
|
return void 0;
|
|
@@ -5166,11 +4812,12 @@ var ENV0_WORKFLOW_FILES, ENV0_FLOW_FILES, importEnv0;
|
|
|
5166
4812
|
var init_env0 = __esm({
|
|
5167
4813
|
"src/import/env0.ts"() {
|
|
5168
4814
|
"use strict";
|
|
4815
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
5169
4816
|
init_types();
|
|
5170
4817
|
init_types2();
|
|
5171
4818
|
ENV0_WORKFLOW_FILES = ["env0.workflow.yml", "env0.workflow.yaml"];
|
|
5172
4819
|
ENV0_FLOW_FILES = ["env0.yml", "env0.yaml"];
|
|
5173
|
-
importEnv0 = (root,
|
|
4820
|
+
importEnv0 = (root, detected, named) => {
|
|
5174
4821
|
const keys = {};
|
|
5175
4822
|
const unsupported = [];
|
|
5176
4823
|
const notes = [];
|
|
@@ -5188,7 +4835,7 @@ var init_env0 = __esm({
|
|
|
5188
4835
|
for (const [id, raw] of Object.entries(envs)) {
|
|
5189
4836
|
const e = isObject(raw) ? raw : {};
|
|
5190
4837
|
const names = [id, ...typeof e.name === "string" ? [e.name] : [], ...typeof e.templateName === "string" ? [e.templateName] : []];
|
|
5191
|
-
const key = stackFor(
|
|
4838
|
+
const key = stackFor(detected, names);
|
|
5192
4839
|
if (key) stackOf.set(id, key);
|
|
5193
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` });
|
|
5194
4841
|
for (const k of Object.keys(e).filter((x) => !["name", "templateName", "needs"].includes(x))) {
|
|
@@ -5266,6 +4913,7 @@ var WORKFLOW_DIRS2, CARRIED, WHY, PULUMI_CLOUD, importPulumiActions;
|
|
|
5266
4913
|
var init_pulumi_actions = __esm({
|
|
5267
4914
|
"src/import/pulumi-actions.ts"() {
|
|
5268
4915
|
"use strict";
|
|
4916
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
5269
4917
|
init_types();
|
|
5270
4918
|
init_types2();
|
|
5271
4919
|
WORKFLOW_DIRS2 = [".github/workflows", ".forgejo/workflows", ".gitea/workflows"];
|
|
@@ -5282,7 +4930,7 @@ var init_pulumi_actions = __esm({
|
|
|
5282
4930
|
plan: "plywood's update applies the preview its approval names, through chant"
|
|
5283
4931
|
};
|
|
5284
4932
|
PULUMI_CLOUD = /^https:\/\/(?:app|api)\.pulumi\.com\/?$/;
|
|
5285
|
-
importPulumiActions = (root,
|
|
4933
|
+
importPulumiActions = (root, detected, named) => {
|
|
5286
4934
|
let keys = {};
|
|
5287
4935
|
const unsupported = [];
|
|
5288
4936
|
const notes = [];
|
|
@@ -5318,7 +4966,7 @@ var init_pulumi_actions = __esm({
|
|
|
5318
4966
|
const dir = repoJoin(typeof w["work-dir"] === "string" ? w["work-dir"] : ".");
|
|
5319
4967
|
const parts = stackName.split("/");
|
|
5320
4968
|
const stack = parts[parts.length - 1];
|
|
5321
|
-
const project = projectAt(
|
|
4969
|
+
const project = projectAt(detected, dir)?.name ?? (parts.length === 3 ? parts[1] : void 0);
|
|
5322
4970
|
if (!project) {
|
|
5323
4971
|
unsupported.push({ file, setting: `${where}.with.stack-name`, why: `${dir} holds no Pulumi.yaml, so ${stackName} names no project` });
|
|
5324
4972
|
return;
|
|
@@ -5371,11 +5019,12 @@ var SPACELIFT_FILES, CARRIED2, importSpacelift;
|
|
|
5371
5019
|
var init_spacelift = __esm({
|
|
5372
5020
|
"src/import/spacelift.ts"() {
|
|
5373
5021
|
"use strict";
|
|
5022
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
5374
5023
|
init_types();
|
|
5375
5024
|
init_types2();
|
|
5376
5025
|
SPACELIFT_FILES = [".spacelift/config.yml", ".spacelift/config.yaml"];
|
|
5377
5026
|
CARRIED2 = ["project_root", "runner_image"];
|
|
5378
|
-
importSpacelift = (root,
|
|
5027
|
+
importSpacelift = (root, detected, named) => {
|
|
5379
5028
|
const keys = {};
|
|
5380
5029
|
const unsupported = [];
|
|
5381
5030
|
const notes = [];
|
|
@@ -5402,7 +5051,7 @@ var init_spacelift = __esm({
|
|
|
5402
5051
|
const own = isObject(raw) ? raw : {};
|
|
5403
5052
|
const s = { ...defaults, ...own };
|
|
5404
5053
|
const dir = repoJoin(typeof s.project_root === "string" ? s.project_root : ".");
|
|
5405
|
-
const project = projectAt(
|
|
5054
|
+
const project = projectAt(detected, dir);
|
|
5406
5055
|
if (!project) {
|
|
5407
5056
|
unsupported.push({ file, setting: `stacks.${id}.project_root`, why: `${dir} holds no Pulumi.yaml` });
|
|
5408
5057
|
continue;
|
|
@@ -5451,9 +5100,9 @@ function parseFrom(value) {
|
|
|
5451
5100
|
if (!source) return { error: `--from ${value} is not one of ${IMPORT_SOURCES.join(", ")}, and its path names none; give <source>=${value}` };
|
|
5452
5101
|
return { source, files: [file] };
|
|
5453
5102
|
}
|
|
5454
|
-
function runImport(root,
|
|
5103
|
+
function runImport(root, detected, from) {
|
|
5455
5104
|
try {
|
|
5456
|
-
return IMPORTERS[from.source](root,
|
|
5105
|
+
return IMPORTERS[from.source](root, detected, from.files);
|
|
5457
5106
|
} catch (e) {
|
|
5458
5107
|
const file = from.files[0] ?? "(its files)";
|
|
5459
5108
|
return { source: from.source, files: [], keys: {}, unsupported: [{ file, setting: "(file)", why: `could not be read: ${e.message}` }], notes: [] };
|
|
@@ -5463,6 +5112,7 @@ var IMPORTERS;
|
|
|
5463
5112
|
var init_import = __esm({
|
|
5464
5113
|
"src/import/index.ts"() {
|
|
5465
5114
|
"use strict";
|
|
5115
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
5466
5116
|
init_deployments();
|
|
5467
5117
|
init_env0();
|
|
5468
5118
|
init_pulumi_actions();
|
|
@@ -5512,11 +5162,11 @@ function parseInitArgs(args) {
|
|
|
5512
5162
|
};
|
|
5513
5163
|
const preset2 = take("--preset");
|
|
5514
5164
|
const gate = take("--gate");
|
|
5515
|
-
const
|
|
5165
|
+
const engine3 = take("--engine");
|
|
5516
5166
|
const forge = take("--forge");
|
|
5517
5167
|
const env = take("--env");
|
|
5518
5168
|
const fromFlag = take("--from");
|
|
5519
|
-
const bad = [preset2, gate,
|
|
5169
|
+
const bad = [preset2, gate, engine3, forge, env, fromFlag].find((t) => t.error);
|
|
5520
5170
|
if (bad) return { error: bad.error };
|
|
5521
5171
|
const flags = /* @__PURE__ */ new Set(["--no-bucket-check", "--dry-run", "--yes", "-y"]);
|
|
5522
5172
|
const extra = rest.filter((a) => !flags.has(a));
|
|
@@ -5524,7 +5174,7 @@ function parseInitArgs(args) {
|
|
|
5524
5174
|
if (preset2.value !== void 0 && !presetByName(preset2.value)) return { error: `--preset ${preset2.value} is not one of ${PRESET_NAMES.join(", ")}` };
|
|
5525
5175
|
const g = gate.value !== void 0 ? gateFlag(gate.value) : void 0;
|
|
5526
5176
|
if (gate.value !== void 0 && !g) return { error: `--gate ${gate.value} is not one of always, on-destructive (or destroy), never` };
|
|
5527
|
-
if (
|
|
5177
|
+
if (engine3.value !== void 0 && !INIT_ENGINES.includes(engine3.value)) return { error: `--engine ${engine3.value} is not one of ${INIT_ENGINES.join(", ")}` };
|
|
5528
5178
|
if (forge.value !== void 0 && !FORGES.includes(forge.value)) return { error: `--forge ${forge.value} is not one of ${FORGES.join(", ")}` };
|
|
5529
5179
|
const from = fromFlag.value !== void 0 ? parseFrom(fromFlag.value) : void 0;
|
|
5530
5180
|
if (from && "error" in from) return { error: from.error };
|
|
@@ -5532,7 +5182,7 @@ function parseInitArgs(args) {
|
|
|
5532
5182
|
...from ? { from } : {},
|
|
5533
5183
|
...preset2.value ? { preset: preset2.value } : {},
|
|
5534
5184
|
...g ? { gate: g } : {},
|
|
5535
|
-
...
|
|
5185
|
+
...engine3.value ? { engine: engine3.value } : {},
|
|
5536
5186
|
...forge.value ? { forge: forge.value } : {},
|
|
5537
5187
|
env: env.value ?? "production",
|
|
5538
5188
|
bucketCheck: !rest.includes("--no-bucket-check"),
|
|
@@ -5553,7 +5203,7 @@ function put(root, rel, text, dryRun, note) {
|
|
|
5553
5203
|
function existingConfig(root) {
|
|
5554
5204
|
return CONFIG_NAMES.find((n) => existsSync10(join13(root, n)));
|
|
5555
5205
|
}
|
|
5556
|
-
async function newConfigKeys(
|
|
5206
|
+
async function newConfigKeys(detected, opts, io) {
|
|
5557
5207
|
const notes = [];
|
|
5558
5208
|
let presetName = opts.preset;
|
|
5559
5209
|
if (!presetName && io.ask && !opts.yes) {
|
|
@@ -5563,31 +5213,31 @@ async function newConfigKeys(detected2, opts, io) {
|
|
|
5563
5213
|
const preset2 = presetByName(presetName ?? DEFAULT_PRESET);
|
|
5564
5214
|
const keys = structuredClone(preset2.keys);
|
|
5565
5215
|
if (opts.gate) keys.gate = opts.gate;
|
|
5566
|
-
const
|
|
5567
|
-
Object.assign(keys,
|
|
5568
|
-
notes.push(...
|
|
5569
|
-
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;
|
|
5570
5220
|
let forge;
|
|
5571
5221
|
if (opts.forge) {
|
|
5572
5222
|
forge = { kind: opts.forge, because: "--forge" };
|
|
5573
|
-
if (
|
|
5574
|
-
else if (
|
|
5575
|
-
} else if (
|
|
5576
|
-
forge = { kind:
|
|
5577
|
-
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 };
|
|
5578
5228
|
} else {
|
|
5579
5229
|
const kind = io.ask && !opts.yes ? await io.ask("Forge", FORGES, "github") : "github";
|
|
5580
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" };
|
|
5581
5231
|
keys.forge = { kind };
|
|
5582
5232
|
}
|
|
5583
5233
|
const fromEnv = io.env.PULUMI_BACKEND_URL?.trim();
|
|
5584
|
-
if (fromEnv && !engineBackends &&
|
|
5234
|
+
if (fromEnv && !engineBackends && detected.projects.some((p) => !p.backendUrl)) {
|
|
5585
5235
|
keys.backend = fromEnv;
|
|
5586
5236
|
notes.push(`backend ${fromEnv} came from PULUMI_BACKEND_URL; it is in plywood.yml so the jobs use it too`);
|
|
5587
5237
|
}
|
|
5588
5238
|
return { keys, notes, forge };
|
|
5589
5239
|
}
|
|
5590
|
-
function initStacks(
|
|
5240
|
+
function initStacks(detected, doc, members = []) {
|
|
5591
5241
|
const listed = stacksOf(doc);
|
|
5592
5242
|
const pinned = new Map(members.filter((m) => m.engineBin && m.engineVersion).map((m) => [m.name, m]));
|
|
5593
5243
|
const out = [];
|
|
@@ -5595,8 +5245,8 @@ function initStacks(detected2, doc, members = []) {
|
|
|
5595
5245
|
const add = (key, project, stack, dir) => {
|
|
5596
5246
|
const own = listed[key] ?? {};
|
|
5597
5247
|
const backend = own.backend ?? (typeof doc.backend === "string" ? doc.backend : void 0);
|
|
5598
|
-
const
|
|
5599
|
-
const grain = lockGrainFor(doc,
|
|
5248
|
+
const engine3 = engineOf(doc, backend)?.engine;
|
|
5249
|
+
const grain = lockGrainFor(doc, engine3);
|
|
5600
5250
|
const stackDir = own.dir ?? dir;
|
|
5601
5251
|
const packs = packsForStack(stepPolicyPacksOf(doc), stackDir);
|
|
5602
5252
|
const pin = pinned.get(key);
|
|
@@ -5605,7 +5255,7 @@ function initStacks(detected2, doc, members = []) {
|
|
|
5605
5255
|
project,
|
|
5606
5256
|
stack,
|
|
5607
5257
|
dir: stackDir,
|
|
5608
|
-
...
|
|
5258
|
+
...engine3 ? { engine: engine3 } : {},
|
|
5609
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.
|
|
5610
5260
|
...backend ? { backend } : {},
|
|
5611
5261
|
...pin ? { engineVersion: pin.engineVersion, ...pin.engineRoot ? { engineRoot: pin.engineRoot } : {} } : {},
|
|
@@ -5617,7 +5267,7 @@ function initStacks(detected2, doc, members = []) {
|
|
|
5617
5267
|
});
|
|
5618
5268
|
seen.add(key);
|
|
5619
5269
|
};
|
|
5620
|
-
for (const s of
|
|
5270
|
+
for (const s of detected.stacks) add(s.key, s.project, s.stack, s.dir);
|
|
5621
5271
|
for (const [key, own] of Object.entries(listed)) {
|
|
5622
5272
|
if (seen.has(key)) continue;
|
|
5623
5273
|
const [project, stack] = key.split("/");
|
|
@@ -5625,30 +5275,22 @@ function initStacks(detected2, doc, members = []) {
|
|
|
5625
5275
|
}
|
|
5626
5276
|
return out;
|
|
5627
5277
|
}
|
|
5628
|
-
function forgeFor2(doc,
|
|
5278
|
+
function forgeFor2(doc, detected, opts) {
|
|
5629
5279
|
if (opts.forge) return { kind: opts.forge, because: "--forge" };
|
|
5630
5280
|
const kind = isObject(doc.forge) ? doc.forge.kind : void 0;
|
|
5631
5281
|
if (kind) return { kind, because: "plywood.yml says forge.kind" };
|
|
5632
|
-
if (
|
|
5282
|
+
if (detected.forge) return { kind: detected.forge.kind, because: detected.forge.because };
|
|
5633
5283
|
return { kind: "github", because: "origin names no forge init knows, so github" };
|
|
5634
5284
|
}
|
|
5635
|
-
async function chantWriteCiHook() {
|
|
5636
|
-
try {
|
|
5637
|
-
const m = await import("@intentius/chant/components/check-hooks");
|
|
5638
|
-
return typeof m.writeCiHook === "function" ? m.writeCiHook : void 0;
|
|
5639
|
-
} catch {
|
|
5640
|
-
return void 0;
|
|
5641
|
-
}
|
|
5642
|
-
}
|
|
5643
5285
|
async function runInit(opts, table, io) {
|
|
5644
|
-
const
|
|
5645
|
-
const { root } =
|
|
5286
|
+
const detected = detect(io.cwd);
|
|
5287
|
+
const { root } = detected;
|
|
5646
5288
|
const files = [];
|
|
5647
5289
|
const notes = [];
|
|
5648
5290
|
const todo = [];
|
|
5649
5291
|
const buckets = [];
|
|
5650
5292
|
const adopt = [];
|
|
5651
|
-
const imported = opts.from ? runImport(root,
|
|
5293
|
+
const imported = opts.from ? runImport(root, detected, opts.from) : void 0;
|
|
5652
5294
|
if (imported && imported.files.length === 0) notes.push(`--from ${imported.source} found no file to read${imported.notes.length ? `: ${imported.notes.join("; ")}` : ""}`);
|
|
5653
5295
|
const fail = (message, forge2 = { kind: "github", because: "" }) => ({
|
|
5654
5296
|
ok: false,
|
|
@@ -5663,8 +5305,8 @@ async function runInit(opts, table, io) {
|
|
|
5663
5305
|
adopt,
|
|
5664
5306
|
...imported ? { imported } : {}
|
|
5665
5307
|
});
|
|
5666
|
-
if (
|
|
5667
|
-
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");
|
|
5668
5310
|
let config2;
|
|
5669
5311
|
let forge;
|
|
5670
5312
|
const kept = existingConfig(root);
|
|
@@ -5678,10 +5320,10 @@ async function runInit(opts, table, io) {
|
|
|
5678
5320
|
const onPlunge = opts.engine === "plunge" && engineOf(config2.doc)?.engine === "plunge";
|
|
5679
5321
|
const ignored = [opts.preset && "--preset", opts.gate && "--gate", opts.engine && !onPlunge && "--engine", imported && "--from"].filter(Boolean);
|
|
5680
5322
|
if (ignored.length) notes.push(`${kept} is kept as it is, so ${ignored.join(", ")} changed nothing; set the keys there`);
|
|
5681
|
-
if (opts.engine === "plunge" && !onPlunge) notes.push(...keptPlungeLines(kept,
|
|
5682
|
-
forge = forgeFor2(config2.doc,
|
|
5323
|
+
if (opts.engine === "plunge" && !onPlunge) notes.push(...keptPlungeLines(kept, detected, io.env));
|
|
5324
|
+
forge = forgeFor2(config2.doc, detected, opts);
|
|
5683
5325
|
} else {
|
|
5684
|
-
const made = await newConfigKeys(
|
|
5326
|
+
const made = await newConfigKeys(detected, opts, io);
|
|
5685
5327
|
notes.push(...made.notes);
|
|
5686
5328
|
forge = made.forge;
|
|
5687
5329
|
const text = plywoodYml(imported ? mergeKeys(made.keys, imported.keys) : made.keys);
|
|
@@ -5694,15 +5336,13 @@ async function runInit(opts, table, io) {
|
|
|
5694
5336
|
if (problems.length) return fail(problems.map((p) => `${kept}: ${p.key} ${p.message}`).join("\n"), forge);
|
|
5695
5337
|
const doc = config2.doc;
|
|
5696
5338
|
const settings = toChantSettings(config2);
|
|
5697
|
-
const
|
|
5698
|
-
|
|
5699
|
-
const reviewSuffix = reviewStacks2 ? reviewSuffixOf(settings.pipeline.reviewStackName) : void 0;
|
|
5700
|
-
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);
|
|
5701
5341
|
const order = await stackOrder(root, stacks2, settings.pipeline.after);
|
|
5702
5342
|
const ordered = orderWaves(wavesOf(stacks2, settings.pipeline.waves), order.edges, settings.pipeline.canary);
|
|
5703
5343
|
const waves = ordered.waves;
|
|
5704
|
-
if (order.edges.length || order.outside.length || ordered.unmet.length
|
|
5705
|
-
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);
|
|
5706
5346
|
files.push(declaration.file);
|
|
5707
5347
|
if (existsSync10(join13(root, CHANT_CONFIG_FILE))) {
|
|
5708
5348
|
const text = readFileSync12(join13(root, CHANT_CONFIG_FILE), "utf8");
|
|
@@ -5712,19 +5352,16 @@ async function runInit(opts, table, io) {
|
|
|
5712
5352
|
} else files.push(put(root, CHANT_CONFIG_FILE, chantConfigTs(), opts.dryRun));
|
|
5713
5353
|
files.push(put(root, COMPONENTS_FILE, componentsFile(stacks2, settings.pipeline.after, reviewSuffix ? { reviewSuffix } : {}), opts.dryRun));
|
|
5714
5354
|
files.push(...providersPresetStep(root, doc, opts.dryRun, notes));
|
|
5715
|
-
if (
|
|
5716
|
-
for (const wf of
|
|
5355
|
+
if (detected.preCommit) files.push(await preCommitStep(root, detected.preCommit, opts.dryRun));
|
|
5356
|
+
for (const wf of detected.pulumiActions) {
|
|
5717
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`);
|
|
5718
5358
|
}
|
|
5719
5359
|
const chant = await chantProduct();
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
files.push({ file: WAVES_SPEC_FILE2, status: "skipped", note: message }, { file: WORKFLOW_FILE[forge.kind], status: "skipped", note: "needs the same chant" });
|
|
5723
|
-
} else {
|
|
5724
|
-
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);
|
|
5725
5362
|
if (settings.pipeline.jobs) {
|
|
5726
5363
|
try {
|
|
5727
|
-
spec = withJobSettings(spec, settings.pipeline.jobs, forge.kind
|
|
5364
|
+
spec = withJobSettings(spec, settings.pipeline.jobs, forge.kind);
|
|
5728
5365
|
} catch (e) {
|
|
5729
5366
|
files.push({ file: WAVES_SPEC_FILE2, status: "failed", note: e.message });
|
|
5730
5367
|
spec = void 0;
|
|
@@ -5736,10 +5373,10 @@ async function runInit(opts, table, io) {
|
|
|
5736
5373
|
}
|
|
5737
5374
|
if (spec) files.push(...await workflowStep(chant, root, doc, settings, forge.kind, opts, stacks2.length > 0 ? spec : void 0, table, io));
|
|
5738
5375
|
}
|
|
5739
|
-
if (opts.bucketCheck) notes.push(...await bucketStep(
|
|
5740
|
-
adopt.push(...await adoptStep(root,
|
|
5741
|
-
todo.push(...todoLines(
|
|
5742
|
-
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)) {
|
|
5743
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`);
|
|
5744
5381
|
}
|
|
5745
5382
|
const failed = files.find((f) => f.status === "failed");
|
|
@@ -5757,9 +5394,9 @@ async function runInit(opts, table, io) {
|
|
|
5757
5394
|
...imported ? { imported } : {}
|
|
5758
5395
|
};
|
|
5759
5396
|
}
|
|
5760
|
-
function declarationStep(root,
|
|
5397
|
+
function declarationStep(root, detected, settings, dryRun) {
|
|
5761
5398
|
if (existsSync10(join13(root, "chant.workspace.jsonc"))) {
|
|
5762
|
-
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}` } };
|
|
5763
5400
|
}
|
|
5764
5401
|
let existing;
|
|
5765
5402
|
const file = join13(root, DECLARATION_FILE);
|
|
@@ -5772,7 +5409,7 @@ function declarationStep(root, detected2, settings, dryRun) {
|
|
|
5772
5409
|
return { file: { file: DECLARATION_FILE, status: "failed", note: `is not JSON init can merge into (${e.message})` } };
|
|
5773
5410
|
}
|
|
5774
5411
|
}
|
|
5775
|
-
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 });
|
|
5776
5413
|
if (existing && JSON.stringify(existing) === JSON.stringify(doc)) return { file: { file: DECLARATION_FILE, status: "unchanged" }, doc };
|
|
5777
5414
|
return { file: put(root, DECLARATION_FILE, jsonText(doc), dryRun), doc };
|
|
5778
5415
|
}
|
|
@@ -5792,10 +5429,7 @@ function providersPresetStep(root, doc, dryRun, notes) {
|
|
|
5792
5429
|
return out;
|
|
5793
5430
|
}
|
|
5794
5431
|
async function preCommitStep(root, config2, dryRun) {
|
|
5795
|
-
const write = await
|
|
5796
|
-
if (!write) {
|
|
5797
|
-
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}` };
|
|
5798
|
-
}
|
|
5432
|
+
const { writeCiHook: write } = await import("@intentius/chant/components/check-hooks");
|
|
5799
5433
|
if (dryRun || !existsSync10(join13(root, DECLARATION_FILE))) return { file: DECLARATION_FILE, status: "skipped", note: `${config2} would run as the check hook pre-commit` };
|
|
5800
5434
|
const before = readFileSync12(join13(root, DECLARATION_FILE), "utf8");
|
|
5801
5435
|
try {
|
|
@@ -5807,19 +5441,20 @@ async function preCommitStep(root, config2, dryRun) {
|
|
|
5807
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` };
|
|
5808
5442
|
}
|
|
5809
5443
|
async function wavesSpecStep(chant, stacks2, doc, settings, branch, ordered, forge) {
|
|
5810
|
-
const runner = waveRunner(chantPipelineOptions(settings.pipeline
|
|
5444
|
+
const runner = waveRunner(chantPipelineOptions(settings.pipeline).runner);
|
|
5445
|
+
const rego = isObject(doc.policy) && (typeof doc.policy.rego === "string" || doc.policy.source !== void 0 || doc.policy.command !== void 0);
|
|
5811
5446
|
const base = wavesSpec({
|
|
5447
|
+
...rego ? { policy: true } : {},
|
|
5812
5448
|
stacks: stacks2,
|
|
5813
5449
|
ordered,
|
|
5814
5450
|
gate: gateOf(doc),
|
|
5815
5451
|
branch,
|
|
5816
5452
|
...runner !== void 0 ? { runner } : {},
|
|
5817
5453
|
...settings.pipeline.quorum?.length ? { quorum: settings.pipeline.quorum } : {},
|
|
5818
|
-
|
|
5819
|
-
...settings.pipeline.notify ? { notify: { ...notifyForChant(settings.pipeline.notify, chant) } } : {}
|
|
5454
|
+
...settings.pipeline.notify ? { notify: { ...settings.pipeline.notify } } : {}
|
|
5820
5455
|
});
|
|
5821
|
-
const named = chant.productWavesSpec
|
|
5822
|
-
return previewOf(doc)
|
|
5456
|
+
const named = chant.productWavesSpec(base, PRODUCT_WORDS);
|
|
5457
|
+
return previewOf(doc) ? withWavePreview(named, forge) : named;
|
|
5823
5458
|
}
|
|
5824
5459
|
async function workflowStep(chant, root, doc, settings, forge, opts, spec, table, io) {
|
|
5825
5460
|
const output = WORKFLOW_FILE[forge];
|
|
@@ -5828,17 +5463,16 @@ async function workflowStep(chant, root, doc, settings, forge, opts, spec, table
|
|
|
5828
5463
|
try {
|
|
5829
5464
|
const { noteControlTarget: noteControlTarget2 } = await Promise.resolve().then(() => (init_control2(), control_exports));
|
|
5830
5465
|
const control3 = await noteControlTarget2(doc, root);
|
|
5831
|
-
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 } : {} });
|
|
5832
5467
|
} catch (e) {
|
|
5833
5468
|
return [{ file: output, status: "failed", note: e.message }];
|
|
5834
5469
|
}
|
|
5835
5470
|
if (opts.dryRun) return [{ file: output, status: "skipped", note: "--dry-run: chant's generator was not run" }];
|
|
5836
5471
|
const argv = plan.argv.map((a, i) => i === 1 && a === "." ? root : a);
|
|
5837
|
-
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 });
|
|
5838
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` }];
|
|
5839
5474
|
const notes = [
|
|
5840
5475
|
...written.kept.length ? [`kept your own jobs: ${written.kept.join(", ")}`] : [],
|
|
5841
|
-
...doc.review_stacks !== void 0 && !plan.argv.includes("--pr-env") ? [REVIEW_STACKS_NEED_CHANT] : [],
|
|
5842
5476
|
...prJobs.notes,
|
|
5843
5477
|
...plan.notes,
|
|
5844
5478
|
...writtenNotes(plan, written.file)
|
|
@@ -5850,18 +5484,17 @@ async function workflowStep(chant, root, doc, settings, forge, opts, spec, table
|
|
|
5850
5484
|
}
|
|
5851
5485
|
return out;
|
|
5852
5486
|
}
|
|
5853
|
-
async function bucketStep(
|
|
5487
|
+
async function bucketStep(detected, doc, env, reports) {
|
|
5854
5488
|
const listed = stacksOf(doc);
|
|
5855
5489
|
const urls = /* @__PURE__ */ new Set();
|
|
5856
|
-
for (const s of
|
|
5857
|
-
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 };
|
|
5858
5492
|
const b = backendOf(project, { ...listed[s.key]?.backend ? { stack: listed[s.key].backend } : {}, ...typeof doc.backend === "string" ? { all: doc.backend } : {} }, env);
|
|
5859
5493
|
if (b.url && !isPulumiCloud(b.url)) urls.add(b.url);
|
|
5860
5494
|
}
|
|
5861
5495
|
const targets = [...urls].map((u) => bucketTarget(u, env)).filter((t) => t !== void 0);
|
|
5862
5496
|
if (targets.length === 0) return [];
|
|
5863
5497
|
const check2 = await chantBucketCheck();
|
|
5864
|
-
if (!check2) return [needsBucketCheckMessage(BUILT_CHANT)];
|
|
5865
5498
|
const lines2 = ["State buckets:"];
|
|
5866
5499
|
for (const t of targets) {
|
|
5867
5500
|
if (!t.ok) {
|
|
@@ -5878,12 +5511,12 @@ async function bucketStep(detected2, doc, env, reports) {
|
|
|
5878
5511
|
}
|
|
5879
5512
|
return lines2;
|
|
5880
5513
|
}
|
|
5881
|
-
async function adoptStep(root,
|
|
5514
|
+
async function adoptStep(root, detected, doc, env, declaration, dryRun, files, notes, todo) {
|
|
5882
5515
|
const listed = stacksOf(doc);
|
|
5883
5516
|
const all = typeof doc.backend === "string" ? doc.backend : void 0;
|
|
5884
5517
|
const targets = [];
|
|
5885
|
-
for (const s of
|
|
5886
|
-
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 };
|
|
5887
5520
|
const own = listed[s.key];
|
|
5888
5521
|
const b = backendOf(project, { ...own?.backend ? { stack: own.backend } : {}, ...all ? { all } : {} }, env);
|
|
5889
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 });
|
|
@@ -5891,11 +5524,7 @@ async function adoptStep(root, detected2, doc, env, declaration, dryRun, files,
|
|
|
5891
5524
|
if (targets.length === 0) return [];
|
|
5892
5525
|
const chant = await chantAdopt();
|
|
5893
5526
|
const previews = await adoptPreviews(targets, chant, root);
|
|
5894
|
-
notes.push(...adoptLines(previews
|
|
5895
|
-
if (!chant?.hasOp) {
|
|
5896
|
-
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}` });
|
|
5897
|
-
return previews;
|
|
5898
|
-
}
|
|
5527
|
+
notes.push(...adoptLines(previews));
|
|
5899
5528
|
const left = stacksToAdopt(targets, previews);
|
|
5900
5529
|
if (left.length === 0) {
|
|
5901
5530
|
if (existsSync10(join13(root, ADOPT_OP_FILE))) files.push({ file: ADOPT_OP_FILE, status: "kept", note: "every stack is adopted; delete it" });
|
|
@@ -5906,17 +5535,17 @@ async function adoptStep(root, detected2, doc, env, declaration, dryRun, files,
|
|
|
5906
5535
|
todo.push(...adoptTodo(rows));
|
|
5907
5536
|
return previews;
|
|
5908
5537
|
}
|
|
5909
|
-
function keptPlungeLines(kept,
|
|
5910
|
-
const step = engineStep({ engine: "plunge", detected
|
|
5538
|
+
function keptPlungeLines(kept, detected, env) {
|
|
5539
|
+
const step = engineStep({ engine: "plunge", detected, env });
|
|
5911
5540
|
const entries = Object.entries(step.keys);
|
|
5912
5541
|
if (entries.length === 0) return step.notes;
|
|
5913
5542
|
const yaml = entries.map(([k, v]) => emitYAMLEntry2(k, v)).join("\n").split("\n").filter((l) => l !== "");
|
|
5914
5543
|
return [...step.notes, `To move to plunge, set these keys in ${kept}:`, ...yaml.map((l) => ` ${l}`)];
|
|
5915
5544
|
}
|
|
5916
|
-
function todoLines(
|
|
5545
|
+
function todoLines(detected, doc, forge, env) {
|
|
5917
5546
|
const out = [];
|
|
5918
5547
|
const all = typeof doc.backend === "string" ? doc.backend : void 0;
|
|
5919
|
-
const cloud =
|
|
5548
|
+
const cloud = detected.projects.some((p) => isPulumiCloud(backendOf(p, all ? { all } : {}, env).url));
|
|
5920
5549
|
const where = forge === "gitlab" ? "CI/CD variables" : "repository secrets";
|
|
5921
5550
|
const traded = identityClouds(doc);
|
|
5922
5551
|
if (cloud && traded.includes("pulumi")) {
|
|
@@ -5924,9 +5553,9 @@ function todoLines(detected2, doc, forge, env) {
|
|
|
5924
5553
|
} else if (cloud) {
|
|
5925
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}`);
|
|
5926
5555
|
}
|
|
5927
|
-
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`);
|
|
5928
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");
|
|
5929
|
-
out.push(`Make ${PRODUCT}/preview a required status on ${
|
|
5558
|
+
out.push(`Make ${PRODUCT}/preview a required status on ${detected.defaultBranch}`);
|
|
5930
5559
|
return out;
|
|
5931
5560
|
}
|
|
5932
5561
|
function initLines(r) {
|
|
@@ -5962,11 +5591,11 @@ var DECLARATION_FILE, CHANT_CONFIG_FILE, COMPONENTS_FILE, WAVES_SPEC_FILE2, CHAN
|
|
|
5962
5591
|
var init_init = __esm({
|
|
5963
5592
|
"src/init.ts"() {
|
|
5964
5593
|
"use strict";
|
|
5594
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
5965
5595
|
init_package();
|
|
5966
5596
|
init_chant_product();
|
|
5967
5597
|
init_chant_config();
|
|
5968
5598
|
init_identity();
|
|
5969
|
-
init_observe();
|
|
5970
5599
|
init_policy();
|
|
5971
5600
|
init_policy_presets();
|
|
5972
5601
|
init_approval();
|
|
@@ -6005,13 +5634,11 @@ var init_init = __esm({
|
|
|
6005
5634
|
var control_exports = {};
|
|
6006
5635
|
__export(control_exports, {
|
|
6007
5636
|
CONTROLLED_KEYS: () => CONTROLLED_KEYS,
|
|
6008
|
-
CONTROL_NEEDS_CHANT: () => CONTROL_NEEDS_CHANT,
|
|
6009
5637
|
chantControl: () => chantControl,
|
|
6010
5638
|
control: () => control2,
|
|
6011
5639
|
controlGenerator: () => controlGenerator,
|
|
6012
5640
|
controlTarget: () => controlTarget,
|
|
6013
5641
|
controlledConfigText: () => controlledConfigText,
|
|
6014
|
-
hasWavesControl: () => hasWavesControl,
|
|
6015
5642
|
noteControlTarget: () => noteControlTarget,
|
|
6016
5643
|
projectSettings: () => projectSettings,
|
|
6017
5644
|
setChantControlForTests: () => setChantControlForTests,
|
|
@@ -6024,15 +5651,10 @@ import { createRequire } from "node:module";
|
|
|
6024
5651
|
import { dirname as dirname10, join as join14 } from "node:path";
|
|
6025
5652
|
import { pathToFileURL } from "node:url";
|
|
6026
5653
|
import { emitYAMLEntry as emitYAMLEntry3, parseYAML as parseYAML4 } from "@intentius/chant/yaml";
|
|
6027
|
-
function hasWavesControl(chant) {
|
|
6028
|
-
return typeof chant.recordControlAppliedFromEnv === "function";
|
|
6029
|
-
}
|
|
6030
5654
|
async function wavesControl(cwd) {
|
|
6031
5655
|
const file = findConfig(cwd);
|
|
6032
5656
|
if (!file || controlOf(loadConfig(file).doc) === void 0) return { none: true };
|
|
6033
5657
|
const chant = await chantControl();
|
|
6034
|
-
if (!chant) return { missing: CONTROL_NEEDS_CHANT };
|
|
6035
|
-
if (!hasWavesControl(chant)) return { missing: "the waves workflow tells the control repo with a chant that has chant#4617" };
|
|
6036
5658
|
const target = await controlTarget(chant, cwd);
|
|
6037
5659
|
if ("error" in target) return target;
|
|
6038
5660
|
const settings = { ...controlOf(loadConfig(file).doc), member: target.member };
|
|
@@ -6044,7 +5666,7 @@ async function noteControlTarget(doc, root) {
|
|
|
6044
5666
|
const settings = controlOf(doc);
|
|
6045
5667
|
if (!settings) return void 0;
|
|
6046
5668
|
const chant = await chantControl();
|
|
6047
|
-
if (
|
|
5669
|
+
if (chant.controlSettingsProblems(settings).length > 0) return void 0;
|
|
6048
5670
|
try {
|
|
6049
5671
|
return chant.resolveControlTarget(settings, repoName(root));
|
|
6050
5672
|
} catch {
|
|
@@ -6052,18 +5674,11 @@ async function noteControlTarget(doc, root) {
|
|
|
6052
5674
|
}
|
|
6053
5675
|
}
|
|
6054
5676
|
function chantControl() {
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
const m = await import("@intentius/chant/ci-control");
|
|
6058
|
-
return REQUIRED3.every((n) => typeof m[n] === "function") ? m : void 0;
|
|
6059
|
-
} catch {
|
|
6060
|
-
return void 0;
|
|
6061
|
-
}
|
|
6062
|
-
})();
|
|
6063
|
-
return cached7;
|
|
5677
|
+
cached5 ??= import("@intentius/chant/ci-control").then((m) => m);
|
|
5678
|
+
return cached5;
|
|
6064
5679
|
}
|
|
6065
5680
|
function setChantControlForTests(fake) {
|
|
6066
|
-
|
|
5681
|
+
cached5 = fake === "reset" ? void 0 : Promise.resolve(fake);
|
|
6067
5682
|
}
|
|
6068
5683
|
function projectSettings(defaults, own) {
|
|
6069
5684
|
return { ...defaults ?? {}, ...own };
|
|
@@ -6109,7 +5724,6 @@ async function control2(args, json, table, io) {
|
|
|
6109
5724
|
const subs = ["init", "put", "import", "list", "retire", "reconcile", "dispatch"];
|
|
6110
5725
|
if (!sub || !subs.includes(sub)) return usage(`control takes one of ${subs.join(", ")}`, table, io);
|
|
6111
5726
|
const chant = await chantControl();
|
|
6112
|
-
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);
|
|
6113
5727
|
const emit = (envelope, lines2) => {
|
|
6114
5728
|
if (json) io.out(JSON.stringify({ command: `control ${sub}`, ...envelope }));
|
|
6115
5729
|
else for (const l of lines2) io.out(l);
|
|
@@ -6157,8 +5771,8 @@ async function controlInit(chant, args, emit, table, io) {
|
|
|
6157
5771
|
const before = existsSync11(path) ? readFileSync13(path, "utf8") : void 0;
|
|
6158
5772
|
const status = before === file.content ? "unchanged" : "written";
|
|
6159
5773
|
if (!dryRun && status === "written") {
|
|
6160
|
-
const { mkdirSync:
|
|
6161
|
-
|
|
5774
|
+
const { mkdirSync: mkdirSync7 } = await import("node:fs");
|
|
5775
|
+
mkdirSync7(dirname10(path), { recursive: true });
|
|
6162
5776
|
writeFileSync7(path, file.content);
|
|
6163
5777
|
}
|
|
6164
5778
|
emit({ ok: true, exitCode: table.done, forge, file: file.path, status, dryRun, notes: file.notes }, [
|
|
@@ -6293,10 +5907,11 @@ async function controlReconcile(chant, args, emit, table, io) {
|
|
|
6293
5907
|
emit({ ok: !failed, exitCode: code, mode, outcomes }, chant.describeReconcile(outcomes, mode).split("\n"));
|
|
6294
5908
|
return code;
|
|
6295
5909
|
}
|
|
6296
|
-
var CONTROLLED_KEYS,
|
|
5910
|
+
var CONTROLLED_KEYS, WAVES_CONTROL_KEYS, cached5, validateControlSettings;
|
|
6297
5911
|
var init_control2 = __esm({
|
|
6298
5912
|
"src/control.ts"() {
|
|
6299
5913
|
"use strict";
|
|
5914
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
6300
5915
|
init_config();
|
|
6301
5916
|
init_control();
|
|
6302
5917
|
init_routing();
|
|
@@ -6305,9 +5920,7 @@ var init_control2 = __esm({
|
|
|
6305
5920
|
init_stages();
|
|
6306
5921
|
init_words();
|
|
6307
5922
|
CONTROLLED_KEYS = ["gate", "approval", "approval_rules", "pulumi", "plunge", "image"];
|
|
6308
|
-
CONTROL_NEEDS_CHANT = "the control repo needs chant >= the release after 0.124.54, which carries @intentius/chant/ci-control (chant#4568)";
|
|
6309
5923
|
WAVES_CONTROL_KEYS = ["repo", "op", "forge", "member", "tokenSecret", "readTokenSecret", "ref", "apiBase"];
|
|
6310
|
-
REQUIRED3 = ["controlDispatchRequest", "putControlEntry", "reconcileProjects"];
|
|
6311
5924
|
validateControlSettings = (_entry, _project, settings) => {
|
|
6312
5925
|
const out = [];
|
|
6313
5926
|
for (const key of Object.keys(settings)) {
|
|
@@ -6341,13 +5954,8 @@ function positionals(args) {
|
|
|
6341
5954
|
}
|
|
6342
5955
|
return out;
|
|
6343
5956
|
}
|
|
6344
|
-
async function withChant(
|
|
6345
|
-
|
|
6346
|
-
if (!chant) {
|
|
6347
|
-
io.err(`${PRODUCT}: ${needsChantMessage(what)}`);
|
|
6348
|
-
return table.failed;
|
|
6349
|
-
}
|
|
6350
|
-
return run(chant);
|
|
5957
|
+
async function withChant(_what, _table, _io, run) {
|
|
5958
|
+
return run(await chantProduct());
|
|
6351
5959
|
}
|
|
6352
5960
|
function passthrough(argv, table, io) {
|
|
6353
5961
|
if (argv[0] === "run" && argv[1] === "wave") return runWave(argv, table, io);
|
|
@@ -6410,7 +6018,6 @@ async function preview(args, json, table, io) {
|
|
|
6410
6018
|
const gate = rest.includes("--gate") ? [] : ["--gate", PR_GATE];
|
|
6411
6019
|
const argv = ["components", "pr-plan", "--base", at, "--pr", number, ...gate, ...stack ? ["--member", stack] : [], ...rest, ...json ? ["--json"] : []];
|
|
6412
6020
|
return withChant(`${PRODUCT} preview`, table, io, async (chant) => {
|
|
6413
|
-
if (targeted && !hasChant4620(chant)) return usage(PREVIEW_TARGET_NEEDS_CHANT, table, io);
|
|
6414
6021
|
return chant.runChantInProcess(argv, PRODUCT_WORDS);
|
|
6415
6022
|
});
|
|
6416
6023
|
}
|
|
@@ -6449,12 +6056,6 @@ async function ci(args, json, table, io) {
|
|
|
6449
6056
|
const env = envFlag.value ?? "production";
|
|
6450
6057
|
const output = outFlag.value ?? WORKFLOW_FILE[forge];
|
|
6451
6058
|
return withChant(`${PRODUCT} ci`, table, io, async (chant) => {
|
|
6452
|
-
const reviewStacks2 = await chantReviewStacks();
|
|
6453
|
-
if (config2.doc.review_stacks !== void 0 && !reviewStacks2) io.err(`${PRODUCT}: ${REVIEW_STACKS_NEED_CHANT}`);
|
|
6454
|
-
if (applyWhenOf(config2.doc) === "pull-request") {
|
|
6455
|
-
const note = requiresNotHonoured(chant, config2.doc);
|
|
6456
|
-
if (note) io.err(`${PRODUCT}: ${note}`);
|
|
6457
|
-
}
|
|
6458
6059
|
let waves;
|
|
6459
6060
|
try {
|
|
6460
6061
|
waves = readWavesSpec(io.cwd);
|
|
@@ -6462,12 +6063,12 @@ async function ci(args, json, table, io) {
|
|
|
6462
6063
|
return usage(`.chant/op-waves/${OP_NAME}.spec.json: ${e.message}`, table, io);
|
|
6463
6064
|
}
|
|
6464
6065
|
const pipeline = toChantSettings(config2).pipeline;
|
|
6465
|
-
const { prJobsFor: prJobsFor2,
|
|
6066
|
+
const { prJobsFor: prJobsFor2, withJobSettings: withJobSettings2 } = await Promise.resolve().then(() => (init_jobs2(), jobs_exports));
|
|
6466
6067
|
const prJobs = await prJobsFor2(pipeline);
|
|
6467
6068
|
for (const note of prJobs.notes) io.err(`${PRODUCT}: ${note}`);
|
|
6468
6069
|
if (waves !== void 0 && pipeline.jobs) {
|
|
6469
6070
|
try {
|
|
6470
|
-
waves = withJobSettings2(waves, pipeline.jobs, forge
|
|
6071
|
+
waves = withJobSettings2(waves, pipeline.jobs, forge);
|
|
6471
6072
|
} catch (e) {
|
|
6472
6073
|
io.err(`${PRODUCT}: ${e.message}`);
|
|
6473
6074
|
return table.failed;
|
|
@@ -6477,7 +6078,8 @@ async function ci(args, json, table, io) {
|
|
|
6477
6078
|
const noteControl = await noteControlTarget2(config2.doc, io.cwd);
|
|
6478
6079
|
let plan;
|
|
6479
6080
|
try {
|
|
6480
|
-
|
|
6081
|
+
const engine3 = chantBuildFlags(chantPipelineOptions(pipeline));
|
|
6082
|
+
plan = workflowPlan(chant, config2.doc, forge, env, waves, engine3, { prJobs, ...noteControl ? { control: noteControl } : {} });
|
|
6481
6083
|
} catch (e) {
|
|
6482
6084
|
io.err(`${PRODUCT}: ${e.message}`);
|
|
6483
6085
|
return table.failed;
|
|
@@ -6518,9 +6120,8 @@ async function ci(args, json, table, io) {
|
|
|
6518
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})`);
|
|
6519
6121
|
}
|
|
6520
6122
|
if (!json && "control" in control3) io.err(` control after the last wave, tells ${control3.repo} how the waves ended`);
|
|
6521
|
-
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'`);
|
|
6522
6123
|
if (dryRun) return table.done;
|
|
6523
|
-
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 });
|
|
6524
6125
|
if (!json && written.kept.length) io.err(` kept your own jobs: ${written.kept.join(", ")}`);
|
|
6525
6126
|
if (written.code === table.done) for (const note of writtenNotes(plan, written.file)) io.err(`${PRODUCT}: ${note}`);
|
|
6526
6127
|
if (json) io.out(JSON.stringify({ command: "ci", ok: written.code === 0, exitCode: written.code, dryRun, ...summary, status: written.status, kept: written.kept }));
|
|
@@ -6545,21 +6146,20 @@ function workflowPlan(chant, doc, forge, env, waves, extra = [], opts = {}) {
|
|
|
6545
6146
|
const shape = pipelineShape(doc, forge, waves);
|
|
6546
6147
|
const notes = [];
|
|
6547
6148
|
const pipeline = toChantSettings({ doc }).pipeline;
|
|
6548
|
-
const respond = respondOptions(
|
|
6149
|
+
const respond = respondOptions(pipeline, notes);
|
|
6549
6150
|
const applyMerge = shape.applyBeforeMerge ? pipeline.applyMerge : void 0;
|
|
6550
|
-
if (approvalOf(doc) === "pr-review" && shape.waves && hasChantApprovalStatus(chant) && !hasChantPullRequestApprovalStatus(chant)) notes.push(APPROVAL_STATUS_NOTE);
|
|
6551
6151
|
const actionJob = actionJobFor(doc, forge, notes);
|
|
6552
6152
|
const jobsWanted = actionJob ? [...shape.jobs, "action"] : shape.jobs;
|
|
6553
6153
|
const words = wordsForJobs(jobsWanted);
|
|
6554
6154
|
const requires = shape.applyBeforeMerge ? requiresArgv(chant, doc) : [];
|
|
6555
|
-
const previewComment = hasChant4620(chant);
|
|
6556
6155
|
const jobs2 = chant.productPipelineJobs(forge, env, {
|
|
6557
6156
|
words,
|
|
6558
6157
|
...shape.applyBeforeMerge ? { applyBeforeMerge: true } : {},
|
|
6559
6158
|
...shape.locks ? { locks: shape.locks } : {},
|
|
6560
6159
|
...shape.waves ? { waves } : {},
|
|
6561
6160
|
...requires.length ? { applyRequires: requires[1].split(",") } : {},
|
|
6562
|
-
|
|
6161
|
+
// chant#4620: `/plywood preview` previews the pull request again.
|
|
6162
|
+
previewComment: true,
|
|
6563
6163
|
// chant#4619: the note job's read of the control repo comes back as post.control.
|
|
6564
6164
|
...opts.control !== void 0 ? { control: { target: opts.control } } : {},
|
|
6565
6165
|
// plywood#29: runners, env, pass and parallelism on the pull request jobs, with chant#4625 (src/jobs.ts).
|
|
@@ -6585,12 +6185,14 @@ function workflowPlan(chant, doc, forge, env, waves, extra = [], opts = {}) {
|
|
|
6585
6185
|
...shape.applyBeforeMerge ? ["--apply-before-merge", ...requires] : [],
|
|
6586
6186
|
...applyMerge ? applyMergeArgv(applyMerge) : [],
|
|
6587
6187
|
...respond.argv,
|
|
6588
|
-
|
|
6188
|
+
"--preview-comment",
|
|
6589
6189
|
"--stale-notes",
|
|
6590
6190
|
...actionJob ? ["--action-job"] : [],
|
|
6591
|
-
// Review stacks (plywood#15): chant's pr-env jobs
|
|
6592
|
-
...doc.review_stacks !== void 0
|
|
6191
|
+
// Review stacks (plywood#15): chant's pr-env jobs.
|
|
6192
|
+
...doc.review_stacks !== void 0 ? ["--pr-env"] : [],
|
|
6593
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) : [],
|
|
6594
6196
|
...extra
|
|
6595
6197
|
];
|
|
6596
6198
|
return { jobs: jobs2, words, prWords, argv, notes, ...applyMerge ? { applyMerge } : {} };
|
|
@@ -6603,15 +6205,11 @@ function actionJobFor(doc, forge, notes) {
|
|
|
6603
6205
|
function applyMergeArgv(m) {
|
|
6604
6206
|
return ["--merge", "auto", "--merge-token-env", m.tokenEnv, ...m.method ? ["--merge-method", m.method] : []];
|
|
6605
6207
|
}
|
|
6606
|
-
function respondOptions(
|
|
6208
|
+
function respondOptions(pipeline, notes) {
|
|
6607
6209
|
const r = pipeline.respond;
|
|
6608
6210
|
if (pipeline.decide && r?.description !== "check") notes.push(DECIDE_LEFT_OFF);
|
|
6609
6211
|
if (!r || Object.keys(r).length === 0) return { options: {}, argv: [] };
|
|
6610
6212
|
const keys = Object.keys(r).map((e) => e.replace("-", "_"));
|
|
6611
|
-
if (!hasChantRespond(chant)) {
|
|
6612
|
-
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`);
|
|
6613
|
-
return { options: {}, argv: [] };
|
|
6614
|
-
}
|
|
6615
6213
|
const responses = { ...r };
|
|
6616
6214
|
const check2 = responses.description === "check";
|
|
6617
6215
|
const decide = check2 ? pipeline.decide : void 0;
|
|
@@ -6620,14 +6218,7 @@ function respondOptions(chant, pipeline, applyBeforeMerge, notes) {
|
|
|
6620
6218
|
notes.push("respond.description is check and decide names no backend and model, so the description check is left off");
|
|
6621
6219
|
}
|
|
6622
6220
|
const options = { respond: responses, ...decide ? { decide: { ...decide } } : {} };
|
|
6623
|
-
|
|
6624
|
-
const prSide = [
|
|
6625
|
-
...responses.description === "check" ? ["respond.description"] : [],
|
|
6626
|
-
...applyBeforeMerge && responses["apply-failed"] === "off" ? ["respond.apply_failed"] : [],
|
|
6627
|
-
...applyBeforeMerge && responses["wave-refused"] === "off" ? ["respond.wave_refused"] : []
|
|
6628
|
-
];
|
|
6629
|
-
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`);
|
|
6630
|
-
return { options, argv: [] };
|
|
6221
|
+
return { options, argv: respondArgv(responses, decide) };
|
|
6631
6222
|
}
|
|
6632
6223
|
function respondArgv(responses, decide) {
|
|
6633
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(",");
|
|
@@ -6636,35 +6227,30 @@ function respondArgv(responses, decide) {
|
|
|
6636
6227
|
}
|
|
6637
6228
|
function requiresArgv(chant, doc) {
|
|
6638
6229
|
const listed = applyRequiresOf(doc);
|
|
6639
|
-
if (listed.length === APPLY_REQUIRES.length
|
|
6230
|
+
if (listed.length === APPLY_REQUIRES.length) return [];
|
|
6640
6231
|
const parsed = chant.parseRequiresList(listed);
|
|
6641
6232
|
return "checks" in parsed ? ["--requires", parsed.checks.join(",")] : [];
|
|
6642
6233
|
}
|
|
6643
|
-
function requiresNotHonoured(chant, doc) {
|
|
6644
|
-
const listed = applyRequiresOf(doc);
|
|
6645
|
-
if (listed.length === APPLY_REQUIRES.length || hasChant4607(chant)) return void 0;
|
|
6646
|
-
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(", ")}`;
|
|
6647
|
-
}
|
|
6648
6234
|
function usage(message, table, io) {
|
|
6649
6235
|
io.err(`${PRODUCT}: ${message}`);
|
|
6650
6236
|
return table.failed;
|
|
6651
6237
|
}
|
|
6652
|
-
var CHANT_PASSTHROUGH, VALUED,
|
|
6238
|
+
var CHANT_PASSTHROUGH, VALUED, WORKFLOW_FILE, ACTIONS_NOT_ON_GITLAB, CHANT_RESPOND_DEFAULTS, DECIDE_LEFT_OFF;
|
|
6653
6239
|
var init_stages = __esm({
|
|
6654
6240
|
"src/stages.ts"() {
|
|
6655
6241
|
"use strict";
|
|
6242
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
6656
6243
|
init_chant_product();
|
|
6657
6244
|
init_chant_config();
|
|
6658
|
-
init_chant_next();
|
|
6659
|
-
init_approval();
|
|
6660
6245
|
init_config_check();
|
|
6661
6246
|
init_apply();
|
|
6662
6247
|
init_config();
|
|
6248
|
+
init_control();
|
|
6663
6249
|
init_locks();
|
|
6664
6250
|
init_stacks();
|
|
6665
6251
|
init_routing();
|
|
6252
|
+
init_images();
|
|
6666
6253
|
init_workflow();
|
|
6667
|
-
init_review_stacks2();
|
|
6668
6254
|
init_waves_workflow();
|
|
6669
6255
|
init_drift2();
|
|
6670
6256
|
init_refusals();
|
|
@@ -6673,20 +6259,22 @@ var init_stages = __esm({
|
|
|
6673
6259
|
init_words();
|
|
6674
6260
|
CHANT_PASSTHROUGH = ["components", "run", "lock", "unlock", "query", "ledger", "audit-log"];
|
|
6675
6261
|
VALUED = /* @__PURE__ */ new Set(["--base", "--pr", "--forge", "--env", "--output", "-o", "--stack", "--file", "--head", "--gate", "--branch", "--report-url", "--target"]);
|
|
6676
|
-
PREVIEW_TARGET_NEEDS_CHANT = "--target needs a chant whose pr-plan passes it to the pulumi-up step (chant#4620), which this chant lacks";
|
|
6677
6262
|
WORKFLOW_FILE = {
|
|
6678
6263
|
github: ".github/workflows/plywood.yml",
|
|
6679
6264
|
forgejo: ".forgejo/workflows/plywood.yml",
|
|
6680
6265
|
gitlab: ".gitlab-ci.yml"
|
|
6681
6266
|
};
|
|
6682
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";
|
|
6683
|
-
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`;
|
|
6684
6268
|
CHANT_RESPOND_DEFAULTS = Object.freeze({ "wave-refused": "diff", "apply-failed": "triage", description: "off" });
|
|
6685
6269
|
DECIDE_LEFT_OFF = "decide is set and respond.description is not check, so plywood leaves the decide service off the pull request jobs";
|
|
6686
6270
|
}
|
|
6687
6271
|
});
|
|
6688
6272
|
|
|
6273
|
+
// src/bin.ts
|
|
6274
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
6275
|
+
|
|
6689
6276
|
// src/chant-resolve.ts
|
|
6277
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
6690
6278
|
import * as nodeModule from "node:module";
|
|
6691
6279
|
var CHANT_SPECIFIER = /^@intentius\/chant(?:-lexicon-[a-z0-9-]+)?(?:\/|$)/;
|
|
6692
6280
|
function chantResolveHook(from) {
|
|
@@ -6712,50 +6300,22 @@ function installChantResolution(from = import.meta.url) {
|
|
|
6712
6300
|
}
|
|
6713
6301
|
|
|
6714
6302
|
// src/cli.ts
|
|
6303
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
6715
6304
|
init_config_check();
|
|
6716
6305
|
|
|
6717
6306
|
// src/exit-codes.ts
|
|
6718
|
-
|
|
6719
|
-
|
|
6720
|
-
|
|
6721
|
-
drift: 2,
|
|
6722
|
-
waiting: 3,
|
|
6723
|
-
moved: 4,
|
|
6724
|
-
held: 5,
|
|
6725
|
-
superseded: 6,
|
|
6726
|
-
denied: 7,
|
|
6727
|
-
declined: 8,
|
|
6728
|
-
misplaced: 9
|
|
6729
|
-
});
|
|
6730
|
-
var NAMES = Object.keys(FALLBACK_EXIT).filter((n) => n !== "misplaced");
|
|
6731
|
-
function isExitTable(t) {
|
|
6732
|
-
if (!t || typeof t !== "object") return false;
|
|
6733
|
-
const r = t;
|
|
6734
|
-
return NAMES.every((n) => Number.isInteger(r[n])) && (r.misplaced === void 0 || Number.isInteger(r.misplaced));
|
|
6735
|
-
}
|
|
6736
|
-
function withMisplaced(t) {
|
|
6737
|
-
return Number.isInteger(t.misplaced) ? t : Object.freeze({ ...t, misplaced: FALLBACK_EXIT.misplaced });
|
|
6738
|
-
}
|
|
6739
|
-
var cached;
|
|
6740
|
-
function exitCodes() {
|
|
6741
|
-
cached ??= (async () => {
|
|
6742
|
-
try {
|
|
6743
|
-
const m = await import("@intentius/chant/exit-codes");
|
|
6744
|
-
const table = m.CHANT_EXIT;
|
|
6745
|
-
if (isExitTable(table)) return { table: withMisplaced(table), source: "chant" };
|
|
6746
|
-
} catch {
|
|
6747
|
-
}
|
|
6748
|
-
return { table: FALLBACK_EXIT, source: "fallback" };
|
|
6749
|
-
})();
|
|
6750
|
-
return cached;
|
|
6751
|
-
}
|
|
6307
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
6308
|
+
import { CHANT_EXIT } from "@intentius/chant/exit-codes";
|
|
6309
|
+
var EXIT = CHANT_EXIT;
|
|
6752
6310
|
|
|
6753
6311
|
// src/actions.ts
|
|
6312
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
6754
6313
|
init_chant_product();
|
|
6755
6314
|
init_stacks();
|
|
6756
6315
|
init_stages();
|
|
6757
6316
|
|
|
6758
6317
|
// src/stack-ops.ts
|
|
6318
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
6759
6319
|
init_chant_config();
|
|
6760
6320
|
init_identity();
|
|
6761
6321
|
init_config();
|
|
@@ -6764,28 +6324,18 @@ init_adopt();
|
|
|
6764
6324
|
init_detect();
|
|
6765
6325
|
init_refusals();
|
|
6766
6326
|
init_stages();
|
|
6767
|
-
init_chant_product();
|
|
6768
6327
|
init_words();
|
|
6769
6328
|
import { existsSync as existsSync13 } from "node:fs";
|
|
6770
6329
|
import { dirname as dirname11, isAbsolute as isAbsolute2, join as join16, resolve as resolve2 } from "node:path";
|
|
6771
6330
|
var STACK_OPS_OP = "pulumi-stack-ops";
|
|
6772
|
-
var REQUIRED4 = ["gatedStackExport", "gatedStackCancel", "gatedStackMove"];
|
|
6773
6331
|
var cachedLocks;
|
|
6774
6332
|
function chantStackLockReader() {
|
|
6775
|
-
cachedLocks ??= (
|
|
6776
|
-
try {
|
|
6777
|
-
const m = await import("@intentius/chant-lexicon-pulumi");
|
|
6778
|
-
return typeof m.pulumiStackLockIds === "function" ? m.pulumiStackLockIds : void 0;
|
|
6779
|
-
} catch {
|
|
6780
|
-
return void 0;
|
|
6781
|
-
}
|
|
6782
|
-
})();
|
|
6333
|
+
cachedLocks ??= import("@intentius/chant-lexicon-pulumi").then((m) => m.pulumiStackLockIds);
|
|
6783
6334
|
return cachedLocks;
|
|
6784
6335
|
}
|
|
6785
6336
|
async function lockToCancel(given, stack, env) {
|
|
6786
6337
|
if (given) return { lockId: given, read: false };
|
|
6787
6338
|
const reader = await chantStackLockReader();
|
|
6788
|
-
if (!reader) return { error: STACK_OP_WORDS.needsLock };
|
|
6789
6339
|
let locks2;
|
|
6790
6340
|
try {
|
|
6791
6341
|
locks2 = await reader({ dir: stack.dir, stack: stack.stack, ...stack.project ? { project: stack.project } : {}, ...stack.engine ? { engine: stack.engine } : {}, env });
|
|
@@ -6798,34 +6348,10 @@ async function lockToCancel(given, stack, env) {
|
|
|
6798
6348
|
return { lockId: locks2.ids[0], read: true };
|
|
6799
6349
|
}
|
|
6800
6350
|
var cachedOps;
|
|
6801
|
-
var cachedRollback;
|
|
6802
6351
|
function chantStackOps() {
|
|
6803
|
-
cachedOps ??= (
|
|
6804
|
-
try {
|
|
6805
|
-
const m = await import("@intentius/chant-lexicon-pulumi");
|
|
6806
|
-
return REQUIRED4.every((name) => typeof m[name] === "function") ? m : void 0;
|
|
6807
|
-
} catch {
|
|
6808
|
-
return void 0;
|
|
6809
|
-
}
|
|
6810
|
-
})();
|
|
6352
|
+
cachedOps ??= import("@intentius/chant-lexicon-pulumi").then((m) => m);
|
|
6811
6353
|
return cachedOps;
|
|
6812
6354
|
}
|
|
6813
|
-
function chantStackRollback() {
|
|
6814
|
-
cachedRollback ??= (async () => {
|
|
6815
|
-
try {
|
|
6816
|
-
const m = await import("@intentius/chant-lexicon-pulumi");
|
|
6817
|
-
if (typeof m.pulumiReleaseInputs !== "function") return false;
|
|
6818
|
-
const copy2 = await import("@intentius/chant-lexicon-pulumi/release-copy");
|
|
6819
|
-
return typeof copy2.prepareReleaseCopy === "function";
|
|
6820
|
-
} catch {
|
|
6821
|
-
return false;
|
|
6822
|
-
}
|
|
6823
|
-
})();
|
|
6824
|
-
return cachedRollback;
|
|
6825
|
-
}
|
|
6826
|
-
function needsStackOpsMessage(what, feature, after = "0.124.54") {
|
|
6827
|
-
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'.`;
|
|
6828
|
-
}
|
|
6829
6355
|
function readConfig(cwd) {
|
|
6830
6356
|
const path = findConfig(cwd);
|
|
6831
6357
|
if (!path) return { error: `no plywood.yml in ${cwd} or above it; run '${PRODUCT} init' first` };
|
|
@@ -6921,10 +6447,6 @@ function reportOutcome(verb, outcome, gate, rules, done, json, table, io) {
|
|
|
6921
6447
|
}
|
|
6922
6448
|
async function runRequest(verb, json, table, io, call) {
|
|
6923
6449
|
const ops = await chantStackOps();
|
|
6924
|
-
if (!ops) {
|
|
6925
|
-
io.err(`${PRODUCT}: ${needsStackOpsMessage(`${PRODUCT} ${verb}`, "the gated stack operations (chant#4578)")}`);
|
|
6926
|
-
return table.failed;
|
|
6927
|
-
}
|
|
6928
6450
|
try {
|
|
6929
6451
|
return await call(ops);
|
|
6930
6452
|
} catch (e) {
|
|
@@ -7035,7 +6557,7 @@ async function moveStack(args, json, table, io) {
|
|
|
7035
6557
|
if (backend) return usage(backend, table, io);
|
|
7036
6558
|
const rules = requestRules(read.config);
|
|
7037
6559
|
const destName = destStackName(source, dest, read.config);
|
|
7038
|
-
const
|
|
6560
|
+
const engine3 = source.engine ?? dest.engine;
|
|
7039
6561
|
return runRequest("move", json, table, io, async (ops) => {
|
|
7040
6562
|
const outcome = await ops.gatedStackMove({
|
|
7041
6563
|
by: requesterOf(common.by, io.env),
|
|
@@ -7046,13 +6568,13 @@ async function moveStack(args, json, table, io) {
|
|
|
7046
6568
|
source: { member: source.key, dir: source.dir, stack: source.stack },
|
|
7047
6569
|
dest: { member: dest.key, dir: dest.dir, stack: destName },
|
|
7048
6570
|
urns,
|
|
7049
|
-
...
|
|
6571
|
+
...engine3 ? { engine: engine3 } : {}
|
|
7050
6572
|
});
|
|
7051
6573
|
return reportOutcome("move", outcome, `move:${source.stack}:${destName}`, rules, (r) => STACK_OP_WORDS.moved(source.key, dest.key, r), json, table, io);
|
|
7052
6574
|
});
|
|
7053
6575
|
}
|
|
7054
|
-
async function moveOntoPlunge(
|
|
7055
|
-
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);
|
|
7056
6578
|
const extra = args.filter((a) => a.startsWith("-"));
|
|
7057
6579
|
if (extra.length) return usage(`move --onto does not take ${extra.join(" ")}`, table, io);
|
|
7058
6580
|
const named = positionals(args);
|
|
@@ -7102,10 +6624,6 @@ async function rollbackStack(args, json, table, io) {
|
|
|
7102
6624
|
argv = ["components", "rollback", env.value ?? environmentOf(stack.stack), "--component", slug(stack.key), ...digest.value ? ["--digest", digest.value] : []];
|
|
7103
6625
|
}
|
|
7104
6626
|
argv.push(...actor.value ? ["--actor", actor.value] : [], ...switches, ...json ? ["--json"] : []);
|
|
7105
|
-
if (!await chantStackRollback()) {
|
|
7106
|
-
io.err(`${PRODUCT}: ${needsStackOpsMessage(`${PRODUCT} rollback`, "rollback by release for stacks (chant#4542)")}`);
|
|
7107
|
-
return table.failed;
|
|
7108
|
-
}
|
|
7109
6627
|
return withChant(`${PRODUCT} rollback`, table, io, async (chant) => {
|
|
7110
6628
|
const code = await chant.runChantInProcess(argv, PRODUCT_WORDS);
|
|
7111
6629
|
if (code === table.waiting && !json) io.err(STACK_OP_WORDS.rollbackWaiting);
|
|
@@ -7123,7 +6641,6 @@ function chantStackAction() {
|
|
|
7123
6641
|
}
|
|
7124
6642
|
async function fromEvent(env) {
|
|
7125
6643
|
const chant = await chantProduct();
|
|
7126
|
-
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` };
|
|
7127
6644
|
const words = chant.assertProductWords({ ...PRODUCT_WORDS, commands: { ...PRODUCT_WORDS.commands, ...ACTION_COMMAND } });
|
|
7128
6645
|
const asked = chant.productActionFromEnv(words, env);
|
|
7129
6646
|
if (asked.kind === "refused") return { error: `the action job was asked for no action: ${asked.reason}` };
|
|
@@ -7194,6 +6711,7 @@ async function action(args, json, table, io) {
|
|
|
7194
6711
|
}
|
|
7195
6712
|
|
|
7196
6713
|
// src/audit.ts
|
|
6714
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
7197
6715
|
init_detect();
|
|
7198
6716
|
init_stages();
|
|
7199
6717
|
init_words();
|
|
@@ -7252,6 +6770,7 @@ init_agents2();
|
|
|
7252
6770
|
init_drift2();
|
|
7253
6771
|
|
|
7254
6772
|
// src/estate.ts
|
|
6773
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
7255
6774
|
init_chant_product();
|
|
7256
6775
|
init_chant_config();
|
|
7257
6776
|
init_config();
|
|
@@ -7261,19 +6780,12 @@ import { dirname as dirname12 } from "node:path";
|
|
|
7261
6780
|
var estateModule;
|
|
7262
6781
|
function chantEstate() {
|
|
7263
6782
|
estateModule ??= (async () => {
|
|
7264
|
-
|
|
7265
|
-
|
|
7266
|
-
|
|
7267
|
-
if (!["readEstateLedger", "buildLedgerEstate", "writeEstate"].every((f) => typeof m[f] === "function")) return void 0;
|
|
7268
|
-
if (typeof r.parseReportUrl !== "function" || typeof r.reportPut !== "function") return void 0;
|
|
7269
|
-
return { ...m, parseReportUrl: r.parseReportUrl, reportPut: r.reportPut };
|
|
7270
|
-
} catch {
|
|
7271
|
-
return void 0;
|
|
7272
|
-
}
|
|
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 };
|
|
7273
6786
|
})();
|
|
7274
6787
|
return estateModule;
|
|
7275
6788
|
}
|
|
7276
|
-
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.`;
|
|
7277
6789
|
function estateSchedules(config2) {
|
|
7278
6790
|
const drift3 = toChantSettings(config2).pipeline.drift;
|
|
7279
6791
|
const driftOps = (drift3?.stacks ?? []).map(driftOpName);
|
|
@@ -7292,11 +6804,6 @@ async function estate(args, json, table, io) {
|
|
|
7292
6804
|
}
|
|
7293
6805
|
if (rest.length) return usage(`estate does not take ${rest.join(" ")}`, table, io);
|
|
7294
6806
|
const chant = await chantEstate();
|
|
7295
|
-
if (!chant) {
|
|
7296
|
-
if (json) io.out(JSON.stringify({ command: "estate", ok: false, exitCode: table.failed, error: ESTATE_NEEDS_CHANT }));
|
|
7297
|
-
else io.err(`${PRODUCT}: ${ESTATE_NEEDS_CHANT}`);
|
|
7298
|
-
return table.failed;
|
|
7299
|
-
}
|
|
7300
6807
|
const path = flags["--file"] ?? findConfig(io.cwd);
|
|
7301
6808
|
let config2;
|
|
7302
6809
|
if (path) {
|
|
@@ -7312,8 +6819,7 @@ async function estate(args, json, table, io) {
|
|
|
7312
6819
|
}
|
|
7313
6820
|
}
|
|
7314
6821
|
const { schedules, driftOps } = config2 ? estateSchedules(config2) : { schedules: [], driftOps: [] };
|
|
7315
|
-
const
|
|
7316
|
-
const words = product ? product.assertProductWords(PRODUCT_WORDS) : PRODUCT_WORDS;
|
|
6822
|
+
const words = (await chantProduct()).assertProductWords(PRODUCT_WORDS);
|
|
7317
6823
|
const root = path ? dirname12(path) : io.cwd;
|
|
7318
6824
|
const load = await chant.readEstateLedger(root);
|
|
7319
6825
|
const built = chant.buildLedgerEstate({ load, words, schedules, ...driftOps.length ? { driftOps } : {} });
|
|
@@ -7338,79 +6844,37 @@ async function estate(args, json, table, io) {
|
|
|
7338
6844
|
}
|
|
7339
6845
|
|
|
7340
6846
|
// src/notify.ts
|
|
6847
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
7341
6848
|
init_stages();
|
|
7342
6849
|
init_words();
|
|
7343
|
-
var RELAY_NEEDS_CHANT = `${PRODUCT} notify relay needs chant >= the release after 0.124.54, which carries chant notify relay (chant#4611).`;
|
|
7344
6850
|
async function notify(args, json, table, io) {
|
|
7345
6851
|
if (args[0] !== "relay") return usage(`unknown notify command '${args[0] ?? ""}'. Run '${PRODUCT} notify relay'.`, table, io);
|
|
7346
|
-
return withChant(`${PRODUCT} notify relay`, table, io, (chant) =>
|
|
7347
|
-
if (typeof chant.startRelayServer !== "function") {
|
|
7348
|
-
if (json) io.out(JSON.stringify({ command: "notify relay", ok: false, exitCode: table.failed, error: RELAY_NEEDS_CHANT }));
|
|
7349
|
-
else io.err(`${PRODUCT}: ${RELAY_NEEDS_CHANT}`);
|
|
7350
|
-
return Promise.resolve(table.failed);
|
|
7351
|
-
}
|
|
7352
|
-
return chant.runChantInProcess(["notify", ...args], PRODUCT_WORDS);
|
|
7353
|
-
});
|
|
6852
|
+
return withChant(`${PRODUCT} notify relay`, table, io, (chant) => chant.runChantInProcess(["notify", ...args], PRODUCT_WORDS));
|
|
7354
6853
|
}
|
|
7355
6854
|
|
|
7356
|
-
// src/cli.ts
|
|
7357
|
-
init_policy();
|
|
7358
|
-
|
|
7359
6855
|
// src/guard.ts
|
|
6856
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
7360
6857
|
init_stages();
|
|
7361
6858
|
init_words();
|
|
7362
|
-
import { chmodSync, copyFileSync, existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "node:fs";
|
|
7363
6859
|
import { join as join17 } from "node:path";
|
|
7364
|
-
|
|
7365
|
-
|
|
7366
|
-
|
|
7367
|
-
};
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
7372
|
-
}
|
|
7373
|
-
function guardVerdict(line) {
|
|
7374
|
-
for (const words of segments(line)) {
|
|
7375
|
-
let i = 0;
|
|
7376
|
-
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++;
|
|
7377
|
-
const bin = (words[i] ?? "").split("/").pop();
|
|
7378
|
-
const refused = GUARDED[bin];
|
|
7379
|
-
if (!refused) continue;
|
|
7380
|
-
for (let j = i + 1; j < words.length; j++) {
|
|
7381
|
-
const w = words[j];
|
|
7382
|
-
if (w.startsWith("-")) {
|
|
7383
|
-
if (VALUED_GLOBALS.has(w)) j++;
|
|
7384
|
-
continue;
|
|
7385
|
-
}
|
|
7386
|
-
if (refused.includes(w)) {
|
|
7387
|
-
return {
|
|
7388
|
-
engine: bin,
|
|
7389
|
-
subcommand: w,
|
|
7390
|
-
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.`
|
|
7391
|
-
};
|
|
7392
|
-
}
|
|
7393
|
-
break;
|
|
7394
|
-
}
|
|
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
|
|
7395
6868
|
}
|
|
7396
|
-
|
|
7397
|
-
}
|
|
7398
|
-
var GUARD_TEMPLATES = new URL("../templates/agent-guard/", import.meta.url);
|
|
7399
|
-
var HOOK_COMMAND = 'node "$CLAUDE_PROJECT_DIR/.claude/hooks/plywood-guard.mjs"';
|
|
7400
|
-
function withGuardHook(settings) {
|
|
7401
|
-
const hooks = typeof settings.hooks === "object" && settings.hooks !== null ? settings.hooks : {};
|
|
7402
|
-
const pre = Array.isArray(hooks.PreToolUse) ? [...hooks.PreToolUse] : [];
|
|
7403
|
-
const has = JSON.stringify(pre).includes("plywood-guard.mjs");
|
|
7404
|
-
if (!has) pre.push({ matcher: "Bash", hooks: [{ type: "command", command: HOOK_COMMAND }] });
|
|
7405
|
-
return { ...settings, hooks: { ...hooks, PreToolUse: pre } };
|
|
7406
|
-
}
|
|
6869
|
+
};
|
|
7407
6870
|
async function guard(args, json, table, io) {
|
|
7408
6871
|
const [sub, ...rest] = args;
|
|
7409
6872
|
if (sub === "check") {
|
|
7410
6873
|
const line = (rest[0] === "--" ? rest.slice(1) : rest).join(" ");
|
|
7411
6874
|
if (!line.trim()) return usage("guard check takes the command line to check", table, io);
|
|
7412
|
-
const v =
|
|
7413
|
-
|
|
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 }));
|
|
7414
6878
|
else io.out(v ? v.message : `${PRODUCT} guard: '${line}' runs`);
|
|
7415
6879
|
return v ? table.denied : table.done;
|
|
7416
6880
|
}
|
|
@@ -7419,34 +6883,16 @@ async function guard(args, json, table, io) {
|
|
|
7419
6883
|
if (dirFlag.missing) return usage("--dir takes a directory", table, io);
|
|
7420
6884
|
if (dirFlag.rest.length) return usage(`guard install does not take ${dirFlag.rest.join(" ")}`, table, io);
|
|
7421
6885
|
const root = dirFlag.value ? join17(io.cwd, dirFlag.value) : io.cwd;
|
|
7422
|
-
|
|
7423
|
-
|
|
7424
|
-
|
|
7425
|
-
|
|
7426
|
-
|
|
7427
|
-
const settingsFile = join17(root, ".claude/settings.json");
|
|
7428
|
-
let settings = {};
|
|
7429
|
-
if (existsSync14(settingsFile)) {
|
|
7430
|
-
try {
|
|
7431
|
-
settings = JSON.parse(readFileSync15(settingsFile, "utf8"));
|
|
7432
|
-
} catch (e) {
|
|
7433
|
-
return usage(`.claude/settings.json is not JSON (${e.message}); fix it, then run this again`, table, io);
|
|
7434
|
-
}
|
|
7435
|
-
}
|
|
7436
|
-
writeFileSync8(settingsFile, JSON.stringify(withGuardHook(settings), null, 2) + "\n");
|
|
7437
|
-
written.push(".claude/settings.json");
|
|
7438
|
-
const binDir = join17(root, ".plywood/agent-bin");
|
|
7439
|
-
mkdirSync7(binDir, { recursive: true });
|
|
7440
|
-
for (const name of Object.keys(GUARDED)) {
|
|
7441
|
-
const to = join17(binDir, name);
|
|
7442
|
-
copyFileSync(new URL(`bin/${name}`, GUARD_TEMPLATES), to);
|
|
7443
|
-
chmodSync(to, 493);
|
|
7444
|
-
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);
|
|
7445
6891
|
}
|
|
7446
6892
|
if (json) io.out(JSON.stringify({ command: "guard install", ok: true, exitCode: table.done, written }));
|
|
7447
6893
|
else {
|
|
7448
6894
|
for (const f of written) io.err(`${PRODUCT} guard: wrote ${f}`);
|
|
7449
|
-
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`);
|
|
7450
6896
|
}
|
|
7451
6897
|
return table.done;
|
|
7452
6898
|
}
|
|
@@ -7454,6 +6900,7 @@ async function guard(args, json, table, io) {
|
|
|
7454
6900
|
}
|
|
7455
6901
|
|
|
7456
6902
|
// src/identities.ts
|
|
6903
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
7457
6904
|
init_jobs2();
|
|
7458
6905
|
init_chant_config();
|
|
7459
6906
|
init_identity();
|
|
@@ -7461,7 +6908,7 @@ init_config();
|
|
|
7461
6908
|
init_stacks();
|
|
7462
6909
|
init_stages();
|
|
7463
6910
|
init_words();
|
|
7464
|
-
import { existsSync as
|
|
6911
|
+
import { existsSync as existsSync14, readFileSync as readFileSync15 } from "node:fs";
|
|
7465
6912
|
import { dirname as dirname13, join as join18 } from "node:path";
|
|
7466
6913
|
import { targetGlobMatch } from "@intentius/chant/ci-identity";
|
|
7467
6914
|
function identityRows(config2, env) {
|
|
@@ -7524,28 +6971,21 @@ function configIdentities(args, json, table, io) {
|
|
|
7524
6971
|
else for (const line of identityLines(config2, environment, rows)) io.out(line);
|
|
7525
6972
|
return table.done;
|
|
7526
6973
|
}
|
|
7527
|
-
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.`;
|
|
7528
6974
|
var FORGES2 = ["github", "gitlab", "forgejo"];
|
|
7529
|
-
var FORGE_CHECK_FUNCTIONS = ["forgeCheck", "forgeCheckExit", "describeForgeCheck", "forgeCheckTarget", "pipelineContract"];
|
|
7530
6975
|
var forgeCheckModule;
|
|
7531
6976
|
function chantForgeCheck() {
|
|
7532
6977
|
forgeCheckModule ??= (async () => {
|
|
7533
|
-
|
|
7534
|
-
|
|
7535
|
-
|
|
7536
|
-
if (!FORGE_CHECK_FUNCTIONS.every((f) => typeof fc[f] === "function") || typeof id.resolveCloudIdentity !== "function") return void 0;
|
|
7537
|
-
return { ...fc, resolveCloudIdentity: id.resolveCloudIdentity };
|
|
7538
|
-
} catch {
|
|
7539
|
-
return void 0;
|
|
7540
|
-
}
|
|
6978
|
+
const fc = await import("@intentius/chant/forge-check");
|
|
6979
|
+
const { resolveCloudIdentity } = await import("@intentius/chant/ci-identity");
|
|
6980
|
+
return { ...fc, resolveCloudIdentity };
|
|
7541
6981
|
})();
|
|
7542
6982
|
return forgeCheckModule;
|
|
7543
6983
|
}
|
|
7544
6984
|
function workspaceMembers(root) {
|
|
7545
6985
|
const file = join18(root, "chant.workspace.json");
|
|
7546
|
-
if (!
|
|
6986
|
+
if (!existsSync14(file)) return [];
|
|
7547
6987
|
try {
|
|
7548
|
-
const doc = JSON.parse(
|
|
6988
|
+
const doc = JSON.parse(readFileSync15(file, "utf8"));
|
|
7549
6989
|
return (doc.members ?? []).map((m) => m.name).filter((n) => typeof n === "string");
|
|
7550
6990
|
} catch {
|
|
7551
6991
|
return [];
|
|
@@ -7574,11 +7014,6 @@ function contractPipelines(fc, cloud, env, members, runner, jobs2) {
|
|
|
7574
7014
|
async function forgeCheck(args, json, table, io) {
|
|
7575
7015
|
if (args[0] !== "check") return usage(`unknown forge command '${args[0] ?? ""}'. Run '${PRODUCT} forge check'.`, table, io);
|
|
7576
7016
|
const fc = await chantForgeCheck();
|
|
7577
|
-
if (!fc) {
|
|
7578
|
-
if (json) io.out(JSON.stringify({ command: "forge check", ok: false, exitCode: table.failed, error: FORGE_CHECK_MESSAGE }));
|
|
7579
|
-
else io.err(`${PRODUCT}: ${FORGE_CHECK_MESSAGE}`);
|
|
7580
|
-
return table.failed;
|
|
7581
|
-
}
|
|
7582
7017
|
const requireClouds = args.includes("--require-clouds");
|
|
7583
7018
|
let rest = args.slice(1).filter((a) => a !== "--require-clouds");
|
|
7584
7019
|
const flags = {};
|
|
@@ -7648,6 +7083,7 @@ async function forgeCheck(args, json, table, io) {
|
|
|
7648
7083
|
init_control2();
|
|
7649
7084
|
|
|
7650
7085
|
// src/telemetry.ts
|
|
7086
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
7651
7087
|
init_chant_product();
|
|
7652
7088
|
init_observe();
|
|
7653
7089
|
init_config();
|
|
@@ -7676,8 +7112,7 @@ async function applyTelemetry(cwd, env = process.env) {
|
|
|
7676
7112
|
return {};
|
|
7677
7113
|
}
|
|
7678
7114
|
Object.assign(env, added);
|
|
7679
|
-
|
|
7680
|
-
if (chant && typeof chant.configureOtlp === "function") chant.configureOtlp({ prefix: PRODUCT, env });
|
|
7115
|
+
(await chantProduct()).configureOtlp({ prefix: PRODUCT, env });
|
|
7681
7116
|
return added;
|
|
7682
7117
|
}
|
|
7683
7118
|
|
|
@@ -7685,16 +7120,16 @@ async function applyTelemetry(cwd, env = process.env) {
|
|
|
7685
7120
|
init_init();
|
|
7686
7121
|
init_stages();
|
|
7687
7122
|
init_tips();
|
|
7688
|
-
init_images();
|
|
7689
7123
|
init_up();
|
|
7690
7124
|
|
|
7691
7125
|
// src/upgrade.ts
|
|
7126
|
+
init_define_PLYWOOD_IMAGE_DIGESTS();
|
|
7692
7127
|
init_config();
|
|
7693
7128
|
init_images();
|
|
7694
7129
|
init_stages();
|
|
7695
7130
|
init_words();
|
|
7696
7131
|
import { execFile } from "node:child_process";
|
|
7697
|
-
import { existsSync as
|
|
7132
|
+
import { existsSync as existsSync15, readFileSync as readFileSync16, writeFileSync as writeFileSync8 } from "node:fs";
|
|
7698
7133
|
import { dirname as dirname14, join as join19, relative as relative2, resolve as resolve3 } from "node:path";
|
|
7699
7134
|
var PLYWOOD_PACKAGE = "@intentius/plywood";
|
|
7700
7135
|
var CHANT_PACKAGE = "@intentius/chant";
|
|
@@ -7727,9 +7162,9 @@ function findPlywoodPackage(dir) {
|
|
|
7727
7162
|
let cur = resolve3(dir);
|
|
7728
7163
|
for (let depth = 0; depth < 32; depth++) {
|
|
7729
7164
|
const file = join19(cur, "package.json");
|
|
7730
|
-
if (
|
|
7165
|
+
if (existsSync15(file)) {
|
|
7731
7166
|
try {
|
|
7732
|
-
const pkg = JSON.parse(
|
|
7167
|
+
const pkg = JSON.parse(readFileSync16(file, "utf8"));
|
|
7733
7168
|
if (DEP_FIELDS.some((f) => isObject2(pkg[f]) && typeof pkg[f][PLYWOOD_PACKAGE] === "string")) return file;
|
|
7734
7169
|
} catch {
|
|
7735
7170
|
}
|
|
@@ -7827,10 +7262,10 @@ async function upgrade(args, json, table, io) {
|
|
|
7827
7262
|
const release2 = await viewRelease(exec, pkgDir, io.env, opts.version);
|
|
7828
7263
|
if ("error" in release2) return fail(release2.error);
|
|
7829
7264
|
const chant = opts.chant ?? release2.chant;
|
|
7830
|
-
const pkgText =
|
|
7265
|
+
const pkgText = readFileSync16(pkgFile, "utf8");
|
|
7831
7266
|
const pkgMove = movePackagePins(pkgText, rel(pkgFile), release2.version, chant);
|
|
7832
7267
|
const declFile = join19(configDir, "chant.workspace.json");
|
|
7833
|
-
const declText =
|
|
7268
|
+
const declText = existsSync15(declFile) ? readFileSync16(declFile, "utf8") : void 0;
|
|
7834
7269
|
let declMove;
|
|
7835
7270
|
if (declText !== void 0) {
|
|
7836
7271
|
try {
|
|
@@ -7840,7 +7275,7 @@ async function upgrade(args, json, table, io) {
|
|
|
7840
7275
|
}
|
|
7841
7276
|
}
|
|
7842
7277
|
const notes = [...pkgMove.left];
|
|
7843
|
-
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`);
|
|
7844
7279
|
if (configFile) {
|
|
7845
7280
|
try {
|
|
7846
7281
|
const note = imageNote(loadConfig(configFile).doc, release2.version);
|
|
@@ -7869,8 +7304,8 @@ async function upgrade(args, json, table, io) {
|
|
|
7869
7304
|
if (json) io.out(JSON.stringify({ ...envelope, ok: true, exitCode: table.done, steps }));
|
|
7870
7305
|
return table.done;
|
|
7871
7306
|
}
|
|
7872
|
-
if (pkgMove.text !== pkgText)
|
|
7873
|
-
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);
|
|
7874
7309
|
const files = [...new Set(moves.map((m) => m.file))];
|
|
7875
7310
|
if (!opts.install) {
|
|
7876
7311
|
say(` left to run: ${steps.install.command} (in ${steps.install.cwd}), then ${steps.ci.command} (in ${steps.ci.cwd})`);
|
|
@@ -7896,7 +7331,7 @@ ${installed2.stderr.trim()}`);
|
|
|
7896
7331
|
for (const n of notes) say(` note: ${n}`);
|
|
7897
7332
|
const ok = ci2 === void 0 || ci2.code === 0;
|
|
7898
7333
|
const lockFile = join19(pkgDir, "package-lock.json");
|
|
7899
|
-
const changed = [...files, ...
|
|
7334
|
+
const changed = [...files, ...existsSync15(lockFile) ? [rel(lockFile)] : []];
|
|
7900
7335
|
if (ok) say(` Open one pull request with ${changed.join(", ")} and the files ${PRODUCT} ci wrote.`);
|
|
7901
7336
|
else say(` ${PRODUCT} ci failed (exit ${ci2?.code}); the pins are moved and installed, so fix plywood.yml and run '${steps.ci.command}'`);
|
|
7902
7337
|
const code = ok ? table.done : ci2?.code ?? table.failed;
|
|
@@ -7905,7 +7340,7 @@ ${installed2.stderr.trim()}`);
|
|
|
7905
7340
|
}
|
|
7906
7341
|
function installedVersion(dir) {
|
|
7907
7342
|
try {
|
|
7908
|
-
const pkg = JSON.parse(
|
|
7343
|
+
const pkg = JSON.parse(readFileSync16(join19(dir, "node_modules", PLYWOOD_PACKAGE, "package.json"), "utf8"));
|
|
7909
7344
|
return typeof pkg.version === "string" ? pkg.version : void 0;
|
|
7910
7345
|
} catch {
|
|
7911
7346
|
return void 0;
|
|
@@ -7928,7 +7363,7 @@ var execChild = (bin, args, opts) => new Promise((done) => {
|
|
|
7928
7363
|
|
|
7929
7364
|
// src/cli.ts
|
|
7930
7365
|
init_words();
|
|
7931
|
-
var VERSION3 = true ? "0.3.
|
|
7366
|
+
var VERSION3 = true ? "0.3.5" : "0.0.0-dev";
|
|
7932
7367
|
var stdio = {
|
|
7933
7368
|
out: (s) => process.stdout.write(s + "\n"),
|
|
7934
7369
|
err: (s) => process.stderr.write(s + "\n")
|
|
@@ -8019,7 +7454,7 @@ function helpText() {
|
|
|
8019
7454
|
].join("\n");
|
|
8020
7455
|
}
|
|
8021
7456
|
async function main(argv, io = stdio) {
|
|
8022
|
-
const
|
|
7457
|
+
const table = EXIT;
|
|
8023
7458
|
const json = argv.includes("--json");
|
|
8024
7459
|
const args = argv.filter((a) => a !== "--json");
|
|
8025
7460
|
const verb = args[0];
|
|
@@ -8032,11 +7467,9 @@ async function main(argv, io = stdio) {
|
|
|
8032
7467
|
return table.done;
|
|
8033
7468
|
}
|
|
8034
7469
|
if (verb === "--version" || verb === "-v") {
|
|
8035
|
-
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);
|
|
8036
7471
|
return table.done;
|
|
8037
7472
|
}
|
|
8038
|
-
await useChantInstallLines();
|
|
8039
|
-
await useChantPolicySource();
|
|
8040
7473
|
const stageIo = { out: io.out, err: io.err, cwd: process.cwd(), env: process.env };
|
|
8041
7474
|
if (verb !== "config" && verb !== "init" && verb !== "tips" && verb !== "upgrade") await applyTelemetry(stageIo.cwd, stageIo.env);
|
|
8042
7475
|
if (verb === "audit") return audit2(args.slice(1), json, table, stageIo);
|