@intentius/plywood 0.0.0 → 0.1.0
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/plywood.mjs +225 -173
- package/dist/plywood.mjs.map +4 -4
- package/package.json +6 -8
package/dist/plywood.mjs
CHANGED
|
@@ -578,10 +578,13 @@ var init_control = __esm({
|
|
|
578
578
|
var version, dependencies;
|
|
579
579
|
var init_package = __esm({
|
|
580
580
|
"package.json"() {
|
|
581
|
-
version = "0.
|
|
581
|
+
version = "0.1.0";
|
|
582
582
|
dependencies = {
|
|
583
|
-
"@intentius/chant": "0.124.
|
|
584
|
-
"@intentius/chant-lexicon-
|
|
583
|
+
"@intentius/chant": "0.124.57",
|
|
584
|
+
"@intentius/chant-lexicon-forgejo": "0.124.57",
|
|
585
|
+
"@intentius/chant-lexicon-github": "0.124.57",
|
|
586
|
+
"@intentius/chant-lexicon-gitlab": "0.124.57",
|
|
587
|
+
"@intentius/chant-lexicon-pulumi": "0.124.57"
|
|
585
588
|
};
|
|
586
589
|
}
|
|
587
590
|
});
|
|
@@ -590,7 +593,10 @@ var init_package = __esm({
|
|
|
590
593
|
var image_digests_default;
|
|
591
594
|
var init_image_digests = __esm({
|
|
592
595
|
"src/image-digests.json"() {
|
|
593
|
-
image_digests_default = {
|
|
596
|
+
image_digests_default = {
|
|
597
|
+
"ghcr.io/intentius/plywood-pulumi:0.1.0-pulumi3.272.0": "sha256:730237bed59e0054e6839898658069a4bcbdab0ffb8f893487c864bc2f3436c8",
|
|
598
|
+
"ghcr.io/intentius/plywood-plunge:0.1.0-plunge0.1.0": "sha256:76a50f55cc2fd96c19e4ecb625a19863783374c9dc89d1606cc70a36635cc48d"
|
|
599
|
+
};
|
|
594
600
|
}
|
|
595
601
|
});
|
|
596
602
|
|
|
@@ -2600,44 +2606,11 @@ var init_chant_product = __esm({
|
|
|
2600
2606
|
"src/chant-product.ts"() {
|
|
2601
2607
|
"use strict";
|
|
2602
2608
|
REQUIRED2 = ["assertProductWords", "productPipelineJobs", "approveLine", "runChantInProcess"];
|
|
2603
|
-
BUILT_CHANT = true ? "0.124.
|
|
2609
|
+
BUILT_CHANT = true ? "0.124.57" : "unknown";
|
|
2604
2610
|
}
|
|
2605
2611
|
});
|
|
2606
2612
|
|
|
2607
2613
|
// src/chant-next.ts
|
|
2608
|
-
async function acceptsContentsWrite(load) {
|
|
2609
|
-
try {
|
|
2610
|
-
const m = await load();
|
|
2611
|
-
if (typeof m.assertScheduledOpCloud !== "function") return false;
|
|
2612
|
-
m.assertScheduledOpCloud(structuredClone(CONTENTS_WRITE_PROBE));
|
|
2613
|
-
return true;
|
|
2614
|
-
} catch {
|
|
2615
|
-
return false;
|
|
2616
|
-
}
|
|
2617
|
-
}
|
|
2618
|
-
async function has(load, name, type = "function") {
|
|
2619
|
-
try {
|
|
2620
|
-
const m = await load();
|
|
2621
|
-
return typeof m[name] === type;
|
|
2622
|
-
} catch {
|
|
2623
|
-
return false;
|
|
2624
|
-
}
|
|
2625
|
-
}
|
|
2626
|
-
function chantNext() {
|
|
2627
|
-
detected ??= (async () => ({
|
|
2628
|
-
// @ts-ignore: the subpath is missing from chant 0.124.55 (chant#4624).
|
|
2629
|
-
checkJob: await has(() => import("@intentius/chant/ci-job-guard"), "checkJob"),
|
|
2630
|
-
planFlags: await has(() => import("@intentius/chant/components/capability"), "PLAN_FLAG_ARGS", "object"),
|
|
2631
|
-
staleNotes: await has(() => import("@intentius/chant/pr-post"), "markStaleNotes"),
|
|
2632
|
-
// @ts-ignore: the subpath is missing from chant 0.124.55 (chant#4631).
|
|
2633
|
-
scheduledOpCloud: await has(() => import("@intentius/chant/scheduled-op-cloud"), "assertScheduledOpCloud"),
|
|
2634
|
-
// @ts-ignore: the subpath is missing from chant 0.124.55 (chant#4631).
|
|
2635
|
-
updateContentsWrite: await acceptsContentsWrite(() => import("@intentius/chant/scheduled-op-cloud")),
|
|
2636
|
-
// @ts-ignore: the subpath is missing from chant 0.124.55 (chant#4636).
|
|
2637
|
-
applyMerge: await has(() => import("@intentius/chant/pr-merge"), "mergeAfterApply")
|
|
2638
|
-
}))();
|
|
2639
|
-
return detected;
|
|
2640
|
-
}
|
|
2641
2614
|
function hasChantRespond(chant) {
|
|
2642
2615
|
return typeof chant?.CI_RESPOND_FLOOR === "string";
|
|
2643
2616
|
}
|
|
@@ -2651,26 +2624,13 @@ function hasChantPullRequestApprovalStatus(chant) {
|
|
|
2651
2624
|
return typeof chant?.APPROVAL_STATUS_PULL_REQUEST_FLOOR === "string";
|
|
2652
2625
|
}
|
|
2653
2626
|
function chantAudit() {
|
|
2654
|
-
audit ??= (
|
|
2655
|
-
try {
|
|
2656
|
-
const m = await import("@intentius/chant/ci-audit");
|
|
2657
|
-
return typeof m.attributeDrift === "function" && typeof m.routeAttributions === "function" && typeof m.parseAuditLogSetting === "function" ? m : void 0;
|
|
2658
|
-
} catch {
|
|
2659
|
-
return void 0;
|
|
2660
|
-
}
|
|
2661
|
-
})();
|
|
2627
|
+
audit ??= import("@intentius/chant/ci-audit").then((m) => m);
|
|
2662
2628
|
return audit;
|
|
2663
2629
|
}
|
|
2664
|
-
var
|
|
2630
|
+
var audit;
|
|
2665
2631
|
var init_chant_next = __esm({
|
|
2666
2632
|
"src/chant-next.ts"() {
|
|
2667
2633
|
"use strict";
|
|
2668
|
-
CONTENTS_WRITE_PROBE = {
|
|
2669
|
-
name: "plywood-probe",
|
|
2670
|
-
trigger: { kind: "cron", schedule: "0 6 * * *" },
|
|
2671
|
-
findingMode: "issue",
|
|
2672
|
-
update: { command: "true", permissions: { contents: "write" } }
|
|
2673
|
-
};
|
|
2674
2634
|
}
|
|
2675
2635
|
});
|
|
2676
2636
|
|
|
@@ -2875,6 +2835,74 @@ var init_review_stacks2 = __esm({
|
|
|
2875
2835
|
// src/waves-workflow.ts
|
|
2876
2836
|
import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "node:fs";
|
|
2877
2837
|
import { dirname as dirname4, join as join4 } from "node:path";
|
|
2838
|
+
function wavesEngine(pipeline) {
|
|
2839
|
+
const o = chantPipelineOptions({ ...pipeline.image ? { image: pipeline.image } : {}, ...pipeline.engineSetup ? { engineSetup: pipeline.engineSetup } : {} }, false);
|
|
2840
|
+
return { ...o.image ? { image: o.image } : {}, ...o.beforeScript?.length ? { beforeScript: o.beforeScript } : {} };
|
|
2841
|
+
}
|
|
2842
|
+
function wavesSpecText(spec, engine2 = {}) {
|
|
2843
|
+
const options = { ...engine2.image ? { image: engine2.image } : {}, ...engine2.beforeScript?.length ? { beforeScript: engine2.beforeScript } : {} };
|
|
2844
|
+
return JSON.stringify(Object.keys(options).length ? { waves: spec, options } : spec, null, 2) + "\n";
|
|
2845
|
+
}
|
|
2846
|
+
function unwrapWavesSpec(doc) {
|
|
2847
|
+
if (!doc || typeof doc !== "object" || Array.isArray(doc)) return doc;
|
|
2848
|
+
const inner = doc.waves;
|
|
2849
|
+
if (Array.isArray(inner)) return doc;
|
|
2850
|
+
return inner && typeof inner === "object" && Array.isArray(inner.waves) ? inner : doc;
|
|
2851
|
+
}
|
|
2852
|
+
function stepId(wave, target) {
|
|
2853
|
+
return `${wave}-${target}`.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
2854
|
+
}
|
|
2855
|
+
function upOpFile(spec) {
|
|
2856
|
+
const s = spec ?? {};
|
|
2857
|
+
const op = typeof s.op === "string" && s.op !== "" ? s.op : OP_NAME;
|
|
2858
|
+
const taken = /* @__PURE__ */ new Set();
|
|
2859
|
+
const phases = [];
|
|
2860
|
+
for (const [i, w] of (s.waves ?? []).entries()) {
|
|
2861
|
+
const name = typeof w.name === "string" && w.name !== "" ? w.name : `wave-${i + 1}`;
|
|
2862
|
+
const steps = [];
|
|
2863
|
+
for (const run of w.runs ?? []) {
|
|
2864
|
+
const input = run.input ?? {};
|
|
2865
|
+
if (typeof input.member !== "string" || typeof input.dir !== "string") continue;
|
|
2866
|
+
let id = stepId(name, run.target);
|
|
2867
|
+
for (let n = 2; taken.has(id); n++) id = `${stepId(name, run.target)}-${n}`;
|
|
2868
|
+
taken.add(id);
|
|
2869
|
+
const opts = [
|
|
2870
|
+
`dir: ${JSON.stringify(input.dir)}`,
|
|
2871
|
+
...typeof input.stack === "string" ? [`stack: ${JSON.stringify(input.stack)}`] : [],
|
|
2872
|
+
...input.engine === "pulumi" || input.engine === "plunge" ? [`engine: ${JSON.stringify(input.engine)}`] : [],
|
|
2873
|
+
`id: ${JSON.stringify(id)}`
|
|
2874
|
+
];
|
|
2875
|
+
steps.push(`pulumiPreviewStep(${JSON.stringify(input.member)}, { ${opts.join(", ")} })`);
|
|
2876
|
+
}
|
|
2877
|
+
if (steps.length > 0) phases.push(` phase(${JSON.stringify(name)}, [${steps.join(", ")}]),`);
|
|
2878
|
+
}
|
|
2879
|
+
return [
|
|
2880
|
+
`// Written by '${PRODUCT} init' and '${PRODUCT} ci' from .chant/op-waves/${OP_NAME}.spec.json, and written again on each run: edit plywood.yml, not this file.`,
|
|
2881
|
+
`// The waves workflow runs under this Op's name: each wave job previews and updates its stacks through the spec's`,
|
|
2882
|
+
`// pulumi-up step and records the runs under ${op}. chant's generator needs the Op to exist. Run by hand`,
|
|
2883
|
+
`// (npx chant run ${op}), it previews each stack in wave order and changes nothing.`,
|
|
2884
|
+
`import { Op, phase } from "@intentius/chant/op";`,
|
|
2885
|
+
`import { pulumiPreviewStep } from "@intentius/chant-lexicon-pulumi";`,
|
|
2886
|
+
"",
|
|
2887
|
+
"export default Op({",
|
|
2888
|
+
` name: ${JSON.stringify(op)},`,
|
|
2889
|
+
` overview: "The stacks ${PRODUCT}'s waves update on merge, previewed in wave order",`,
|
|
2890
|
+
` labels: { Surface: "${PRODUCT}-up" },`,
|
|
2891
|
+
" phases: [",
|
|
2892
|
+
...phases,
|
|
2893
|
+
" ],",
|
|
2894
|
+
"});",
|
|
2895
|
+
""
|
|
2896
|
+
].join("\n");
|
|
2897
|
+
}
|
|
2898
|
+
function writeUpOp(cwd, spec) {
|
|
2899
|
+
const path = join4(cwd, UP_OP_FILE);
|
|
2900
|
+
const text = upOpFile(spec);
|
|
2901
|
+
if (existsSync4(path) && readFileSync4(path, "utf8") === text) return false;
|
|
2902
|
+
mkdirSync2(dirname4(path), { recursive: true });
|
|
2903
|
+
writeFileSync2(path, text);
|
|
2904
|
+
return true;
|
|
2905
|
+
}
|
|
2878
2906
|
function binLines(waves, bin) {
|
|
2879
2907
|
const binArgs = bin.split(/\s+/).filter(Boolean);
|
|
2880
2908
|
return waves.jobs.map((job) => {
|
|
@@ -2894,15 +2922,16 @@ function generatedFor(dir, specFile) {
|
|
|
2894
2922
|
if (!existsSync4(dir)) return [];
|
|
2895
2923
|
return readdirSync2(dir).filter((f) => /\.ya?ml$/.test(f)).map((f) => join4(dir, f)).filter((p) => (readFileSync4(p, "utf8").split("\n", 1)[0] ?? "").includes(`--spec ${specFile}`));
|
|
2896
2924
|
}
|
|
2897
|
-
async function writeWaveJobs(chant, forge, waves, words, table, io) {
|
|
2925
|
+
async function writeWaveJobs(chant, forge, waves, words, table, io, engine2 = {}) {
|
|
2898
2926
|
const specPath = join4(io.cwd, waves.specFile);
|
|
2899
|
-
const specText =
|
|
2927
|
+
const specText = wavesSpecText(waves.spec, engine2);
|
|
2900
2928
|
const before = existsSync4(specPath) ? readFileSync4(specPath, "utf8") : void 0;
|
|
2901
2929
|
if (before !== specText) {
|
|
2902
2930
|
mkdirSync2(dirname4(specPath), { recursive: true });
|
|
2903
2931
|
writeFileSync2(specPath, specText);
|
|
2904
2932
|
io.err(`${PRODUCT} ci: wrote ${waves.specFile} with plywood's op and gates; commit it with the workflow`);
|
|
2905
2933
|
}
|
|
2934
|
+
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`);
|
|
2906
2935
|
const outDir = WAVES_OUTPUT_DIR[forge];
|
|
2907
2936
|
const code = await chant.runChantInProcess(["run", "--generate", forge, "--spec", waves.specFile, "--output", outDir], words);
|
|
2908
2937
|
if (code !== table.done) return code;
|
|
@@ -2925,17 +2954,19 @@ async function writeWaveJobs(chant, forge, waves, words, table, io) {
|
|
|
2925
2954
|
}
|
|
2926
2955
|
return table.done;
|
|
2927
2956
|
}
|
|
2928
|
-
var WAVES_OUTPUT_DIR;
|
|
2957
|
+
var WAVES_OUTPUT_DIR, UP_OP_FILE;
|
|
2929
2958
|
var init_waves_workflow = __esm({
|
|
2930
2959
|
"src/waves-workflow.ts"() {
|
|
2931
2960
|
"use strict";
|
|
2932
2961
|
init_chant_product();
|
|
2962
|
+
init_images();
|
|
2933
2963
|
init_words();
|
|
2934
2964
|
WAVES_OUTPUT_DIR = {
|
|
2935
2965
|
github: ".github/workflows",
|
|
2936
2966
|
forgejo: ".forgejo/workflows",
|
|
2937
2967
|
gitlab: "."
|
|
2938
2968
|
};
|
|
2969
|
+
UP_OP_FILE = "ops/pulumi-up.op.ts";
|
|
2939
2970
|
}
|
|
2940
2971
|
});
|
|
2941
2972
|
|
|
@@ -3116,7 +3147,7 @@ __export(jobs_exports, {
|
|
|
3116
3147
|
withJobSettings: () => withJobSettings
|
|
3117
3148
|
});
|
|
3118
3149
|
function hasChant4625() {
|
|
3119
|
-
|
|
3150
|
+
detected ??= (async () => {
|
|
3120
3151
|
try {
|
|
3121
3152
|
const m = await import("@intentius/chant/parallelism");
|
|
3122
3153
|
return typeof m.mapLimited === "function" && typeof m.parallelismProblem === "function";
|
|
@@ -3124,10 +3155,10 @@ function hasChant4625() {
|
|
|
3124
3155
|
return false;
|
|
3125
3156
|
}
|
|
3126
3157
|
})();
|
|
3127
|
-
return
|
|
3158
|
+
return detected;
|
|
3128
3159
|
}
|
|
3129
|
-
function setChant4625ForTests(
|
|
3130
|
-
|
|
3160
|
+
function setChant4625ForTests(has) {
|
|
3161
|
+
detected = has === "reset" ? void 0 : Promise.resolve(has);
|
|
3131
3162
|
}
|
|
3132
3163
|
function secretReference(forge, secret) {
|
|
3133
3164
|
return forge === "gitlab" ? `$${secret}` : `\${{ secrets.${secret} }}`;
|
|
@@ -3228,7 +3259,7 @@ async function prJobsFor(pipeline) {
|
|
|
3228
3259
|
const { flags, notes } = prLoopJobFlags(pipeline.jobs, pipeline.runner);
|
|
3229
3260
|
return { options: prLoopJobOptions(pipeline.jobs, pipeline.runner), flags, notes };
|
|
3230
3261
|
}
|
|
3231
|
-
var
|
|
3262
|
+
var detected, JOBS_NEED_CHANT, GROUP_NAME;
|
|
3232
3263
|
var init_jobs2 = __esm({
|
|
3233
3264
|
"src/jobs.ts"() {
|
|
3234
3265
|
"use strict";
|
|
@@ -3362,7 +3393,7 @@ function wavesSpecAt(cwd, file = WAVES_SPEC_FILE) {
|
|
|
3362
3393
|
const path = join6(cwd, file);
|
|
3363
3394
|
if (!existsSync6(path)) return void 0;
|
|
3364
3395
|
try {
|
|
3365
|
-
const spec = JSON.parse(readFileSync6(path, "utf8"));
|
|
3396
|
+
const spec = unwrapWavesSpec(JSON.parse(readFileSync6(path, "utf8")));
|
|
3366
3397
|
return spec && typeof spec === "object" && !Array.isArray(spec) ? spec : void 0;
|
|
3367
3398
|
} catch {
|
|
3368
3399
|
return void 0;
|
|
@@ -3450,7 +3481,7 @@ function readUpSpec(cwd, file = WAVES_SPEC_FILE) {
|
|
|
3450
3481
|
if (!existsSync6(path)) return { error: `no ${file}; '${PRODUCT} init' writes it from plywood.yml's waves` };
|
|
3451
3482
|
let spec;
|
|
3452
3483
|
try {
|
|
3453
|
-
spec = JSON.parse(readFileSync6(path, "utf8"));
|
|
3484
|
+
spec = unwrapWavesSpec(JSON.parse(readFileSync6(path, "utf8")));
|
|
3454
3485
|
} catch (e) {
|
|
3455
3486
|
return { error: `${file}: ${e.message}` };
|
|
3456
3487
|
}
|
|
@@ -3549,20 +3580,15 @@ async function upBeforeMerge(number, args, json, table, io) {
|
|
|
3549
3580
|
"--forge",
|
|
3550
3581
|
forge,
|
|
3551
3582
|
...requiresArgv(chant, config2.doc),
|
|
3552
|
-
...
|
|
3583
|
+
...mergeArgv(config2.doc),
|
|
3553
3584
|
...json ? ["--json"] : []
|
|
3554
3585
|
];
|
|
3555
3586
|
return explainRefusal(await chant.runChantInProcess(argv, PRODUCT_WORDS), table, io.err, { pr: number });
|
|
3556
3587
|
});
|
|
3557
3588
|
}
|
|
3558
|
-
|
|
3589
|
+
function mergeArgv(doc) {
|
|
3559
3590
|
const merge = applyMergeOf(doc);
|
|
3560
|
-
|
|
3561
|
-
if (!(await chantNext()).applyMerge) {
|
|
3562
|
-
io.err(`${PRODUCT}: ${APPLY_MERGE_NEEDS_CHANT}`);
|
|
3563
|
-
return [];
|
|
3564
|
-
}
|
|
3565
|
-
return applyMergeArgv(merge);
|
|
3591
|
+
return merge ? applyMergeArgv(merge) : [];
|
|
3566
3592
|
}
|
|
3567
3593
|
async function resume(args, json, table, io) {
|
|
3568
3594
|
const op = takeFlag(args, "--op");
|
|
@@ -3597,6 +3623,7 @@ var init_up = __esm({
|
|
|
3597
3623
|
init_review_stacks2();
|
|
3598
3624
|
init_stages();
|
|
3599
3625
|
init_words();
|
|
3626
|
+
init_waves_workflow();
|
|
3600
3627
|
OVERRIDE_OP = "policy-override";
|
|
3601
3628
|
WAVES_SPEC_FILE = `.chant/op-waves/${OP_NAME}.spec.json`;
|
|
3602
3629
|
UP_STEP_KIND = "pulumi-up";
|
|
@@ -3769,12 +3796,7 @@ async function readStack(chant, stack, dir, opts) {
|
|
|
3769
3796
|
async function attributionFor(doc, io) {
|
|
3770
3797
|
const logs = driftSettingsOf(doc).auditLogs;
|
|
3771
3798
|
if (!logs) return void 0;
|
|
3772
|
-
|
|
3773
|
-
if (!audit3) {
|
|
3774
|
-
io.err(`${PRODUCT} drift: ${ATTRIBUTION_NEEDS_CHANT}`);
|
|
3775
|
-
return void 0;
|
|
3776
|
-
}
|
|
3777
|
-
return { audit: audit3, logs };
|
|
3799
|
+
return { audit: await chantAudit(), logs };
|
|
3778
3800
|
}
|
|
3779
3801
|
function auditLogFor(logs, stack) {
|
|
3780
3802
|
return Object.entries(logs).find(([glob]) => glob === "*" || glob === "**" || globMatches(glob, stack))?.[1];
|
|
@@ -3882,9 +3904,8 @@ async function writeDriftJobs(chant, forge, doc, words, table, io, env = "produc
|
|
|
3882
3904
|
io.err(`${PRODUCT} ci: drift.schedule is set and plywood.yml lists no stacks, so no drift job was written`);
|
|
3883
3905
|
return table.done;
|
|
3884
3906
|
}
|
|
3885
|
-
const next = await chantNext();
|
|
3886
3907
|
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`);
|
|
3887
|
-
if (writeIfChanged2(io.cwd, DRIFT_SPEC_FILE, JSON.stringify(driftOpSpec(doc, forge, settings.stacks, settings.schedule, chant, { cloud:
|
|
3908
|
+
if (writeIfChanged2(io.cwd, DRIFT_SPEC_FILE, JSON.stringify(driftOpSpec(doc, forge, settings.stacks, settings.schedule, chant, { cloud: true, contentsWrite: true, env }), null, 2) + "\n")) io.err(`${PRODUCT} ci: wrote ${DRIFT_SPEC_FILE}`);
|
|
3888
3909
|
const outDir = OP_OUTPUT_DIR2[forge];
|
|
3889
3910
|
const code = await chant.runChantInProcess(["run", "--generate", forge, "--spec", DRIFT_SPEC_FILE, "--output", outDir], words);
|
|
3890
3911
|
if (code !== table.done) return code;
|
|
@@ -3894,7 +3915,7 @@ async function writeDriftJobs(chant, forge, doc, words, table, io, env = "produc
|
|
|
3894
3915
|
if (files.length && forge === "gitlab") io.err(`${PRODUCT} ci: GitLab runs the drift job from a pipeline schedule; include the file from .gitlab-ci.yml and add a schedule`);
|
|
3895
3916
|
return table.done;
|
|
3896
3917
|
}
|
|
3897
|
-
var DRIFT_OP_FILE, DRIFT_SPEC_FILE, SNAPSHOTS_REF, SNAPSHOTS_FETCH, DRIFT_STATUSES, forgeOverride, notifyFetch,
|
|
3918
|
+
var DRIFT_OP_FILE, DRIFT_SPEC_FILE, SNAPSHOTS_REF, SNAPSHOTS_FETCH, DRIFT_STATUSES, forgeOverride, notifyFetch, OP_OUTPUT_DIR2;
|
|
3898
3919
|
var init_drift2 = __esm({
|
|
3899
3920
|
"src/drift.ts"() {
|
|
3900
3921
|
"use strict";
|
|
@@ -3918,7 +3939,6 @@ var init_drift2 = __esm({
|
|
|
3918
3939
|
SNAPSHOTS_REF = "refs/chant/snapshots";
|
|
3919
3940
|
SNAPSHOTS_FETCH = `git fetch --no-tags origin '+${SNAPSHOTS_REF}:${SNAPSHOTS_REF}' || true`;
|
|
3920
3941
|
DRIFT_STATUSES = ["drifted", "missing"];
|
|
3921
|
-
ATTRIBUTION_NEEDS_CHANT = "drift.audit_log is set, and the installed chant has no audit-log reads (chant#4632), so no drifted attribute was attributed";
|
|
3922
3942
|
OP_OUTPUT_DIR2 = { github: ".github/workflows", forgejo: ".forgejo/workflows", gitlab: "." };
|
|
3923
3943
|
}
|
|
3924
3944
|
});
|
|
@@ -4325,7 +4345,7 @@ function adoptLines(previews, chant) {
|
|
|
4325
4345
|
}
|
|
4326
4346
|
const count = `${v.resources.length} resource${v.resources.length === 1 ? "" : "s"}`;
|
|
4327
4347
|
if (v.status === "refused") {
|
|
4328
|
-
out.push(` ${p.key}: plunge refuses to adopt it:`);
|
|
4348
|
+
out.push(` ${p.key}: plunge refuses to adopt it, so the adopt Op leaves it out:`);
|
|
4329
4349
|
for (const r of v.refusals) out.push(` ${r.code}: ${r.reason}${r.urn ? ` (${r.urn})` : ""}`);
|
|
4330
4350
|
} else if (v.status === "already-adopted") {
|
|
4331
4351
|
out.push(` ${p.key}: already on plunge's records (${count})`);
|
|
@@ -4338,7 +4358,10 @@ function adoptLines(previews, chant) {
|
|
|
4338
4358
|
return out;
|
|
4339
4359
|
}
|
|
4340
4360
|
function stacksToAdopt(stacks2, previews) {
|
|
4341
|
-
return stacks2.filter((s) =>
|
|
4361
|
+
return stacks2.filter((s) => {
|
|
4362
|
+
const status = previews.find((p) => p.key === s.key)?.view?.status;
|
|
4363
|
+
return status !== "already-adopted" && status !== "refused";
|
|
4364
|
+
});
|
|
4342
4365
|
}
|
|
4343
4366
|
function adoptGate(member, stack, single) {
|
|
4344
4367
|
return single ? `approve-${ADOPT_OP}` : `approve-${ADOPT_OP}-${member}-${stack}`;
|
|
@@ -4401,9 +4424,9 @@ function plungeUrlOf(url) {
|
|
|
4401
4424
|
const scheme = url.split("://")[0];
|
|
4402
4425
|
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}` };
|
|
4403
4426
|
}
|
|
4404
|
-
function plungeStacks(
|
|
4405
|
-
return
|
|
4406
|
-
const project =
|
|
4427
|
+
function plungeStacks(detected2, env) {
|
|
4428
|
+
return detected2.stacks.map((s) => {
|
|
4429
|
+
const project = detected2.projects.find((p) => p.dir === s.dir && p.name === s.project) ?? { backendUrl: void 0 };
|
|
4407
4430
|
const b = backendOf(project, {}, env);
|
|
4408
4431
|
const to = plungeUrlOf(b.url);
|
|
4409
4432
|
return {
|
|
@@ -4534,6 +4557,7 @@ function stepInput(s) {
|
|
|
4534
4557
|
stack: s.stack,
|
|
4535
4558
|
member: s.project,
|
|
4536
4559
|
...s.engine ? { engine: s.engine } : {},
|
|
4560
|
+
...s.backend ? { backend: s.backend } : {},
|
|
4537
4561
|
...s.engineVersion ? { engineVersion: s.engineVersion, ...s.engineRoot ? { engineRoot: s.engineRoot } : {} } : {},
|
|
4538
4562
|
...s.lockGrain ? { lockGrain: s.lockGrain } : {},
|
|
4539
4563
|
...s.policyPacks?.length ? { policyPacks: s.policyPacks } : {}
|
|
@@ -4774,9 +4798,9 @@ function repoJoin(...parts) {
|
|
|
4774
4798
|
const out = posix2.normalize(parts.filter((p) => p !== "" && p !== ".").join("/") || ".").replace(/^\.\//, "").replace(/\/+$/, "");
|
|
4775
4799
|
return out === "" ? "." : out;
|
|
4776
4800
|
}
|
|
4777
|
-
function projectAt(
|
|
4801
|
+
function projectAt(detected2, dir) {
|
|
4778
4802
|
const want = repoJoin(dir);
|
|
4779
|
-
return
|
|
4803
|
+
return detected2.projects.find((p) => repoJoin(p.dir) === want);
|
|
4780
4804
|
}
|
|
4781
4805
|
function stackEntry(keys, key) {
|
|
4782
4806
|
const stacks2 = isObject(keys.stacks) ? keys.stacks : keys.stacks = {};
|
|
@@ -4819,10 +4843,10 @@ var init_types2 = __esm({
|
|
|
4819
4843
|
// src/import/deployments.ts
|
|
4820
4844
|
import { readdirSync as readdirSync6 } from "node:fs";
|
|
4821
4845
|
import { basename as basename2, dirname as dirname8, join as join11 } from "node:path";
|
|
4822
|
-
function deploymentFiles(root,
|
|
4846
|
+
function deploymentFiles(root, detected2, named) {
|
|
4823
4847
|
if (named.length) return [...named];
|
|
4824
4848
|
const out = [];
|
|
4825
|
-
for (const p of
|
|
4849
|
+
for (const p of detected2.projects) {
|
|
4826
4850
|
let names;
|
|
4827
4851
|
try {
|
|
4828
4852
|
names = readdirSync6(join11(root, p.dir)).sort();
|
|
@@ -4852,7 +4876,7 @@ var init_deployments = __esm({
|
|
|
4852
4876
|
"gitHub.deployPullRequest": "set apply.when: pull-request and update with /plywood up",
|
|
4853
4877
|
"gitHub.repository": "plywood runs in the repository it is set up in"
|
|
4854
4878
|
};
|
|
4855
|
-
importDeployments = (root,
|
|
4879
|
+
importDeployments = (root, detected2, named) => {
|
|
4856
4880
|
let keys = {};
|
|
4857
4881
|
const unsupported = [];
|
|
4858
4882
|
const notes = [];
|
|
@@ -4860,7 +4884,7 @@ var init_deployments = __esm({
|
|
|
4860
4884
|
const not = (file, setting, why) => {
|
|
4861
4885
|
unsupported.push({ file, setting, why: why ?? NOT_CARRIED[setting] ?? "plywood has no setting for it" });
|
|
4862
4886
|
};
|
|
4863
|
-
for (const file of deploymentFiles(root,
|
|
4887
|
+
for (const file of deploymentFiles(root, detected2, named)) {
|
|
4864
4888
|
const m = SETTINGS_FILE.exec(basename2(file));
|
|
4865
4889
|
if (!m) {
|
|
4866
4890
|
not(file, "(file)", "names no stack: call it Pulumi.<stack>.deploy.yaml (or .json) beside the project's Pulumi.yaml");
|
|
@@ -4879,7 +4903,7 @@ var init_deployments = __esm({
|
|
|
4879
4903
|
const fileDir = repoJoin(posixRel(root, dirname8(join11(root, file))));
|
|
4880
4904
|
const source = isObject(settings.sourceContext) && isObject(settings.sourceContext.git) ? settings.sourceContext.git : {};
|
|
4881
4905
|
const dir = typeof source.repoDir === "string" ? repoJoin(source.repoDir) : fileDir;
|
|
4882
|
-
const project = projectAt(
|
|
4906
|
+
const project = projectAt(detected2, dir)?.name ?? projectAt(detected2, fileDir)?.name;
|
|
4883
4907
|
if (!project) {
|
|
4884
4908
|
not(file, "sourceContext.git.repoDir", `${dir} holds no Pulumi.yaml`);
|
|
4885
4909
|
continue;
|
|
@@ -4934,9 +4958,9 @@ var init_deployments = __esm({
|
|
|
4934
4958
|
});
|
|
4935
4959
|
|
|
4936
4960
|
// src/import/env0.ts
|
|
4937
|
-
function stackFor(
|
|
4961
|
+
function stackFor(detected2, names) {
|
|
4938
4962
|
for (const n of names) {
|
|
4939
|
-
const hits =
|
|
4963
|
+
const hits = detected2.stacks.filter((s) => s.key === n || s.stack === n || detected2.stacks.filter((x) => x.project === s.project).length === 1 && s.project === n);
|
|
4940
4964
|
if (hits.length === 1) return hits[0].key;
|
|
4941
4965
|
}
|
|
4942
4966
|
return void 0;
|
|
@@ -4949,7 +4973,7 @@ var init_env0 = __esm({
|
|
|
4949
4973
|
init_types2();
|
|
4950
4974
|
ENV0_WORKFLOW_FILES = ["env0.workflow.yml", "env0.workflow.yaml"];
|
|
4951
4975
|
ENV0_FLOW_FILES = ["env0.yml", "env0.yaml"];
|
|
4952
|
-
importEnv0 = (root,
|
|
4976
|
+
importEnv0 = (root, detected2, named) => {
|
|
4953
4977
|
const keys = {};
|
|
4954
4978
|
const unsupported = [];
|
|
4955
4979
|
const notes = [];
|
|
@@ -4967,7 +4991,7 @@ var init_env0 = __esm({
|
|
|
4967
4991
|
for (const [id, raw] of Object.entries(envs)) {
|
|
4968
4992
|
const e = isObject(raw) ? raw : {};
|
|
4969
4993
|
const names = [id, ...typeof e.name === "string" ? [e.name] : [], ...typeof e.templateName === "string" ? [e.templateName] : []];
|
|
4970
|
-
const key = stackFor(
|
|
4994
|
+
const key = stackFor(detected2, names);
|
|
4971
4995
|
if (key) stackOf.set(id, key);
|
|
4972
4996
|
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` });
|
|
4973
4997
|
for (const k of Object.keys(e).filter((x) => !["name", "templateName", "needs"].includes(x))) {
|
|
@@ -5061,7 +5085,7 @@ var init_pulumi_actions = __esm({
|
|
|
5061
5085
|
plan: "plywood's update applies the preview its approval names, through chant"
|
|
5062
5086
|
};
|
|
5063
5087
|
PULUMI_CLOUD = /^https:\/\/(?:app|api)\.pulumi\.com\/?$/;
|
|
5064
|
-
importPulumiActions = (root,
|
|
5088
|
+
importPulumiActions = (root, detected2, named) => {
|
|
5065
5089
|
let keys = {};
|
|
5066
5090
|
const unsupported = [];
|
|
5067
5091
|
const notes = [];
|
|
@@ -5097,7 +5121,7 @@ var init_pulumi_actions = __esm({
|
|
|
5097
5121
|
const dir = repoJoin(typeof w["work-dir"] === "string" ? w["work-dir"] : ".");
|
|
5098
5122
|
const parts = stackName.split("/");
|
|
5099
5123
|
const stack = parts[parts.length - 1];
|
|
5100
|
-
const project = projectAt(
|
|
5124
|
+
const project = projectAt(detected2, dir)?.name ?? (parts.length === 3 ? parts[1] : void 0);
|
|
5101
5125
|
if (!project) {
|
|
5102
5126
|
unsupported.push({ file, setting: `${where}.with.stack-name`, why: `${dir} holds no Pulumi.yaml, so ${stackName} names no project` });
|
|
5103
5127
|
return;
|
|
@@ -5154,7 +5178,7 @@ var init_spacelift = __esm({
|
|
|
5154
5178
|
init_types2();
|
|
5155
5179
|
SPACELIFT_FILES = [".spacelift/config.yml", ".spacelift/config.yaml"];
|
|
5156
5180
|
CARRIED2 = ["project_root", "runner_image"];
|
|
5157
|
-
importSpacelift = (root,
|
|
5181
|
+
importSpacelift = (root, detected2, named) => {
|
|
5158
5182
|
const keys = {};
|
|
5159
5183
|
const unsupported = [];
|
|
5160
5184
|
const notes = [];
|
|
@@ -5181,7 +5205,7 @@ var init_spacelift = __esm({
|
|
|
5181
5205
|
const own = isObject(raw) ? raw : {};
|
|
5182
5206
|
const s = { ...defaults, ...own };
|
|
5183
5207
|
const dir = repoJoin(typeof s.project_root === "string" ? s.project_root : ".");
|
|
5184
|
-
const project = projectAt(
|
|
5208
|
+
const project = projectAt(detected2, dir);
|
|
5185
5209
|
if (!project) {
|
|
5186
5210
|
unsupported.push({ file, setting: `stacks.${id}.project_root`, why: `${dir} holds no Pulumi.yaml` });
|
|
5187
5211
|
continue;
|
|
@@ -5230,9 +5254,9 @@ function parseFrom(value) {
|
|
|
5230
5254
|
if (!source) return { error: `--from ${value} is not one of ${IMPORT_SOURCES.join(", ")}, and its path names none; give <source>=${value}` };
|
|
5231
5255
|
return { source, files: [file] };
|
|
5232
5256
|
}
|
|
5233
|
-
function runImport(root,
|
|
5257
|
+
function runImport(root, detected2, from) {
|
|
5234
5258
|
try {
|
|
5235
|
-
return IMPORTERS[from.source](root,
|
|
5259
|
+
return IMPORTERS[from.source](root, detected2, from.files);
|
|
5236
5260
|
} catch (e) {
|
|
5237
5261
|
const file = from.files[0] ?? "(its files)";
|
|
5238
5262
|
return { source: from.source, files: [], keys: {}, unsupported: [{ file, setting: "(file)", why: `could not be read: ${e.message}` }], notes: [] };
|
|
@@ -5327,7 +5351,7 @@ function put(root, rel, text, dryRun, note) {
|
|
|
5327
5351
|
function existingConfig(root) {
|
|
5328
5352
|
return CONFIG_NAMES.find((n) => existsSync10(join13(root, n)));
|
|
5329
5353
|
}
|
|
5330
|
-
async function newConfigKeys(
|
|
5354
|
+
async function newConfigKeys(detected2, opts, io) {
|
|
5331
5355
|
const notes = [];
|
|
5332
5356
|
let presetName = opts.preset;
|
|
5333
5357
|
if (!presetName && io.ask && !opts.yes) {
|
|
@@ -5337,31 +5361,31 @@ async function newConfigKeys(detected3, opts, io) {
|
|
|
5337
5361
|
const preset2 = presetByName(presetName ?? DEFAULT_PRESET);
|
|
5338
5362
|
const keys = structuredClone(preset2.keys);
|
|
5339
5363
|
if (opts.gate) keys.gate = opts.gate;
|
|
5340
|
-
const engine2 = engineStep({ engine: opts.engine, detected:
|
|
5364
|
+
const engine2 = engineStep({ engine: opts.engine, detected: detected2, env: io.env });
|
|
5341
5365
|
Object.assign(keys, engine2.keys);
|
|
5342
5366
|
notes.push(...engine2.notes);
|
|
5343
5367
|
const engineBackends = engine2.keys.backend !== void 0 || engine2.keys.stacks !== void 0;
|
|
5344
5368
|
let forge;
|
|
5345
5369
|
if (opts.forge) {
|
|
5346
5370
|
forge = { kind: opts.forge, because: "--forge" };
|
|
5347
|
-
if (
|
|
5348
|
-
else if (
|
|
5349
|
-
} else if (
|
|
5350
|
-
forge = { kind:
|
|
5351
|
-
if (
|
|
5371
|
+
if (detected2.forge?.kind !== opts.forge) keys.forge = { kind: opts.forge };
|
|
5372
|
+
else if (detected2.forge.url) keys.forge = { kind: opts.forge, url: detected2.forge.url };
|
|
5373
|
+
} else if (detected2.forge) {
|
|
5374
|
+
forge = { kind: detected2.forge.kind, because: detected2.forge.because };
|
|
5375
|
+
if (detected2.forge.url) keys.forge = { kind: detected2.forge.kind, url: detected2.forge.url };
|
|
5352
5376
|
} else {
|
|
5353
5377
|
const kind = io.ask && !opts.yes ? await io.ask("Forge", FORGES, "github") : "github";
|
|
5354
5378
|
forge = { kind, because: io.ask && !opts.yes ? "you chose it" : "origin names no forge init knows, so github; change forge.kind in plywood.yml" };
|
|
5355
5379
|
keys.forge = { kind };
|
|
5356
5380
|
}
|
|
5357
5381
|
const fromEnv = io.env.PULUMI_BACKEND_URL?.trim();
|
|
5358
|
-
if (fromEnv && !engineBackends &&
|
|
5382
|
+
if (fromEnv && !engineBackends && detected2.projects.some((p) => !p.backendUrl)) {
|
|
5359
5383
|
keys.backend = fromEnv;
|
|
5360
5384
|
notes.push(`backend ${fromEnv} came from PULUMI_BACKEND_URL; it is in plywood.yml so the jobs use it too`);
|
|
5361
5385
|
}
|
|
5362
5386
|
return { keys, notes, forge };
|
|
5363
5387
|
}
|
|
5364
|
-
function initStacks(
|
|
5388
|
+
function initStacks(detected2, doc, members = []) {
|
|
5365
5389
|
const listed = stacksOf(doc);
|
|
5366
5390
|
const pinned = new Map(members.filter((m) => m.engineBin && m.engineVersion).map((m) => [m.name, m]));
|
|
5367
5391
|
const out = [];
|
|
@@ -5370,6 +5394,7 @@ function initStacks(detected3, doc, members = []) {
|
|
|
5370
5394
|
const own = listed[key] ?? {};
|
|
5371
5395
|
const backend = own.backend ?? (typeof doc.backend === "string" ? doc.backend : void 0);
|
|
5372
5396
|
const engine2 = engineOf(doc, backend)?.engine;
|
|
5397
|
+
const grain = lockGrainFor(doc, engine2);
|
|
5373
5398
|
const stackDir = own.dir ?? dir;
|
|
5374
5399
|
const packs = packsForStack(stepPolicyPacksOf(doc), stackDir);
|
|
5375
5400
|
const pin = pinned.get(key);
|
|
@@ -5379,13 +5404,18 @@ function initStacks(detected3, doc, members = []) {
|
|
|
5379
5404
|
stack,
|
|
5380
5405
|
dir: stackDir,
|
|
5381
5406
|
...engine2 ? { engine: engine2 } : {},
|
|
5407
|
+
// plywood#22: the step's backend, so a preview and an update read this stack's store, not the job's PULUMI_BACKEND_URL.
|
|
5408
|
+
...backend ? { backend } : {},
|
|
5382
5409
|
...pin ? { engineVersion: pin.engineVersion, ...pin.engineRoot ? { engineRoot: pin.engineRoot } : {} } : {},
|
|
5383
5410
|
...own.gate ? { gate: own.gate } : {},
|
|
5384
|
-
...packs.length ? { policyPacks: packs } : {}
|
|
5411
|
+
...packs.length ? { policyPacks: packs } : {},
|
|
5412
|
+
// chant's ledger lock (ruling for ci-products 2026-10-28): each stack's grain on its step, so a plunge stack on the
|
|
5413
|
+
// resource grain leases its resource rows and never waits, and a stock stack (or locks.grain: stack) waits for its stack row.
|
|
5414
|
+
...grain !== "per-engine" ? { lockGrain: LOCK_GRAIN_OF[grain] } : {}
|
|
5385
5415
|
});
|
|
5386
5416
|
seen.add(key);
|
|
5387
5417
|
};
|
|
5388
|
-
for (const s of
|
|
5418
|
+
for (const s of detected2.stacks) add(s.key, s.project, s.stack, s.dir);
|
|
5389
5419
|
for (const [key, own] of Object.entries(listed)) {
|
|
5390
5420
|
if (seen.has(key)) continue;
|
|
5391
5421
|
const [project, stack] = key.split("/");
|
|
@@ -5393,11 +5423,11 @@ function initStacks(detected3, doc, members = []) {
|
|
|
5393
5423
|
}
|
|
5394
5424
|
return out;
|
|
5395
5425
|
}
|
|
5396
|
-
function forgeFor2(doc,
|
|
5426
|
+
function forgeFor2(doc, detected2, opts) {
|
|
5397
5427
|
if (opts.forge) return { kind: opts.forge, because: "--forge" };
|
|
5398
5428
|
const kind = isObject(doc.forge) ? doc.forge.kind : void 0;
|
|
5399
5429
|
if (kind) return { kind, because: "plywood.yml says forge.kind" };
|
|
5400
|
-
if (
|
|
5430
|
+
if (detected2.forge) return { kind: detected2.forge.kind, because: detected2.forge.because };
|
|
5401
5431
|
return { kind: "github", because: "origin names no forge init knows, so github" };
|
|
5402
5432
|
}
|
|
5403
5433
|
async function chantWriteCiHook() {
|
|
@@ -5409,14 +5439,14 @@ async function chantWriteCiHook() {
|
|
|
5409
5439
|
}
|
|
5410
5440
|
}
|
|
5411
5441
|
async function runInit(opts, table, io) {
|
|
5412
|
-
const
|
|
5413
|
-
const { root } =
|
|
5442
|
+
const detected2 = detect(io.cwd);
|
|
5443
|
+
const { root } = detected2;
|
|
5414
5444
|
const files = [];
|
|
5415
5445
|
const notes = [];
|
|
5416
5446
|
const todo = [];
|
|
5417
5447
|
const buckets = [];
|
|
5418
5448
|
const adopt = [];
|
|
5419
|
-
const imported = opts.from ? runImport(root,
|
|
5449
|
+
const imported = opts.from ? runImport(root, detected2, opts.from) : void 0;
|
|
5420
5450
|
if (imported && imported.files.length === 0) notes.push(`--from ${imported.source} found no file to read${imported.notes.length ? `: ${imported.notes.join("; ")}` : ""}`);
|
|
5421
5451
|
const fail = (message, forge2 = { kind: "github", because: "" }) => ({
|
|
5422
5452
|
ok: false,
|
|
@@ -5431,8 +5461,8 @@ async function runInit(opts, table, io) {
|
|
|
5431
5461
|
adopt,
|
|
5432
5462
|
...imported ? { imported } : {}
|
|
5433
5463
|
});
|
|
5434
|
-
if (
|
|
5435
|
-
if (
|
|
5464
|
+
if (detected2.projects.length === 0) return fail(`no Pulumi.yaml under ${root}; plywood init sets up a repository that has a Pulumi program`);
|
|
5465
|
+
if (detected2.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");
|
|
5436
5466
|
let config2;
|
|
5437
5467
|
let forge;
|
|
5438
5468
|
const kept = existingConfig(root);
|
|
@@ -5446,10 +5476,10 @@ async function runInit(opts, table, io) {
|
|
|
5446
5476
|
const onPlunge = opts.engine === "plunge" && engineOf(config2.doc)?.engine === "plunge";
|
|
5447
5477
|
const ignored = [opts.preset && "--preset", opts.gate && "--gate", opts.engine && !onPlunge && "--engine", imported && "--from"].filter(Boolean);
|
|
5448
5478
|
if (ignored.length) notes.push(`${kept} is kept as it is, so ${ignored.join(", ")} changed nothing; set the keys there`);
|
|
5449
|
-
if (opts.engine === "plunge" && !onPlunge) notes.push(...keptPlungeLines(kept,
|
|
5450
|
-
forge = forgeFor2(config2.doc,
|
|
5479
|
+
if (opts.engine === "plunge" && !onPlunge) notes.push(...keptPlungeLines(kept, detected2, io.env));
|
|
5480
|
+
forge = forgeFor2(config2.doc, detected2, opts);
|
|
5451
5481
|
} else {
|
|
5452
|
-
const made = await newConfigKeys(
|
|
5482
|
+
const made = await newConfigKeys(detected2, opts, io);
|
|
5453
5483
|
notes.push(...made.notes);
|
|
5454
5484
|
forge = made.forge;
|
|
5455
5485
|
const text = plywoodYml(imported ? mergeKeys(made.keys, imported.keys) : made.keys);
|
|
@@ -5465,12 +5495,12 @@ async function runInit(opts, table, io) {
|
|
|
5465
5495
|
const reviewStacks2 = config2.doc.review_stacks !== void 0 ? await chantReviewStacks() : false;
|
|
5466
5496
|
if (settings.ci.prEnvironment && !reviewStacks2) delete settings.ci.prEnvironment;
|
|
5467
5497
|
const reviewSuffix = reviewStacks2 ? reviewSuffixOf(settings.pipeline.reviewStackName) : void 0;
|
|
5468
|
-
const stacks2 = initStacks(
|
|
5498
|
+
const stacks2 = initStacks(detected2, doc, settings.members);
|
|
5469
5499
|
const order = await stackOrder(root, stacks2, settings.pipeline.after);
|
|
5470
5500
|
const ordered = orderWaves(wavesOf(stacks2, settings.pipeline.waves), order.edges, settings.pipeline.canary);
|
|
5471
5501
|
const waves = ordered.waves;
|
|
5472
5502
|
if (order.edges.length || order.outside.length || ordered.unmet.length || !order.reads && stacks2.length > 1) notes.push(...orderLines(order, ordered.unmet));
|
|
5473
|
-
const declaration = declarationStep(root,
|
|
5503
|
+
const declaration = declarationStep(root, detected2, settings, opts.dryRun);
|
|
5474
5504
|
files.push(declaration.file);
|
|
5475
5505
|
if (existsSync10(join13(root, CHANT_CONFIG_FILE))) {
|
|
5476
5506
|
const text = readFileSync12(join13(root, CHANT_CONFIG_FILE), "utf8");
|
|
@@ -5480,8 +5510,8 @@ async function runInit(opts, table, io) {
|
|
|
5480
5510
|
} else files.push(put(root, CHANT_CONFIG_FILE, chantConfigTs(), opts.dryRun));
|
|
5481
5511
|
files.push(put(root, COMPONENTS_FILE, componentsFile(stacks2, settings.pipeline.after, reviewSuffix ? { reviewSuffix } : {}), opts.dryRun));
|
|
5482
5512
|
files.push(...providersPresetStep(root, doc, opts.dryRun, notes));
|
|
5483
|
-
if (
|
|
5484
|
-
for (const wf of
|
|
5513
|
+
if (detected2.preCommit) files.push(await preCommitStep(root, detected2.preCommit, opts.dryRun));
|
|
5514
|
+
for (const wf of detected2.pulumiActions) {
|
|
5485
5515
|
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`);
|
|
5486
5516
|
}
|
|
5487
5517
|
const chant = await chantProduct();
|
|
@@ -5489,7 +5519,7 @@ async function runInit(opts, table, io) {
|
|
|
5489
5519
|
const message = needsChantMessage("Writing the waves spec and the workflow");
|
|
5490
5520
|
files.push({ file: WAVES_SPEC_FILE2, status: "skipped", note: message }, { file: WORKFLOW_FILE[forge.kind], status: "skipped", note: "needs the same chant" });
|
|
5491
5521
|
} else {
|
|
5492
|
-
let spec = wavesSpecStep(chant, stacks2, doc, settings,
|
|
5522
|
+
let spec = wavesSpecStep(chant, stacks2, doc, settings, detected2.defaultBranch, waves);
|
|
5493
5523
|
if (settings.pipeline.jobs) {
|
|
5494
5524
|
try {
|
|
5495
5525
|
spec = withJobSettings(spec, settings.pipeline.jobs, forge.kind, { parallelism: await hasChant4625() });
|
|
@@ -5498,12 +5528,15 @@ async function runInit(opts, table, io) {
|
|
|
5498
5528
|
spec = void 0;
|
|
5499
5529
|
}
|
|
5500
5530
|
}
|
|
5501
|
-
if (spec && stacks2.length > 0)
|
|
5531
|
+
if (spec && stacks2.length > 0) {
|
|
5532
|
+
files.push(put(root, WAVES_SPEC_FILE2, wavesSpecText(spec, wavesEngine(settings.pipeline)), opts.dryRun));
|
|
5533
|
+
files.push(put(root, UP_OP_FILE, upOpFile(spec), opts.dryRun));
|
|
5534
|
+
}
|
|
5502
5535
|
if (spec) files.push(...await workflowStep(chant, root, doc, settings, forge.kind, opts, stacks2.length > 0 ? spec : void 0, table, io));
|
|
5503
5536
|
}
|
|
5504
|
-
if (opts.bucketCheck) notes.push(...await bucketStep(
|
|
5505
|
-
adopt.push(...await adoptStep(root,
|
|
5506
|
-
todo.push(...todoLines(
|
|
5537
|
+
if (opts.bucketCheck) notes.push(...await bucketStep(detected2, doc, io.env, buckets));
|
|
5538
|
+
adopt.push(...await adoptStep(root, detected2, doc, io.env, declaration.doc, opts.dryRun, files, notes, todo));
|
|
5539
|
+
todo.push(...todoLines(detected2, doc, forge.kind, io.env));
|
|
5507
5540
|
const failed = files.find((f) => f.status === "failed");
|
|
5508
5541
|
return {
|
|
5509
5542
|
ok: !failed,
|
|
@@ -5519,9 +5552,9 @@ async function runInit(opts, table, io) {
|
|
|
5519
5552
|
...imported ? { imported } : {}
|
|
5520
5553
|
};
|
|
5521
5554
|
}
|
|
5522
|
-
function declarationStep(root,
|
|
5555
|
+
function declarationStep(root, detected2, settings, dryRun) {
|
|
5523
5556
|
if (existsSync10(join13(root, "chant.workspace.jsonc"))) {
|
|
5524
|
-
return { file: { file: "chant.workspace.jsonc", status: "kept", note: `add a member of kind pulumi for each of ${
|
|
5557
|
+
return { file: { file: "chant.workspace.jsonc", status: "kept", note: `add a member of kind pulumi for each of ${detected2.projects.map((p) => p.dir).join(", ")}, and pin ${PULUMI_LEXICON}@${CHANT_PIN}` } };
|
|
5525
5558
|
}
|
|
5526
5559
|
let existing;
|
|
5527
5560
|
const file = join13(root, DECLARATION_FILE);
|
|
@@ -5534,7 +5567,7 @@ function declarationStep(root, detected3, settings, dryRun) {
|
|
|
5534
5567
|
return { file: { file: DECLARATION_FILE, status: "failed", note: `is not JSON init can merge into (${e.message})` } };
|
|
5535
5568
|
}
|
|
5536
5569
|
}
|
|
5537
|
-
const doc = workspaceDeclaration(existing, { name: repoName(root), projects:
|
|
5570
|
+
const doc = workspaceDeclaration(existing, { name: repoName(root), projects: detected2.projects, ci: settings.ci, lexiconVersion: CHANT_PIN });
|
|
5538
5571
|
if (existing && JSON.stringify(existing) === JSON.stringify(doc)) return { file: { file: DECLARATION_FILE, status: "unchanged" }, doc };
|
|
5539
5572
|
return { file: put(root, DECLARATION_FILE, jsonText(doc), dryRun), doc };
|
|
5540
5573
|
}
|
|
@@ -5589,7 +5622,7 @@ async function workflowStep(chant, root, doc, settings, forge, opts, spec, table
|
|
|
5589
5622
|
try {
|
|
5590
5623
|
const { noteControlTarget: noteControlTarget2 } = await Promise.resolve().then(() => (init_control2(), control_exports));
|
|
5591
5624
|
const control3 = await noteControlTarget2(doc, root);
|
|
5592
|
-
plan = workflowPlan(chant, doc, forge, opts.env, spec, chantBuildFlags(chantPipelineOptions(settings.pipeline, false)), { reviewStacks: await chantReviewStacks(), prJobs,
|
|
5625
|
+
plan = workflowPlan(chant, doc, forge, opts.env, spec, chantBuildFlags(chantPipelineOptions(settings.pipeline, false)), { reviewStacks: await chantReviewStacks(), prJobs, ...control3 ? { control: control3 } : {} });
|
|
5593
5626
|
} catch (e) {
|
|
5594
5627
|
return [{ file: output, status: "failed", note: e.message }];
|
|
5595
5628
|
}
|
|
@@ -5606,16 +5639,16 @@ async function workflowStep(chant, root, doc, settings, forge, opts, spec, table
|
|
|
5606
5639
|
];
|
|
5607
5640
|
const out = [{ file: output, status: written.status, ...notes.length ? { note: notes.join("; ") } : {} }];
|
|
5608
5641
|
if (plan.jobs.waves) {
|
|
5609
|
-
const code = await writeWaveJobs(chant, forge, plan.jobs.waves, plan.words, table, { ...io, cwd: root });
|
|
5642
|
+
const code = await writeWaveJobs(chant, forge, plan.jobs.waves, plan.words, table, { ...io, cwd: root }, wavesEngine(settings.pipeline));
|
|
5610
5643
|
if (code !== table.done) out.push({ file: WAVES_OUTPUT_DIR[forge], status: "failed", note: `the waves workflow: chant exited ${code}` });
|
|
5611
5644
|
}
|
|
5612
5645
|
return out;
|
|
5613
5646
|
}
|
|
5614
|
-
async function bucketStep(
|
|
5647
|
+
async function bucketStep(detected2, doc, env, reports) {
|
|
5615
5648
|
const listed = stacksOf(doc);
|
|
5616
5649
|
const urls = /* @__PURE__ */ new Set();
|
|
5617
|
-
for (const s of
|
|
5618
|
-
const project =
|
|
5650
|
+
for (const s of detected2.stacks) {
|
|
5651
|
+
const project = detected2.projects.find((p) => p.dir === s.dir && p.name === s.project) ?? { backendUrl: void 0 };
|
|
5619
5652
|
const b = backendOf(project, { ...listed[s.key]?.backend ? { stack: listed[s.key].backend } : {}, ...typeof doc.backend === "string" ? { all: doc.backend } : {} }, env);
|
|
5620
5653
|
if (b.url && !isPulumiCloud(b.url)) urls.add(b.url);
|
|
5621
5654
|
}
|
|
@@ -5639,12 +5672,12 @@ async function bucketStep(detected3, doc, env, reports) {
|
|
|
5639
5672
|
}
|
|
5640
5673
|
return lines2;
|
|
5641
5674
|
}
|
|
5642
|
-
async function adoptStep(root,
|
|
5675
|
+
async function adoptStep(root, detected2, doc, env, declaration, dryRun, files, notes, todo) {
|
|
5643
5676
|
const listed = stacksOf(doc);
|
|
5644
5677
|
const all = typeof doc.backend === "string" ? doc.backend : void 0;
|
|
5645
5678
|
const targets = [];
|
|
5646
|
-
for (const s of
|
|
5647
|
-
const project =
|
|
5679
|
+
for (const s of detected2.stacks) {
|
|
5680
|
+
const project = detected2.projects.find((p) => p.dir === s.dir && p.name === s.project) ?? { backendUrl: void 0 };
|
|
5648
5681
|
const own = listed[s.key];
|
|
5649
5682
|
const b = backendOf(project, { ...own?.backend ? { stack: own.backend } : {}, ...all ? { all } : {} }, env);
|
|
5650
5683
|
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 });
|
|
@@ -5667,17 +5700,17 @@ async function adoptStep(root, detected3, doc, env, declaration, dryRun, files,
|
|
|
5667
5700
|
todo.push(...adoptTodo(rows));
|
|
5668
5701
|
return previews;
|
|
5669
5702
|
}
|
|
5670
|
-
function keptPlungeLines(kept,
|
|
5671
|
-
const step = engineStep({ engine: "plunge", detected:
|
|
5703
|
+
function keptPlungeLines(kept, detected2, env) {
|
|
5704
|
+
const step = engineStep({ engine: "plunge", detected: detected2, env });
|
|
5672
5705
|
const entries = Object.entries(step.keys);
|
|
5673
5706
|
if (entries.length === 0) return step.notes;
|
|
5674
5707
|
const yaml = entries.map(([k, v]) => emitYAMLEntry2(k, v)).join("\n").split("\n").filter((l) => l !== "");
|
|
5675
5708
|
return [...step.notes, `To move to plunge, set these keys in ${kept}:`, ...yaml.map((l) => ` ${l}`)];
|
|
5676
5709
|
}
|
|
5677
|
-
function todoLines(
|
|
5710
|
+
function todoLines(detected2, doc, forge, env) {
|
|
5678
5711
|
const out = [];
|
|
5679
5712
|
const all = typeof doc.backend === "string" ? doc.backend : void 0;
|
|
5680
|
-
const cloud =
|
|
5713
|
+
const cloud = detected2.projects.some((p) => isPulumiCloud(backendOf(p, all ? { all } : {}, env).url));
|
|
5681
5714
|
const where = forge === "gitlab" ? "CI/CD variables" : "repository secrets";
|
|
5682
5715
|
const traded = identityClouds(doc);
|
|
5683
5716
|
if (cloud && traded.includes("pulumi")) {
|
|
@@ -5685,9 +5718,9 @@ function todoLines(detected3, doc, forge, env) {
|
|
|
5685
5718
|
} else if (cloud) {
|
|
5686
5719
|
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}`);
|
|
5687
5720
|
}
|
|
5688
|
-
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 ${
|
|
5721
|
+
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 ${detected2.defaultBranch} only`);
|
|
5689
5722
|
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");
|
|
5690
|
-
out.push(`Make ${PRODUCT}/preview a required status on ${
|
|
5723
|
+
out.push(`Make ${PRODUCT}/preview a required status on ${detected2.defaultBranch}`);
|
|
5691
5724
|
return out;
|
|
5692
5725
|
}
|
|
5693
5726
|
function initLines(r) {
|
|
@@ -5732,6 +5765,7 @@ var init_init = __esm({
|
|
|
5732
5765
|
init_policy_presets();
|
|
5733
5766
|
init_approval();
|
|
5734
5767
|
init_engine();
|
|
5768
|
+
init_locks();
|
|
5735
5769
|
init_config();
|
|
5736
5770
|
init_routing();
|
|
5737
5771
|
init_stacks();
|
|
@@ -5748,7 +5782,6 @@ var init_init = __esm({
|
|
|
5748
5782
|
init_review_stacks2();
|
|
5749
5783
|
init_waves_workflow();
|
|
5750
5784
|
init_stages();
|
|
5751
|
-
init_chant_next();
|
|
5752
5785
|
init_stack_order();
|
|
5753
5786
|
init_import();
|
|
5754
5787
|
init_templates();
|
|
@@ -6132,7 +6165,6 @@ async function check(args, json, table, io) {
|
|
|
6132
6165
|
}
|
|
6133
6166
|
async function preview(args, json, table, io) {
|
|
6134
6167
|
const targeted = args.includes("--target");
|
|
6135
|
-
const planFlags = ["--refresh", "--target-dependents"].filter((f) => args.includes(f));
|
|
6136
6168
|
if (args.includes("--target-dependents") && !targeted) return usage("--target-dependents widens a --target preview to the resources that depend on the targets; give a --target", table, io);
|
|
6137
6169
|
for (let i = 0; i < args.length; i++) {
|
|
6138
6170
|
if (args[i] === "--target" && (args[i + 1] === void 0 || args[i + 1].startsWith("-"))) return usage("--target takes a resource's URN", table, io);
|
|
@@ -6155,16 +6187,12 @@ async function preview(args, json, table, io) {
|
|
|
6155
6187
|
const argv = ["components", "pr-plan", "--base", at, "--pr", number, ...gate, ...stack ? ["--member", stack] : [], ...rest, ...json ? ["--json"] : []];
|
|
6156
6188
|
return withChant(`${PRODUCT} preview`, table, io, async (chant) => {
|
|
6157
6189
|
if (targeted && !hasChant4620(chant)) return usage(PREVIEW_TARGET_NEEDS_CHANT, table, io);
|
|
6158
|
-
if (planFlags.length && !(await chantNext()).planFlags) return usage(previewFlagsNeedChant(planFlags), table, io);
|
|
6159
6190
|
return chant.runChantInProcess(argv, PRODUCT_WORDS);
|
|
6160
6191
|
});
|
|
6161
6192
|
}
|
|
6162
|
-
function previewFlagsNeedChant(flags) {
|
|
6163
|
-
return `${flags.join(" and ")} ${flags.length === 1 ? "needs" : "need"} a chant whose pr-plan passes ${flags.length === 1 ? "it" : "them"} to the pulumi-up step (chant#4627), which this chant lacks`;
|
|
6164
|
-
}
|
|
6165
6193
|
function readWavesSpec(cwd) {
|
|
6166
6194
|
const file = join15(cwd, ".chant/op-waves", `${OP_NAME}.spec.json`);
|
|
6167
|
-
return existsSync12(file) ? JSON.parse(readFileSync14(file, "utf8")) : void 0;
|
|
6195
|
+
return existsSync12(file) ? unwrapWavesSpec(JSON.parse(readFileSync14(file, "utf8"))) : void 0;
|
|
6168
6196
|
}
|
|
6169
6197
|
function pipelineShape(doc, forge, waves) {
|
|
6170
6198
|
const applyBeforeMerge = applyWhenOf(doc) === "pull-request";
|
|
@@ -6225,7 +6253,7 @@ async function ci(args, json, table, io) {
|
|
|
6225
6253
|
const noteControl = await noteControlTarget2(config2.doc, io.cwd);
|
|
6226
6254
|
let plan;
|
|
6227
6255
|
try {
|
|
6228
|
-
plan = workflowPlan(chant, config2.doc, forge, env, waves, [], { reviewStacks: reviewStacks2, prJobs,
|
|
6256
|
+
plan = workflowPlan(chant, config2.doc, forge, env, waves, [], { reviewStacks: reviewStacks2, prJobs, ...noteControl ? { control: noteControl } : {} });
|
|
6229
6257
|
} catch (e) {
|
|
6230
6258
|
io.err(`${PRODUCT}: ${e.message}`);
|
|
6231
6259
|
return table.failed;
|
|
@@ -6274,7 +6302,7 @@ async function ci(args, json, table, io) {
|
|
|
6274
6302
|
if (!json) io.err(`${PRODUCT} ci: chant's generator exited ${written.code} and wrote no ${output}; any file there before is back as it was`);
|
|
6275
6303
|
return written.code;
|
|
6276
6304
|
}
|
|
6277
|
-
const wavesCode = waveJobs ? await writeWaveJobs(chant, forge, waveJobs, plan.words, table, io) : table.done;
|
|
6305
|
+
const wavesCode = waveJobs ? await writeWaveJobs(chant, forge, waveJobs, plan.words, table, io, wavesEngine(toChantSettings(config2).pipeline)) : table.done;
|
|
6278
6306
|
if (wavesCode !== table.done) return wavesCode;
|
|
6279
6307
|
const driftCode = await writeDriftJobs(chant, forge, config2.doc, plan.words, table, io, env);
|
|
6280
6308
|
if (driftCode !== table.done) return driftCode;
|
|
@@ -6289,12 +6317,10 @@ function writtenNotes(plan, file) {
|
|
|
6289
6317
|
}
|
|
6290
6318
|
function workflowPlan(chant, doc, forge, env, waves, extra = [], opts = {}) {
|
|
6291
6319
|
const shape = pipelineShape(doc, forge, waves);
|
|
6292
|
-
const next = opts.next;
|
|
6293
6320
|
const notes = [];
|
|
6294
6321
|
const pipeline = toChantSettings({ doc }).pipeline;
|
|
6295
|
-
const staleNotes = next?.staleNotes === true;
|
|
6296
6322
|
const respond = respondOptions(chant, pipeline, shape.applyBeforeMerge, notes);
|
|
6297
|
-
const applyMerge = shape.applyBeforeMerge
|
|
6323
|
+
const applyMerge = shape.applyBeforeMerge ? pipeline.applyMerge : void 0;
|
|
6298
6324
|
if (approvalOf(doc) === "pr-review" && shape.waves && hasChantApprovalStatus(chant) && !hasChantPullRequestApprovalStatus(chant)) notes.push(APPROVAL_STATUS_NOTE);
|
|
6299
6325
|
const words = wordsForJobs(shape.jobs);
|
|
6300
6326
|
const requires = shape.applyBeforeMerge ? requiresArgv(chant, doc) : [];
|
|
@@ -6310,7 +6336,8 @@ function workflowPlan(chant, doc, forge, env, waves, extra = [], opts = {}) {
|
|
|
6310
6336
|
...opts.control !== void 0 ? { control: { target: opts.control } } : {},
|
|
6311
6337
|
// plywood#29: runners, env, pass and parallelism on the pull request jobs, with chant#4625 (src/jobs.ts).
|
|
6312
6338
|
...opts.prJobs?.options ?? {},
|
|
6313
|
-
|
|
6339
|
+
// chant#4628: the note names the target commit it was previewed against, and a push marks older notes stale.
|
|
6340
|
+
staleNotes: true,
|
|
6314
6341
|
...respond.options,
|
|
6315
6342
|
...applyMerge ? { applyMerge } : {}
|
|
6316
6343
|
});
|
|
@@ -6330,7 +6357,7 @@ function workflowPlan(chant, doc, forge, env, waves, extra = [], opts = {}) {
|
|
|
6330
6357
|
...applyMerge ? applyMergeArgv(applyMerge) : [],
|
|
6331
6358
|
...respond.argv,
|
|
6332
6359
|
...previewComment ? ["--preview-comment"] : [],
|
|
6333
|
-
|
|
6360
|
+
"--stale-notes",
|
|
6334
6361
|
// Review stacks (plywood#15): chant's pr-env jobs, only once the pulumi lexicon can remove what they create.
|
|
6335
6362
|
...doc.review_stacks !== void 0 && opts.reviewStacks ? ["--pr-env"] : [],
|
|
6336
6363
|
...opts.prJobs?.flags ?? [],
|
|
@@ -6387,7 +6414,7 @@ function usage(message, table, io) {
|
|
|
6387
6414
|
io.err(`${PRODUCT}: ${message}`);
|
|
6388
6415
|
return table.failed;
|
|
6389
6416
|
}
|
|
6390
|
-
var CHANT_PASSTHROUGH, VALUED, PREVIEW_TARGET_NEEDS_CHANT, WORKFLOW_FILE,
|
|
6417
|
+
var CHANT_PASSTHROUGH, VALUED, PREVIEW_TARGET_NEEDS_CHANT, WORKFLOW_FILE, APPROVAL_STATUS_NOTE, CHANT_RESPOND_DEFAULTS, DECIDE_LEFT_OFF;
|
|
6391
6418
|
var init_stages = __esm({
|
|
6392
6419
|
"src/stages.ts"() {
|
|
6393
6420
|
"use strict";
|
|
@@ -6415,13 +6442,37 @@ var init_stages = __esm({
|
|
|
6415
6442
|
forgejo: ".forgejo/workflows/plywood.yml",
|
|
6416
6443
|
gitlab: ".gitlab-ci.yml"
|
|
6417
6444
|
};
|
|
6418
|
-
APPLY_MERGE_NEEDS_CHANT = "apply.merge is auto, and the installed chant has no pr-apply --merge auto (chant#4636), so a person merges after each update before merge";
|
|
6419
6445
|
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`;
|
|
6420
6446
|
CHANT_RESPOND_DEFAULTS = Object.freeze({ "wave-refused": "diff", "apply-failed": "triage", description: "off" });
|
|
6421
6447
|
DECIDE_LEFT_OFF = "decide is set and respond.description is not check, so plywood leaves the decide service off the pull request jobs";
|
|
6422
6448
|
}
|
|
6423
6449
|
});
|
|
6424
6450
|
|
|
6451
|
+
// src/chant-resolve.ts
|
|
6452
|
+
import * as nodeModule from "node:module";
|
|
6453
|
+
var CHANT_SPECIFIER = /^@intentius\/chant(?:-lexicon-[a-z0-9-]+)?(?:\/|$)/;
|
|
6454
|
+
function chantResolveHook(from) {
|
|
6455
|
+
return (specifier, context, next) => {
|
|
6456
|
+
try {
|
|
6457
|
+
return next(specifier, context);
|
|
6458
|
+
} catch (err) {
|
|
6459
|
+
const code = err.code;
|
|
6460
|
+
if (!CHANT_SPECIFIER.test(specifier) || code !== "ERR_MODULE_NOT_FOUND" && code !== "MODULE_NOT_FOUND") throw err;
|
|
6461
|
+
if (context.parentURL === from) throw err;
|
|
6462
|
+
return next(specifier, { ...context, parentURL: from });
|
|
6463
|
+
}
|
|
6464
|
+
};
|
|
6465
|
+
}
|
|
6466
|
+
var installed = false;
|
|
6467
|
+
function installChantResolution(from = import.meta.url) {
|
|
6468
|
+
if (installed) return true;
|
|
6469
|
+
const register = nodeModule.registerHooks;
|
|
6470
|
+
if (typeof register !== "function") return false;
|
|
6471
|
+
register({ resolve: chantResolveHook(from) });
|
|
6472
|
+
installed = true;
|
|
6473
|
+
return true;
|
|
6474
|
+
}
|
|
6475
|
+
|
|
6425
6476
|
// src/cli.ts
|
|
6426
6477
|
init_config_check();
|
|
6427
6478
|
|
|
@@ -6668,8 +6719,8 @@ var HOOK_COMMAND = 'node "$CLAUDE_PROJECT_DIR/.claude/hooks/plywood-guard.mjs"';
|
|
|
6668
6719
|
function withGuardHook(settings) {
|
|
6669
6720
|
const hooks = typeof settings.hooks === "object" && settings.hooks !== null ? settings.hooks : {};
|
|
6670
6721
|
const pre = Array.isArray(hooks.PreToolUse) ? [...hooks.PreToolUse] : [];
|
|
6671
|
-
const
|
|
6672
|
-
if (!
|
|
6722
|
+
const has = JSON.stringify(pre).includes("plywood-guard.mjs");
|
|
6723
|
+
if (!has) pre.push({ matcher: "Bash", hooks: [{ type: "command", command: HOOK_COMMAND }] });
|
|
6673
6724
|
return { ...settings, hooks: { ...hooks, PreToolUse: pre } };
|
|
6674
6725
|
}
|
|
6675
6726
|
async function guard(args, json, table, io) {
|
|
@@ -7147,11 +7198,11 @@ async function upgrade(args, json, table, io) {
|
|
|
7147
7198
|
return table.done;
|
|
7148
7199
|
}
|
|
7149
7200
|
say(` $ ${steps.install.command}`);
|
|
7150
|
-
const
|
|
7151
|
-
if (
|
|
7201
|
+
const installed2 = await exec("npm", ["install", "--no-audit", "--no-fund"], { cwd: pkgDir, env: io.env });
|
|
7202
|
+
if (installed2.code !== 0) {
|
|
7152
7203
|
for (const n of notes) say(` note: ${n}`);
|
|
7153
|
-
return fail(`npm install failed (exit ${
|
|
7154
|
-
${
|
|
7204
|
+
return fail(`npm install failed (exit ${installed2.code}); package.json is moved, so fix the install and run '${steps.ci.command}'
|
|
7205
|
+
${installed2.stderr.trim()}`);
|
|
7155
7206
|
}
|
|
7156
7207
|
const got = installedVersion(pkgDir);
|
|
7157
7208
|
if (got !== void 0 && got !== release2.version) notes.push(`node_modules holds ${PLYWOOD_PACKAGE} ${got}, not ${release2.version}; check the install`);
|
|
@@ -7554,7 +7605,7 @@ async function rollbackStack(args, json, table, io) {
|
|
|
7554
7605
|
|
|
7555
7606
|
// src/cli.ts
|
|
7556
7607
|
init_words();
|
|
7557
|
-
var VERSION3 = true ? "0.
|
|
7608
|
+
var VERSION3 = true ? "0.1.0" : "0.0.0-dev";
|
|
7558
7609
|
var stdio = {
|
|
7559
7610
|
out: (s) => process.stdout.write(s + "\n"),
|
|
7560
7611
|
err: (s) => process.stderr.write(s + "\n")
|
|
@@ -7750,5 +7801,6 @@ function config(args, table, emit) {
|
|
|
7750
7801
|
}
|
|
7751
7802
|
|
|
7752
7803
|
// src/bin.ts
|
|
7804
|
+
installChantResolution();
|
|
7753
7805
|
process.exitCode = await main(process.argv.slice(2));
|
|
7754
7806
|
//# sourceMappingURL=plywood.mjs.map
|