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