@intentius/behold 0.28.0 → 0.29.1
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/README.md +27 -0
- package/dist/cli.js +570 -262
- package/package.json +1 -1
- package/web/app.js +141 -19
- package/web/kiosk.html +67 -0
- package/web/kiosk.js +385 -0
- package/web/operator.js +1 -0
- package/web/run-playhead.js +1 -0
- package/web/terragucci-change.js +423 -0
package/dist/cli.js
CHANGED
|
@@ -5828,10 +5828,10 @@ var require_utils = __commonJS({
|
|
|
5828
5828
|
REGEX_SPECIAL_CHARS_GLOBAL
|
|
5829
5829
|
} = require_constants();
|
|
5830
5830
|
exports.isObject = (val) => val !== null && typeof val === "object" && !Array.isArray(val);
|
|
5831
|
-
exports.hasRegexChars = (
|
|
5832
|
-
exports.isRegexChar = (
|
|
5833
|
-
exports.escapeRegex = (
|
|
5834
|
-
exports.toPosixSlashes = (
|
|
5831
|
+
exports.hasRegexChars = (str16) => REGEX_SPECIAL_CHARS.test(str16);
|
|
5832
|
+
exports.isRegexChar = (str16) => str16.length === 1 && exports.hasRegexChars(str16);
|
|
5833
|
+
exports.escapeRegex = (str16) => str16.replace(REGEX_SPECIAL_CHARS_GLOBAL, "\\$1");
|
|
5834
|
+
exports.toPosixSlashes = (str16) => str16.replace(REGEX_BACKSLASH, "/");
|
|
5835
5835
|
exports.isWindows = () => {
|
|
5836
5836
|
if (typeof navigator !== "undefined" && navigator.platform) {
|
|
5837
5837
|
const platform2 = navigator.platform.toLowerCase();
|
|
@@ -5842,8 +5842,8 @@ var require_utils = __commonJS({
|
|
|
5842
5842
|
}
|
|
5843
5843
|
return false;
|
|
5844
5844
|
};
|
|
5845
|
-
exports.removeBackslashes = (
|
|
5846
|
-
return
|
|
5845
|
+
exports.removeBackslashes = (str16) => {
|
|
5846
|
+
return str16.replace(REGEX_REMOVE_BACKSLASH, (match) => {
|
|
5847
5847
|
return match === "\\" ? "" : match;
|
|
5848
5848
|
});
|
|
5849
5849
|
};
|
|
@@ -5933,7 +5933,7 @@ var require_scan = __commonJS({
|
|
|
5933
5933
|
const slashes = [];
|
|
5934
5934
|
const tokens = [];
|
|
5935
5935
|
const parts = [];
|
|
5936
|
-
let
|
|
5936
|
+
let str16 = input;
|
|
5937
5937
|
let index = -1;
|
|
5938
5938
|
let start = 0;
|
|
5939
5939
|
let lastIndex = 0;
|
|
@@ -5952,10 +5952,10 @@ var require_scan = __commonJS({
|
|
|
5952
5952
|
let code;
|
|
5953
5953
|
let token = { value: "", depth: 0, isGlob: false };
|
|
5954
5954
|
const eos = () => index >= length;
|
|
5955
|
-
const peek = () =>
|
|
5955
|
+
const peek = () => str16.charCodeAt(index + 1);
|
|
5956
5956
|
const advance = () => {
|
|
5957
5957
|
prev = code;
|
|
5958
|
-
return
|
|
5958
|
+
return str16.charCodeAt(++index);
|
|
5959
5959
|
};
|
|
5960
5960
|
while (index < length) {
|
|
5961
5961
|
code = advance();
|
|
@@ -6123,24 +6123,24 @@ var require_scan = __commonJS({
|
|
|
6123
6123
|
isExtglob = false;
|
|
6124
6124
|
isGlob = false;
|
|
6125
6125
|
}
|
|
6126
|
-
let base =
|
|
6126
|
+
let base = str16;
|
|
6127
6127
|
let prefix = "";
|
|
6128
6128
|
let glob = "";
|
|
6129
6129
|
if (start > 0) {
|
|
6130
|
-
prefix =
|
|
6131
|
-
|
|
6130
|
+
prefix = str16.slice(0, start);
|
|
6131
|
+
str16 = str16.slice(start);
|
|
6132
6132
|
lastIndex -= start;
|
|
6133
6133
|
}
|
|
6134
6134
|
if (base && isGlob === true && lastIndex > 0) {
|
|
6135
|
-
base =
|
|
6136
|
-
glob =
|
|
6135
|
+
base = str16.slice(0, lastIndex);
|
|
6136
|
+
glob = str16.slice(lastIndex);
|
|
6137
6137
|
} else if (isGlob === true) {
|
|
6138
6138
|
base = "";
|
|
6139
|
-
glob =
|
|
6139
|
+
glob = str16;
|
|
6140
6140
|
} else {
|
|
6141
|
-
base =
|
|
6141
|
+
base = str16;
|
|
6142
6142
|
}
|
|
6143
|
-
if (base && base !== "" && base !== "/" && base !==
|
|
6143
|
+
if (base && base !== "" && base !== "/" && base !== str16) {
|
|
6144
6144
|
if (isPathSeparator(base.charCodeAt(base.length - 1))) {
|
|
6145
6145
|
base = base.slice(0, -1);
|
|
6146
6146
|
}
|
|
@@ -7187,8 +7187,8 @@ var require_parse = __commonJS({
|
|
|
7187
7187
|
if (opts2.noglobstar === true) return star;
|
|
7188
7188
|
return `(${capture2}(?:(?!${START_ANCHOR}${opts2.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`;
|
|
7189
7189
|
};
|
|
7190
|
-
const create = (
|
|
7191
|
-
switch (
|
|
7190
|
+
const create = (str16) => {
|
|
7191
|
+
switch (str16) {
|
|
7192
7192
|
case "*":
|
|
7193
7193
|
return `${nodot}${ONE_CHAR}${star}`;
|
|
7194
7194
|
case ".*":
|
|
@@ -7206,7 +7206,7 @@ var require_parse = __commonJS({
|
|
|
7206
7206
|
case "**/.*":
|
|
7207
7207
|
return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${DOT_LITERAL}${ONE_CHAR}${star}`;
|
|
7208
7208
|
default: {
|
|
7209
|
-
const match = /^(.*?)\.(\w+)$/.exec(
|
|
7209
|
+
const match = /^(.*?)\.(\w+)$/.exec(str16);
|
|
7210
7210
|
if (!match) return;
|
|
7211
7211
|
const source3 = create(match[1]);
|
|
7212
7212
|
if (!source3) return;
|
|
@@ -7237,9 +7237,9 @@ var require_picomatch = __commonJS({
|
|
|
7237
7237
|
var picomatch3 = (glob, options2, returnState = false) => {
|
|
7238
7238
|
if (Array.isArray(glob)) {
|
|
7239
7239
|
const fns = glob.map((input) => picomatch3(input, options2, returnState));
|
|
7240
|
-
const arrayMatcher = (
|
|
7240
|
+
const arrayMatcher = (str16) => {
|
|
7241
7241
|
for (const isMatch of fns) {
|
|
7242
|
-
const state2 = isMatch(
|
|
7242
|
+
const state2 = isMatch(str16);
|
|
7243
7243
|
if (state2) return state2;
|
|
7244
7244
|
}
|
|
7245
7245
|
return false;
|
|
@@ -7315,7 +7315,7 @@ var require_picomatch = __commonJS({
|
|
|
7315
7315
|
const regex = glob instanceof RegExp ? glob : picomatch3.makeRe(glob, options2);
|
|
7316
7316
|
return regex.test(utils.basename(input, { windows: posix5 }));
|
|
7317
7317
|
};
|
|
7318
|
-
picomatch3.isMatch = (
|
|
7318
|
+
picomatch3.isMatch = (str16, patterns, options2) => picomatch3(patterns, options2)(str16);
|
|
7319
7319
|
picomatch3.parse = (pattern, options2) => {
|
|
7320
7320
|
if (Array.isArray(pattern)) return pattern.map((p) => picomatch3.parse(p, options2));
|
|
7321
7321
|
return parse(pattern, { ...options2, fastpaths: false });
|
|
@@ -8904,8 +8904,8 @@ __export(tree_exports, {
|
|
|
8904
8904
|
workingTree: () => workingTree
|
|
8905
8905
|
});
|
|
8906
8906
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
8907
|
-
import { readdirSync as readdirSync10, readFileSync as
|
|
8908
|
-
import { join as
|
|
8907
|
+
import { readdirSync as readdirSync10, readFileSync as readFileSync19, statSync as statSync8 } from "node:fs";
|
|
8908
|
+
import { join as join26 } from "node:path";
|
|
8909
8909
|
function joinPath(...parts) {
|
|
8910
8910
|
return parts.filter((p) => p !== "" && p !== ".").join("/");
|
|
8911
8911
|
}
|
|
@@ -8914,7 +8914,7 @@ function workingTree(root) {
|
|
|
8914
8914
|
label: "",
|
|
8915
8915
|
stat(path) {
|
|
8916
8916
|
try {
|
|
8917
|
-
const s = statSync8(
|
|
8917
|
+
const s = statSync8(join26(root, path));
|
|
8918
8918
|
return s.isDirectory() ? "dir" : s.isFile() ? "file" : void 0;
|
|
8919
8919
|
} catch {
|
|
8920
8920
|
return void 0;
|
|
@@ -8923,7 +8923,7 @@ function workingTree(root) {
|
|
|
8923
8923
|
list(path) {
|
|
8924
8924
|
let dirents;
|
|
8925
8925
|
try {
|
|
8926
|
-
dirents = readdirSync10(
|
|
8926
|
+
dirents = readdirSync10(join26(root, path), { withFileTypes: true });
|
|
8927
8927
|
} catch {
|
|
8928
8928
|
return void 0;
|
|
8929
8929
|
}
|
|
@@ -8932,7 +8932,7 @@ function workingTree(root) {
|
|
|
8932
8932
|
let type = d.isDirectory() ? "dir" : d.isFile() ? "file" : void 0;
|
|
8933
8933
|
if (!type && d.isSymbolicLink()) {
|
|
8934
8934
|
try {
|
|
8935
|
-
const s = statSync8(
|
|
8935
|
+
const s = statSync8(join26(root, path, d.name));
|
|
8936
8936
|
type = s.isDirectory() ? "dir" : s.isFile() ? "file" : void 0;
|
|
8937
8937
|
} catch {
|
|
8938
8938
|
}
|
|
@@ -8942,10 +8942,10 @@ function workingTree(root) {
|
|
|
8942
8942
|
return out;
|
|
8943
8943
|
},
|
|
8944
8944
|
read(path) {
|
|
8945
|
-
return
|
|
8945
|
+
return readFileSync19(join26(root, path), "utf-8");
|
|
8946
8946
|
},
|
|
8947
8947
|
bytes(path) {
|
|
8948
|
-
return
|
|
8948
|
+
return readFileSync19(join26(root, path));
|
|
8949
8949
|
}
|
|
8950
8950
|
};
|
|
8951
8951
|
}
|
|
@@ -9547,12 +9547,12 @@ var init_identity_gate_rule = __esm({
|
|
|
9547
9547
|
});
|
|
9548
9548
|
|
|
9549
9549
|
// node_modules/@intentius/chant/src/workspace/class-data.ts
|
|
9550
|
-
import { existsSync as existsSync17, readFileSync as
|
|
9551
|
-
import { dirname as dirname7, join as
|
|
9550
|
+
import { existsSync as existsSync17, readFileSync as readFileSync20, statSync as statSync9 } from "node:fs";
|
|
9551
|
+
import { dirname as dirname7, join as join27, posix, resolve as resolve14 } from "node:path";
|
|
9552
9552
|
function diskFiles(dir) {
|
|
9553
9553
|
return (path) => {
|
|
9554
9554
|
try {
|
|
9555
|
-
return
|
|
9555
|
+
return readFileSync20(join27(dir, ...path.split("/")), "utf-8");
|
|
9556
9556
|
} catch {
|
|
9557
9557
|
return void 0;
|
|
9558
9558
|
}
|
|
@@ -9560,9 +9560,9 @@ function diskFiles(dir) {
|
|
|
9560
9560
|
}
|
|
9561
9561
|
function findInstalledPackage(name, fromDir) {
|
|
9562
9562
|
for (let dir = resolve14(fromDir); ; dir = dirname7(dir)) {
|
|
9563
|
-
const candidate =
|
|
9563
|
+
const candidate = join27(dir, "node_modules", ...name.split("/"));
|
|
9564
9564
|
try {
|
|
9565
|
-
if (statSync9(candidate).isDirectory() && existsSync17(
|
|
9565
|
+
if (statSync9(candidate).isDirectory() && existsSync17(join27(candidate, "package.json"))) return candidate;
|
|
9566
9566
|
} catch {
|
|
9567
9567
|
}
|
|
9568
9568
|
if (dirname7(dir) === dir) return void 0;
|
|
@@ -10012,10 +10012,10 @@ var init_jsonc = __esm({
|
|
|
10012
10012
|
});
|
|
10013
10013
|
|
|
10014
10014
|
// node_modules/@intentius/chant/src/package-version.ts
|
|
10015
|
-
import { readFileSync as
|
|
10015
|
+
import { readFileSync as readFileSync21 } from "node:fs";
|
|
10016
10016
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
10017
10017
|
function readChantPackageVersion() {
|
|
10018
|
-
return JSON.parse(
|
|
10018
|
+
return JSON.parse(readFileSync21(fileURLToPath4(new URL("../package.json", import.meta.url)), "utf-8")).version;
|
|
10019
10019
|
}
|
|
10020
10020
|
var init_package_version = __esm({
|
|
10021
10021
|
"node_modules/@intentius/chant/src/package-version.ts"() {
|
|
@@ -10814,8 +10814,8 @@ var init_declaration = __esm({
|
|
|
10814
10814
|
});
|
|
10815
10815
|
|
|
10816
10816
|
// src/cli.ts
|
|
10817
|
-
import { resolve as resolve33, dirname as dirname21, join as
|
|
10818
|
-
import { realpathSync as realpathSync3, existsSync as existsSync30, readFileSync as
|
|
10817
|
+
import { resolve as resolve33, dirname as dirname21, join as join50 } from "node:path";
|
|
10818
|
+
import { realpathSync as realpathSync3, existsSync as existsSync30, readFileSync as readFileSync38, rmSync as rmSync4 } from "node:fs";
|
|
10819
10819
|
import { fileURLToPath as fileURLToPath12 } from "node:url";
|
|
10820
10820
|
import { spawn as spawn9, spawnSync as spawnSync4 } from "node:child_process";
|
|
10821
10821
|
|
|
@@ -10873,8 +10873,8 @@ import { serve } from "@hono/node-server";
|
|
|
10873
10873
|
import { fileURLToPath as fileURLToPath10 } from "node:url";
|
|
10874
10874
|
import { execFile as execFile5 } from "node:child_process";
|
|
10875
10875
|
import { promisify as promisify3 } from "node:util";
|
|
10876
|
-
import { existsSync as existsSync27, readFileSync as
|
|
10877
|
-
import { basename as basename5, dirname as dirname18, join as
|
|
10876
|
+
import { existsSync as existsSync27, readFileSync as readFileSync34 } from "node:fs";
|
|
10877
|
+
import { basename as basename5, dirname as dirname18, join as join46, relative as relative10, resolve as resolve30, sep as sep9 } from "node:path";
|
|
10878
10878
|
|
|
10879
10879
|
// src/recents.ts
|
|
10880
10880
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
@@ -13280,10 +13280,10 @@ function joinNamespaceBindings(members, isDir = realIsDir) {
|
|
|
13280
13280
|
if (!winner || tied) continue;
|
|
13281
13281
|
const prev = best.get(winner.dir);
|
|
13282
13282
|
if (prev === "ambiguous") continue;
|
|
13283
|
-
const
|
|
13284
|
-
if (!prev) best.set(winner.dir, { score: winner.score, join:
|
|
13283
|
+
const join51 = { dir: winner.dir, namespace: b.namespace, path: b.path, declaredBy: b.declaredBy };
|
|
13284
|
+
if (!prev) best.set(winner.dir, { score: winner.score, join: join51 });
|
|
13285
13285
|
else if (prev.join.namespace !== b.namespace) best.set(winner.dir, "ambiguous");
|
|
13286
|
-
else if (winner.score > prev.score) best.set(winner.dir, { score: winner.score, join:
|
|
13286
|
+
else if (winner.score > prev.score) best.set(winner.dir, { score: winner.score, join: join51 });
|
|
13287
13287
|
}
|
|
13288
13288
|
return [...best.values()].filter((v) => v !== "ambiguous").map((v) => v.join);
|
|
13289
13289
|
}
|
|
@@ -17490,11 +17490,11 @@ var RAN = { ok: "ok", fail: "failed", skipped: "skipped" };
|
|
|
17490
17490
|
function tookLabel(ms) {
|
|
17491
17491
|
return ms < 1e3 ? `${ms}ms` : `${(ms / 1e3).toFixed(1)}s`;
|
|
17492
17492
|
}
|
|
17493
|
-
function paintPlayhead(ir,
|
|
17494
|
-
if (!
|
|
17493
|
+
function paintPlayhead(ir, join51, gate) {
|
|
17494
|
+
if (!join51.byNode.size && !join51.at) return ir;
|
|
17495
17495
|
const nodes = ir.nodes.map((node) => {
|
|
17496
|
-
const run3 =
|
|
17497
|
-
const here =
|
|
17496
|
+
const run3 = join51.byNode.get(node.id);
|
|
17497
|
+
const here = join51.at === node.id;
|
|
17498
17498
|
if (!run3 && !here) return node;
|
|
17499
17499
|
const isGate = node.attrs?._step === "gate";
|
|
17500
17500
|
const attrs = { ...node.attrs };
|
|
@@ -17509,7 +17509,7 @@ function paintPlayhead(ir, join49, gate) {
|
|
|
17509
17509
|
if (here) {
|
|
17510
17510
|
attrs._playhead = true;
|
|
17511
17511
|
attrs._status = isGate && gate ? "warn" : "accent";
|
|
17512
|
-
attrs.run = isGate && gate ? `waiting for approval since ${gate.since}` :
|
|
17512
|
+
attrs.run = isGate && gate ? `waiting for approval since ${gate.since}` : join51.atReason ?? "here";
|
|
17513
17513
|
if (isGate && gate?.description) attrs.gateDescription = gate.description;
|
|
17514
17514
|
}
|
|
17515
17515
|
return { ...node, attrs };
|
|
@@ -17531,11 +17531,11 @@ function pendingGateCard(op, gate, ir) {
|
|
|
17531
17531
|
}
|
|
17532
17532
|
};
|
|
17533
17533
|
}
|
|
17534
|
-
function playheadNote(state,
|
|
17534
|
+
function playheadNote(state, join51) {
|
|
17535
17535
|
if (!state.op) {
|
|
17536
17536
|
return "No run state: nothing has been run from this behold session, so every step paints as declared.";
|
|
17537
17537
|
}
|
|
17538
|
-
const settled =
|
|
17538
|
+
const settled = join51 ? join51.byNode.size : state.records.length;
|
|
17539
17539
|
const bits = [];
|
|
17540
17540
|
const head = state.status === "idle" ? `Run state for ${state.op}: no per-step records \u2014 this behold session didn't start the run, so only the gate query answers` : `Run: ${state.op} (${state.mode === "temporal" ? "durable" : "local executor"}) \u2014 ${settled} step${settled === 1 ? "" : "s"} settled`;
|
|
17541
17541
|
if (state.status === "running") {
|
|
@@ -17551,9 +17551,9 @@ function playheadNote(state, join49) {
|
|
|
17551
17551
|
} else if (state.gateNote) {
|
|
17552
17552
|
bits.push(`gate state unavailable \u2014 ${state.gateNote}`);
|
|
17553
17553
|
}
|
|
17554
|
-
if (
|
|
17554
|
+
if (join51?.unplaced.length) {
|
|
17555
17555
|
bits.push(
|
|
17556
|
-
`${
|
|
17556
|
+
`${join51.unplaced.length} record${join51.unplaced.length === 1 ? "" : "s"} matched no declared step and ${join51.unplaced.length === 1 ? "is" : "are"} not painted`
|
|
17557
17557
|
);
|
|
17558
17558
|
}
|
|
17559
17559
|
if (!bits.length) return `${head}.`;
|
|
@@ -18223,10 +18223,90 @@ var OpRunner = class {
|
|
|
18223
18223
|
}
|
|
18224
18224
|
};
|
|
18225
18225
|
|
|
18226
|
+
// src/public-mode.ts
|
|
18227
|
+
import { readFileSync as readFileSync15 } from "node:fs";
|
|
18228
|
+
import { join as join20 } from "node:path";
|
|
18229
|
+
var ReadOnlyRunner = class {
|
|
18230
|
+
running = null;
|
|
18231
|
+
applyProgress = initialApplyProgress;
|
|
18232
|
+
runState = initialRunState;
|
|
18233
|
+
operatorState = initialOperatorState;
|
|
18234
|
+
retarget() {
|
|
18235
|
+
}
|
|
18236
|
+
noteGate() {
|
|
18237
|
+
return initialRunState;
|
|
18238
|
+
}
|
|
18239
|
+
noteDeclaredConvergeOps() {
|
|
18240
|
+
return initialOperatorState;
|
|
18241
|
+
}
|
|
18242
|
+
noteOperator() {
|
|
18243
|
+
return initialOperatorState;
|
|
18244
|
+
}
|
|
18245
|
+
trigger() {
|
|
18246
|
+
return false;
|
|
18247
|
+
}
|
|
18248
|
+
run() {
|
|
18249
|
+
return false;
|
|
18250
|
+
}
|
|
18251
|
+
apply() {
|
|
18252
|
+
return false;
|
|
18253
|
+
}
|
|
18254
|
+
pipeline() {
|
|
18255
|
+
return false;
|
|
18256
|
+
}
|
|
18257
|
+
bringUp() {
|
|
18258
|
+
return false;
|
|
18259
|
+
}
|
|
18260
|
+
track() {
|
|
18261
|
+
return false;
|
|
18262
|
+
}
|
|
18263
|
+
};
|
|
18264
|
+
var REFUSED_READS = [
|
|
18265
|
+
[/^\/api\/ops\/[^/]+\/status$/, "the op tools are off"],
|
|
18266
|
+
[/^\/api\/operator(\/|$)/, "the op tools are off"],
|
|
18267
|
+
[/^\/api\/overlay$/, "it reads the live estate with behold's own credentials"],
|
|
18268
|
+
[/^\/api\/reconcile$/, "it plans against the live estate with behold's own credentials"],
|
|
18269
|
+
[/^\/api\/diff(\/|$)/, "it reads the live estate with behold's own credentials"],
|
|
18270
|
+
[/^\/api\/components\/compare$/, "it reads the live estate with behold's own credentials"],
|
|
18271
|
+
[/^\/api\/resources$/, "it runs chant against the project"],
|
|
18272
|
+
[/^\/api\/ci(\/|$)/, "it reads the pipeline and the forge run"],
|
|
18273
|
+
[/^\/api\/doctor$/, "it probes this machine's tools"],
|
|
18274
|
+
[/^\/api\/substrates$/, "it probes this machine's tools"],
|
|
18275
|
+
[/^\/api\/history$/, "it runs git against the checkout"],
|
|
18276
|
+
[/^\/api\/workspace\/(gates|why)$/, "it runs chant against the workspace"],
|
|
18277
|
+
[/^\/api\/choudoufu\/moves$/, "it runs choudoufu"],
|
|
18278
|
+
[/^\/choudoufu\/morph$/, "it runs choudoufu"],
|
|
18279
|
+
[/^\/api\/sql-yodeler\/lifecycle$/, "a public view shows a terragucci repo"]
|
|
18280
|
+
];
|
|
18281
|
+
var LIVE_PARAMS = ["env", "target", "consistent", "plan", "live"];
|
|
18282
|
+
function publicReadRefusal(path, query) {
|
|
18283
|
+
for (const [re, why] of REFUSED_READS) if (re.test(path)) return why;
|
|
18284
|
+
if (path === "/api/graph" && LIVE_PARAMS.some((p) => query.has(p))) {
|
|
18285
|
+
return "a public view draws the source graph only; a live read uses behold's own credentials";
|
|
18286
|
+
}
|
|
18287
|
+
return void 0;
|
|
18288
|
+
}
|
|
18289
|
+
var PUBLIC_FLAG = "<script>window.__BEHOLD_PUBLIC__ = true;</script>";
|
|
18290
|
+
function publicPage(webRoot2) {
|
|
18291
|
+
const html = readFileSync15(join20(webRoot2, "index.html"), "utf8");
|
|
18292
|
+
return html.replace("</head>", ` ${PUBLIC_FLAG}
|
|
18293
|
+
</head>`);
|
|
18294
|
+
}
|
|
18295
|
+
function publicRoutes(app, webRoot2) {
|
|
18296
|
+
app.use("*", async (c, next) => {
|
|
18297
|
+
const why = publicReadRefusal(c.req.path, new URL(c.req.url).searchParams);
|
|
18298
|
+
if (why) return c.json({ error: `behold is serving a public, read-only view: ${c.req.path} is refused, because ${why}`, code: "public" }, 403);
|
|
18299
|
+
await next();
|
|
18300
|
+
});
|
|
18301
|
+
const page = () => new Response(publicPage(webRoot2), { headers: { "content-type": "text/html; charset=utf-8", "cache-control": "no-cache" } });
|
|
18302
|
+
app.get("/", () => page());
|
|
18303
|
+
app.get("/index.html", () => page());
|
|
18304
|
+
}
|
|
18305
|
+
|
|
18226
18306
|
// src/substrates.ts
|
|
18227
18307
|
import { spawn as spawn5 } from "node:child_process";
|
|
18228
|
-
import { existsSync as existsSync14, readFileSync as
|
|
18229
|
-
import { join as
|
|
18308
|
+
import { existsSync as existsSync14, readFileSync as readFileSync16 } from "node:fs";
|
|
18309
|
+
import { join as join21 } from "node:path";
|
|
18230
18310
|
import { platform } from "node:os";
|
|
18231
18311
|
var DOCKER_DOWN_DETAIL = "docker is down";
|
|
18232
18312
|
function probe(cmd, args) {
|
|
@@ -18255,11 +18335,11 @@ async function dockerRunning(run3, nameFilter) {
|
|
|
18255
18335
|
return out.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
18256
18336
|
}
|
|
18257
18337
|
function scriptBringUp(projectDir, relPath, label) {
|
|
18258
|
-
return existsSync14(
|
|
18338
|
+
return existsSync14(join21(projectDir, relPath)) ? { label, cmd: "bash", args: [relPath] } : void 0;
|
|
18259
18339
|
}
|
|
18260
18340
|
function projectLexicons(projectDir) {
|
|
18261
18341
|
try {
|
|
18262
|
-
const src =
|
|
18342
|
+
const src = readFileSync16(join21(projectDir, "chant.config.ts"), "utf-8");
|
|
18263
18343
|
const m = src.match(/lexicons\s*:\s*\[([^\]]*)\]/);
|
|
18264
18344
|
if (!m) return [];
|
|
18265
18345
|
return [...m[1].matchAll(/["']([^"']+)["']/g)].map((x) => x[1]);
|
|
@@ -18311,7 +18391,7 @@ async function detectSubstrates(projectDir, preview = false, boundContext, probe
|
|
|
18311
18391
|
["forgejo", "Forgejo", ".forgejo", "test/forgejo-runtime-e2e.sh"]
|
|
18312
18392
|
];
|
|
18313
18393
|
for (const [name, label, marker, script] of forges) {
|
|
18314
|
-
if (!existsSync14(
|
|
18394
|
+
if (!existsSync14(join21(projectDir, marker))) continue;
|
|
18315
18395
|
const c = docker ? await dockerRunning(run3, name) : [];
|
|
18316
18396
|
const d = dep(c.length > 0, "container up", "on-demand (pipeline run)");
|
|
18317
18397
|
subs.push({
|
|
@@ -18345,7 +18425,7 @@ async function detectSubstrates(projectDir, preview = false, boundContext, probe
|
|
|
18345
18425
|
detail: endpoint ? `targeting ${endpoint}` : "real Fly (FLY_FLAPS_BASE_URL unset)"
|
|
18346
18426
|
});
|
|
18347
18427
|
}
|
|
18348
|
-
if (existsSync14(
|
|
18428
|
+
if (existsSync14(join21(projectDir, ".github", "workflows"))) {
|
|
18349
18429
|
const gh = await run3("gh", ["auth", "status"]);
|
|
18350
18430
|
const ready = gh.code === 0;
|
|
18351
18431
|
subs.push({
|
|
@@ -18358,7 +18438,7 @@ async function detectSubstrates(projectDir, preview = false, boundContext, probe
|
|
|
18358
18438
|
if (lexicons.includes("temporal")) {
|
|
18359
18439
|
let hasProfiles = false;
|
|
18360
18440
|
try {
|
|
18361
|
-
hasProfiles = /temporal\s*:\s*\{[\s\S]{0,400}?profiles\s*:/.test(
|
|
18441
|
+
hasProfiles = /temporal\s*:\s*\{[\s\S]{0,400}?profiles\s*:/.test(readFileSync16(join21(projectDir, "chant.config.ts"), "utf-8"));
|
|
18362
18442
|
} catch {
|
|
18363
18443
|
}
|
|
18364
18444
|
subs.push({
|
|
@@ -18501,8 +18581,8 @@ function statusVocabulary(kinds) {
|
|
|
18501
18581
|
}
|
|
18502
18582
|
|
|
18503
18583
|
// src/behaviour.ts
|
|
18504
|
-
import { readFileSync as
|
|
18505
|
-
import { join as
|
|
18584
|
+
import { readFileSync as readFileSync17 } from "node:fs";
|
|
18585
|
+
import { join as join22 } from "node:path";
|
|
18506
18586
|
var rec5 = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : void 0;
|
|
18507
18587
|
var str6 = (v) => typeof v === "string" && v.trim().length > 0 ? v : void 0;
|
|
18508
18588
|
var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
@@ -18609,7 +18689,7 @@ var FIRST_TOTAL_SHOWN = "two members stated an estate total; the first is shown"
|
|
|
18609
18689
|
var behaviourFileName = (env) => `behaviour.${env}.json`;
|
|
18610
18690
|
var realRead = (path) => {
|
|
18611
18691
|
try {
|
|
18612
|
-
return
|
|
18692
|
+
return readFileSync17(path, "utf8");
|
|
18613
18693
|
} catch {
|
|
18614
18694
|
return void 0;
|
|
18615
18695
|
}
|
|
@@ -18667,7 +18747,7 @@ function attachBehaviour(ir, members, env, readFile2 = realRead) {
|
|
|
18667
18747
|
}
|
|
18668
18748
|
const file = behaviourFileName(env);
|
|
18669
18749
|
looked.push(`${label}/${file}`);
|
|
18670
|
-
const raw = readFile2(
|
|
18750
|
+
const raw = readFile2(join22(m.dir, file));
|
|
18671
18751
|
if (raw === void 0) {
|
|
18672
18752
|
sources.set(label, "none");
|
|
18673
18753
|
continue;
|
|
@@ -19103,7 +19183,7 @@ init_choudoufu_live();
|
|
|
19103
19183
|
init_choudoufu_member();
|
|
19104
19184
|
import { mkdtempSync, rmSync as rmSync3 } from "node:fs";
|
|
19105
19185
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
19106
|
-
import { join as
|
|
19186
|
+
import { join as join23 } from "node:path";
|
|
19107
19187
|
var isRecord10 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
19108
19188
|
var refuse7 = (error, remedy) => ({ ok: false, refusal: { error, code: "choudoufu-live-check", remedy } });
|
|
19109
19189
|
var CREDS2 = "Give behold the ambient read-only AWS credentials the estate's `plan` needs (AWS_PROFILE, or AWS_ENDPOINT_URL for an emulator), then re-check live with plan.";
|
|
@@ -19177,8 +19257,8 @@ var planArgv = (out) => ({
|
|
|
19177
19257
|
show: ["show", "-json", out]
|
|
19178
19258
|
});
|
|
19179
19259
|
async function readChoudoufuPlan(dir, run3 = captureChoudoufu) {
|
|
19180
|
-
const scratch = mkdtempSync(
|
|
19181
|
-
const file =
|
|
19260
|
+
const scratch = mkdtempSync(join23(tmpdir2(), "behold-choudoufu-plan-"));
|
|
19261
|
+
const file = join23(scratch, "plan.tfplan");
|
|
19182
19262
|
const argv = planArgv(file);
|
|
19183
19263
|
try {
|
|
19184
19264
|
const planRun = await run3(argv.plan, dir);
|
|
@@ -19210,8 +19290,8 @@ init_choudoufu_refs();
|
|
|
19210
19290
|
// src/choudoufu-moves.ts
|
|
19211
19291
|
init_choudoufu_member();
|
|
19212
19292
|
init_choudoufu_live();
|
|
19213
|
-
import { existsSync as existsSync15, readFileSync as
|
|
19214
|
-
import { join as
|
|
19293
|
+
import { existsSync as existsSync15, readFileSync as readFileSync18 } from "node:fs";
|
|
19294
|
+
import { join as join24, relative as relative5 } from "node:path";
|
|
19215
19295
|
var isRecord11 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
19216
19296
|
var refuse8 = (error, remedy) => ({ ok: false, refusal: { error, code: "choudoufu-live-check", remedy } });
|
|
19217
19297
|
var HOW_TO_GET_ONE2 = "A plan is the live-mv workbench's carve.json: `{from, estates[], moves[{address, from, to}]}` \u2014 the workbench's plan phase writes one, or write it by hand.";
|
|
@@ -19231,7 +19311,7 @@ function parseCarvePlan(value) {
|
|
|
19231
19311
|
}
|
|
19232
19312
|
};
|
|
19233
19313
|
}
|
|
19234
|
-
function readCarvePlan(path, readFile2 = (p) =>
|
|
19314
|
+
function readCarvePlan(path, readFile2 = (p) => readFileSync18(p, "utf8")) {
|
|
19235
19315
|
let text;
|
|
19236
19316
|
try {
|
|
19237
19317
|
text = readFile2(path);
|
|
@@ -19247,7 +19327,7 @@ function readCarvePlan(path, readFile2 = (p) => readFileSync17(p, "utf8")) {
|
|
|
19247
19327
|
function discoverCarvePlans(memberDirs, base) {
|
|
19248
19328
|
const out = [];
|
|
19249
19329
|
for (const dir of memberDirs) {
|
|
19250
|
-
const p =
|
|
19330
|
+
const p = join24(dir, "carve.json");
|
|
19251
19331
|
if (existsSync15(p)) out.push(relative5(base, p));
|
|
19252
19332
|
}
|
|
19253
19333
|
return out;
|
|
@@ -19478,24 +19558,24 @@ function getRuntime() {
|
|
|
19478
19558
|
|
|
19479
19559
|
// node_modules/@intentius/chant/src/lifecycle/member-ledger.ts
|
|
19480
19560
|
import { existsSync as existsSync18 } from "node:fs";
|
|
19481
|
-
import { dirname as dirname8, join as
|
|
19561
|
+
import { dirname as dirname8, join as join28, relative as relative6, resolve as resolve15, sep as sep6 } from "node:path";
|
|
19482
19562
|
|
|
19483
19563
|
// node_modules/@intentius/chant/src/project-root.ts
|
|
19484
19564
|
import { existsSync as existsSync16 } from "fs";
|
|
19485
|
-
import { dirname as dirname6, join as
|
|
19565
|
+
import { dirname as dirname6, join as join25, resolve as resolve13 } from "path";
|
|
19486
19566
|
var WORKSPACE_DECLARATION_FILES = ["chant.workspace.json", "chant.workspace.jsonc"];
|
|
19487
19567
|
function findWorkspaceRoot(startDir) {
|
|
19488
19568
|
const start = resolve13(startDir);
|
|
19489
19569
|
let top;
|
|
19490
19570
|
for (let dir = start; ; dir = dirname6(dir)) {
|
|
19491
|
-
if (existsSync16(
|
|
19571
|
+
if (existsSync16(join25(dir, ".git"))) {
|
|
19492
19572
|
top = dir;
|
|
19493
19573
|
break;
|
|
19494
19574
|
}
|
|
19495
19575
|
if (dirname6(dir) === dir) break;
|
|
19496
19576
|
}
|
|
19497
19577
|
for (let dir = start; ; dir = dirname6(dir)) {
|
|
19498
|
-
const [json2, jsonc] = WORKSPACE_DECLARATION_FILES.map((name) =>
|
|
19578
|
+
const [json2, jsonc] = WORKSPACE_DECLARATION_FILES.map((name) => join25(dir, name));
|
|
19499
19579
|
const hasJson = existsSync16(json2);
|
|
19500
19580
|
const hasJsonc = existsSync16(jsonc);
|
|
19501
19581
|
if (hasJson && hasJsonc) return { dir, file: json2, conflicting: jsonc };
|
|
@@ -19557,7 +19637,7 @@ async function ledgerIn(root, dir) {
|
|
|
19557
19637
|
}
|
|
19558
19638
|
let outer = FLAT;
|
|
19559
19639
|
const parent = dirname8(root);
|
|
19560
|
-
if (parent !== root && !existsSync18(
|
|
19640
|
+
if (parent !== root && !existsSync18(join28(root, ".git"))) {
|
|
19561
19641
|
const outerFound = findWorkspaceRoot(parent);
|
|
19562
19642
|
if (outerFound) outer = await ledgerIn(outerFound.dir, root);
|
|
19563
19643
|
}
|
|
@@ -20112,7 +20192,7 @@ function decideOverride(input) {
|
|
|
20112
20192
|
|
|
20113
20193
|
// src/terragucci-repo.ts
|
|
20114
20194
|
import { existsSync as existsSync19 } from "node:fs";
|
|
20115
|
-
import { dirname as dirname9, join as
|
|
20195
|
+
import { dirname as dirname9, join as join29, resolve as resolve16 } from "node:path";
|
|
20116
20196
|
var TERRAGUCCI_CONFIG_NAMES = ["terragucci.yml", "terragucci.yaml", "terragucci.json", "terragucci.ts"];
|
|
20117
20197
|
function terragucciConfig(dir) {
|
|
20118
20198
|
return walkUp(dir, TERRAGUCCI_CONFIG_NAMES);
|
|
@@ -20121,10 +20201,10 @@ function walkUp(dir, names) {
|
|
|
20121
20201
|
let at2 = resolve16(dir);
|
|
20122
20202
|
for (; ; ) {
|
|
20123
20203
|
for (const name of names) {
|
|
20124
|
-
const file =
|
|
20204
|
+
const file = join29(at2, name);
|
|
20125
20205
|
if (existsSync19(file)) return file;
|
|
20126
20206
|
}
|
|
20127
|
-
if (existsSync19(
|
|
20207
|
+
if (existsSync19(join29(at2, ".git"))) return void 0;
|
|
20128
20208
|
const up = dirname9(at2);
|
|
20129
20209
|
if (up === at2) return void 0;
|
|
20130
20210
|
at2 = up;
|
|
@@ -20529,6 +20609,9 @@ function guardRequest(req, cfg) {
|
|
|
20529
20609
|
return { ok: false, status: 421, error: `behold doesn't answer to host ${name}. Start it with --allow-host ${name} if a proxy of yours sends that name.` };
|
|
20530
20610
|
}
|
|
20531
20611
|
if (SAFE.has(req.method.toUpperCase())) return { ok: true };
|
|
20612
|
+
if (cfg.public) {
|
|
20613
|
+
return { ok: false, status: 405, error: `behold is serving a public, read-only view: a ${req.method.toUpperCase()} is refused (GET, HEAD and OPTIONS only)` };
|
|
20614
|
+
}
|
|
20532
20615
|
if ((req.secFetchSite || "").toLowerCase() === "cross-site") {
|
|
20533
20616
|
return { ok: false, status: 403, error: "a cross-site page can't write to behold" };
|
|
20534
20617
|
}
|
|
@@ -20924,7 +21007,7 @@ init_foreign();
|
|
|
20924
21007
|
|
|
20925
21008
|
// src/events.ts
|
|
20926
21009
|
import { watch, existsSync as existsSync20 } from "node:fs";
|
|
20927
|
-
import { join as
|
|
21010
|
+
import { join as join30 } from "node:path";
|
|
20928
21011
|
var Broadcaster = class {
|
|
20929
21012
|
listeners = /* @__PURE__ */ new Set();
|
|
20930
21013
|
subscribe(fn) {
|
|
@@ -20942,7 +21025,7 @@ var Broadcaster = class {
|
|
|
20942
21025
|
};
|
|
20943
21026
|
var IGNORE = /(^|[\\/])(node_modules|dist|\.git)([\\/]|$)/;
|
|
20944
21027
|
function watchSource(projectDir, onChange, debounceMs = 200) {
|
|
20945
|
-
const dir = existsSync20(
|
|
21028
|
+
const dir = existsSync20(join30(projectDir, "src")) ? join30(projectDir, "src") : projectDir;
|
|
20946
21029
|
let timer;
|
|
20947
21030
|
const watcher = watch(dir, { recursive: true }, (_event, file) => {
|
|
20948
21031
|
const name = typeof file === "string" ? file : "";
|
|
@@ -21245,13 +21328,13 @@ async function emulatorDown(projectDir) {
|
|
|
21245
21328
|
|
|
21246
21329
|
// src/terragucci-view.ts
|
|
21247
21330
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
21248
|
-
import { dirname as dirname13, join as
|
|
21331
|
+
import { dirname as dirname13, join as join39, relative as relative8, sep as sep7 } from "node:path";
|
|
21249
21332
|
init_terraform_member();
|
|
21250
21333
|
|
|
21251
21334
|
// src/terragucci-reports.ts
|
|
21252
|
-
import { readFileSync as
|
|
21335
|
+
import { readFileSync as readFileSync23, statSync as statSync10 } from "node:fs";
|
|
21253
21336
|
import { createRequire as createRequire4 } from "node:module";
|
|
21254
|
-
import { dirname as dirname10, join as
|
|
21337
|
+
import { dirname as dirname10, join as join33, resolve as resolve20 } from "node:path";
|
|
21255
21338
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
21256
21339
|
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
21257
21340
|
|
|
@@ -21260,12 +21343,12 @@ import { execFile as execFile4 } from "node:child_process";
|
|
|
21260
21343
|
import { createHash as createHash8 } from "node:crypto";
|
|
21261
21344
|
import { existsSync as existsSync21, mkdirSync as mkdirSync6, readdirSync as readdirSync11 } from "node:fs";
|
|
21262
21345
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
21263
|
-
import { basename as basename3, isAbsolute as isAbsolute4, join as
|
|
21346
|
+
import { basename as basename3, isAbsolute as isAbsolute4, join as join32, resolve as resolve19 } from "node:path";
|
|
21264
21347
|
|
|
21265
21348
|
// node_modules/@intentius/chant/src/gate-decision.ts
|
|
21266
21349
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
21267
|
-
import { readFileSync as
|
|
21268
|
-
import { join as
|
|
21350
|
+
import { readFileSync as readFileSync22 } from "node:fs";
|
|
21351
|
+
import { join as join31, posix as posix3 } from "node:path";
|
|
21269
21352
|
|
|
21270
21353
|
// node_modules/@intentius/chant/src/lifecycle/gate-applied.ts
|
|
21271
21354
|
function appliedGatePath(op) {
|
|
@@ -21374,7 +21457,7 @@ function pinnedClassesAt(repo, base, pins, packages) {
|
|
|
21374
21457
|
}
|
|
21375
21458
|
let installed2;
|
|
21376
21459
|
try {
|
|
21377
|
-
installed2 = JSON.parse(
|
|
21460
|
+
installed2 = JSON.parse(readFileSync22(join31(dir, "package.json"), "utf-8")).version;
|
|
21378
21461
|
} catch {
|
|
21379
21462
|
installed2 = void 0;
|
|
21380
21463
|
}
|
|
@@ -21969,7 +22052,7 @@ var realGit = (args, o = {}) => new Promise((res) => {
|
|
|
21969
22052
|
});
|
|
21970
22053
|
var redact = (s) => s.replace(/(\b[a-z][a-z0-9+.-]*:\/\/)[^/@\s]+@/gi, "$1");
|
|
21971
22054
|
function cacheDir(origin, root = tmpdir3()) {
|
|
21972
|
-
return
|
|
22055
|
+
return join32(root, `${CACHE_PREFIX}${createHash8("sha256").update(origin).digest("hex").slice(0, 16)}`);
|
|
21973
22056
|
}
|
|
21974
22057
|
var inflight = /* @__PURE__ */ new Map();
|
|
21975
22058
|
function serially(key, fn) {
|
|
@@ -21988,7 +22071,7 @@ function fetchable(url, checkout) {
|
|
|
21988
22071
|
async function fetchIntoCache(git3, origin, cache6) {
|
|
21989
22072
|
assertScratch(basename3(cache6));
|
|
21990
22073
|
return serially(cache6, async () => {
|
|
21991
|
-
if (!existsSync21(
|
|
22074
|
+
if (!existsSync21(join32(cache6, "HEAD"))) {
|
|
21992
22075
|
if (existsSync21(cache6) && readdirSync11(cache6).length > 0) return { ok: false, error: `${cache6} exists and is not behold's lifecycle cache; behold leaves it alone` };
|
|
21993
22076
|
mkdirSync6(cache6, { recursive: true });
|
|
21994
22077
|
const init = await git3(["init", "--bare", "-q", cache6]);
|
|
@@ -22176,7 +22259,7 @@ function structural(name, doc) {
|
|
|
22176
22259
|
var compiledSchemas = /* @__PURE__ */ new Map();
|
|
22177
22260
|
function schemaStamp(dist) {
|
|
22178
22261
|
try {
|
|
22179
|
-
const files = [
|
|
22262
|
+
const files = [join33(dist, "..", "package.json"), ...Object.values(SCHEMA_FILES).map((f) => join33(dist, f))];
|
|
22180
22263
|
return [dist, ...files.map((f) => {
|
|
22181
22264
|
const st = statSync10(f);
|
|
22182
22265
|
return `${st.mtimeMs}:${st.size}`;
|
|
@@ -22186,10 +22269,10 @@ function schemaStamp(dist) {
|
|
|
22186
22269
|
}
|
|
22187
22270
|
}
|
|
22188
22271
|
function compileSchemas(dist) {
|
|
22189
|
-
const version = JSON.parse(
|
|
22272
|
+
const version = JSON.parse(readFileSync23(join33(dist, "..", "package.json"), "utf8")).version ?? "";
|
|
22190
22273
|
const ajv = new Ajv2020({ strict: false, allErrors: false });
|
|
22191
22274
|
const compiled4 = Object.fromEntries(
|
|
22192
|
-
Object.keys(SCHEMA_FILES).map((n) => [n, ajv.compile(JSON.parse(
|
|
22275
|
+
Object.keys(SCHEMA_FILES).map((n) => [n, ajv.compile(JSON.parse(readFileSync23(join33(dist, SCHEMA_FILES[n]), "utf8")))])
|
|
22193
22276
|
);
|
|
22194
22277
|
return {
|
|
22195
22278
|
validation: { by: "schema", terragucci: version },
|
|
@@ -22206,7 +22289,7 @@ function terragucciValidator(dirs = []) {
|
|
|
22206
22289
|
for (const from of [...dirs, dirname10(fileURLToPath5(import.meta.url))]) {
|
|
22207
22290
|
let entry;
|
|
22208
22291
|
try {
|
|
22209
|
-
entry = createRequire4(
|
|
22292
|
+
entry = createRequire4(join33(resolve20(from), "noop.js")).resolve("@intentius/terragucci/report.schema.json");
|
|
22210
22293
|
} catch {
|
|
22211
22294
|
continue;
|
|
22212
22295
|
}
|
|
@@ -22330,6 +22413,10 @@ async function readTerragucci(source2, opts = {}) {
|
|
|
22330
22413
|
}
|
|
22331
22414
|
}
|
|
22332
22415
|
}
|
|
22416
|
+
let pullRequest;
|
|
22417
|
+
const prRow = rows.find((r) => r.stage === "tf-plan" && r.pull_request);
|
|
22418
|
+
const prDoc = prRow ? await report(prRow) : void 0;
|
|
22419
|
+
if (prRow && prDoc) pullRequest = pullRequestPlan(prDoc, under(prRow.path, "report.html"));
|
|
22333
22420
|
const estateDoc = await readDoc(source2, "estate.json", "estate", v);
|
|
22334
22421
|
const estateProject = estateDoc?.projects.find((p) => p.project === project);
|
|
22335
22422
|
const waiting = [];
|
|
@@ -22375,6 +22462,7 @@ async function readTerragucci(source2, opts = {}) {
|
|
|
22375
22462
|
reports: cache6.size,
|
|
22376
22463
|
roots,
|
|
22377
22464
|
waiting,
|
|
22465
|
+
...pullRequest ? { pull_request: pullRequest } : {},
|
|
22378
22466
|
...estateDoc && estateProject ? {
|
|
22379
22467
|
estate: {
|
|
22380
22468
|
generated: estateDoc.generated,
|
|
@@ -22387,6 +22475,57 @@ async function readTerragucci(source2, opts = {}) {
|
|
|
22387
22475
|
} : {}
|
|
22388
22476
|
};
|
|
22389
22477
|
}
|
|
22478
|
+
function pullRequestPlan(doc, report) {
|
|
22479
|
+
return {
|
|
22480
|
+
run: {
|
|
22481
|
+
commit: doc.run.commit,
|
|
22482
|
+
finished: doc.run.finished,
|
|
22483
|
+
pull_request: doc.run.pull_request ?? "",
|
|
22484
|
+
...doc.run.pull_request_url ? { pull_request_url: doc.run.pull_request_url } : {},
|
|
22485
|
+
...doc.run.job_url ? { job_url: doc.run.job_url } : {},
|
|
22486
|
+
report
|
|
22487
|
+
},
|
|
22488
|
+
reads: doc.roots.flatMap(
|
|
22489
|
+
(r) => (r.reads ?? []).map((x) => ({
|
|
22490
|
+
root: r.path,
|
|
22491
|
+
upstream: x.upstream,
|
|
22492
|
+
data: x.data,
|
|
22493
|
+
outputs: x.outputs,
|
|
22494
|
+
...x.unknown?.length ? { unknown: [...x.unknown] } : {},
|
|
22495
|
+
...x.why ? { why: x.why } : {},
|
|
22496
|
+
...x.composed ? { composed: true } : {}
|
|
22497
|
+
}))
|
|
22498
|
+
),
|
|
22499
|
+
roots: doc.roots.map((r) => r.path),
|
|
22500
|
+
waves: doc.waves.map((w) => ({
|
|
22501
|
+
number: w.number,
|
|
22502
|
+
roots: w.roots,
|
|
22503
|
+
...w.state ? { state: w.state } : {},
|
|
22504
|
+
// A wave that plans again has no digest a review binds until it does.
|
|
22505
|
+
set_digest: w.replans_after?.length ? null : w.set_digest,
|
|
22506
|
+
...w.review_digest !== void 0 ? { review_digest: w.replans_after?.length ? null : w.review_digest } : {},
|
|
22507
|
+
...w.waits !== void 0 ? { waits: w.waits } : {},
|
|
22508
|
+
...w.reads?.length ? { reads: w.reads } : {},
|
|
22509
|
+
...w.replans_after?.length ? { replans_after: w.replans_after } : {},
|
|
22510
|
+
...w.cost ? { cost: w.cost } : {},
|
|
22511
|
+
// #568 step 3: each member's changes come from its own slice, under its own name.
|
|
22512
|
+
...w.composed ? {
|
|
22513
|
+
composed: {
|
|
22514
|
+
...w.composed,
|
|
22515
|
+
members: w.composed.members.map((m) => {
|
|
22516
|
+
const counts = doc.roots.find((r) => r.path === m.root)?.counts;
|
|
22517
|
+
return { ...m, ...counts ? { counts } : {} };
|
|
22518
|
+
})
|
|
22519
|
+
}
|
|
22520
|
+
} : {}
|
|
22521
|
+
})),
|
|
22522
|
+
...doc.compose ? { compose: doc.compose } : {},
|
|
22523
|
+
...doc.totals ? { totals: doc.totals } : {},
|
|
22524
|
+
...doc.totals_roots !== void 0 ? { totals_roots: doc.totals_roots } : {},
|
|
22525
|
+
...doc.cost ? { cost: doc.cost } : {},
|
|
22526
|
+
...doc.blast ? { blast: doc.blast } : {}
|
|
22527
|
+
};
|
|
22528
|
+
}
|
|
22390
22529
|
|
|
22391
22530
|
// src/terragucci-overlay.ts
|
|
22392
22531
|
function blockPath(address) {
|
|
@@ -22491,11 +22630,11 @@ function joinTerragucci(read, nodes, roots) {
|
|
|
22491
22630
|
// src/terragucci-source.ts
|
|
22492
22631
|
import { spawn as spawn7 } from "node:child_process";
|
|
22493
22632
|
import { readFile } from "node:fs/promises";
|
|
22494
|
-
import { join as
|
|
22633
|
+
import { join as join34, resolve as resolve21 } from "node:path";
|
|
22495
22634
|
|
|
22496
22635
|
// src/s3-object.ts
|
|
22497
22636
|
import { createHash as createHash9, createHmac } from "node:crypto";
|
|
22498
|
-
import { readFileSync as
|
|
22637
|
+
import { readFileSync as readFileSync24 } from "node:fs";
|
|
22499
22638
|
var S3Error = class extends Error {
|
|
22500
22639
|
/** The HTTP status S3 answered, when it answered. */
|
|
22501
22640
|
status;
|
|
@@ -22542,7 +22681,7 @@ function s3FromEnv(bucket, env = process.env) {
|
|
|
22542
22681
|
async function assumeRole(w, fetchFn) {
|
|
22543
22682
|
let token;
|
|
22544
22683
|
try {
|
|
22545
|
-
token =
|
|
22684
|
+
token = readFileSync24(w.tokenFile, "utf8").trim();
|
|
22546
22685
|
} catch (e) {
|
|
22547
22686
|
throw new S3Error(`cannot read the OIDC token in ${w.tokenFile}: ${e.message}`);
|
|
22548
22687
|
}
|
|
@@ -22739,7 +22878,7 @@ function terragucciSource(spec2, opts = {}) {
|
|
|
22739
22878
|
location: dir,
|
|
22740
22879
|
async read(key) {
|
|
22741
22880
|
try {
|
|
22742
|
-
return await readFile(
|
|
22881
|
+
return await readFile(join34(dir, ...trim(key).split("/")), "utf8");
|
|
22743
22882
|
} catch (e) {
|
|
22744
22883
|
if (e.code === "ENOENT") return void 0;
|
|
22745
22884
|
throw e;
|
|
@@ -22749,9 +22888,9 @@ function terragucciSource(spec2, opts = {}) {
|
|
|
22749
22888
|
}
|
|
22750
22889
|
|
|
22751
22890
|
// src/terragucci-timeline.ts
|
|
22752
|
-
import { readFileSync as
|
|
22891
|
+
import { readFileSync as readFileSync25, statSync as statSync11 } from "node:fs";
|
|
22753
22892
|
import { createRequire as createRequire5 } from "node:module";
|
|
22754
|
-
import { dirname as dirname11, join as
|
|
22893
|
+
import { dirname as dirname11, join as join35, resolve as resolve22 } from "node:path";
|
|
22755
22894
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
22756
22895
|
import { Ajv2020 as Ajv20202 } from "ajv/dist/2020.js";
|
|
22757
22896
|
var AUDIT_SCHEMA = "terragucci.audit/v1";
|
|
@@ -22792,12 +22931,12 @@ function auditValidator(dirs = []) {
|
|
|
22792
22931
|
for (const from of [...dirs, dirname11(fileURLToPath6(import.meta.url))]) {
|
|
22793
22932
|
let dist;
|
|
22794
22933
|
try {
|
|
22795
|
-
dist = dirname11(createRequire5(
|
|
22934
|
+
dist = dirname11(createRequire5(join35(resolve22(from), "noop.js")).resolve("@intentius/terragucci/report.schema.json"));
|
|
22796
22935
|
} catch {
|
|
22797
22936
|
continue;
|
|
22798
22937
|
}
|
|
22799
|
-
const pkg =
|
|
22800
|
-
const file =
|
|
22938
|
+
const pkg = join35(dist, "..", "package.json");
|
|
22939
|
+
const file = join35(dist, AUDIT_SCHEMA_FILE);
|
|
22801
22940
|
let stamp;
|
|
22802
22941
|
try {
|
|
22803
22942
|
stamp = [dist, ...[pkg, file].map((f) => {
|
|
@@ -22811,8 +22950,8 @@ function auditValidator(dirs = []) {
|
|
|
22811
22950
|
if (hit) return hit;
|
|
22812
22951
|
let v;
|
|
22813
22952
|
try {
|
|
22814
|
-
const version = JSON.parse(
|
|
22815
|
-
v = compileAuditSchema(JSON.parse(
|
|
22953
|
+
const version = JSON.parse(readFileSync25(pkg, "utf8")).version ?? "";
|
|
22954
|
+
v = compileAuditSchema(JSON.parse(readFileSync25(file, "utf8")), version);
|
|
22816
22955
|
} catch {
|
|
22817
22956
|
continue;
|
|
22818
22957
|
}
|
|
@@ -23008,7 +23147,7 @@ function terragucciTimelineRoute(app, source2, opts = {}) {
|
|
|
23008
23147
|
// src/terragucci-poll.ts
|
|
23009
23148
|
import { createHash as createHash10 } from "node:crypto";
|
|
23010
23149
|
import { stat } from "node:fs/promises";
|
|
23011
|
-
import { join as
|
|
23150
|
+
import { join as join36, resolve as resolve23 } from "node:path";
|
|
23012
23151
|
import { streamSSE } from "hono/streaming";
|
|
23013
23152
|
var TERRAGUCCI_POLL_SECS = 30;
|
|
23014
23153
|
var INDEX_KEY = "index.json";
|
|
@@ -23059,7 +23198,7 @@ function indexProbe(spec2, opts = {}) {
|
|
|
23059
23198
|
return verdict2(prev, etag);
|
|
23060
23199
|
};
|
|
23061
23200
|
}
|
|
23062
|
-
const file =
|
|
23201
|
+
const file = join36(resolve23(opts.cwd ?? process.cwd(), spec2), name);
|
|
23063
23202
|
return async (prev) => {
|
|
23064
23203
|
try {
|
|
23065
23204
|
const s = await stat(file);
|
|
@@ -23214,9 +23353,9 @@ function terragucciLaneRoutes(app, o, base, forget) {
|
|
|
23214
23353
|
}
|
|
23215
23354
|
|
|
23216
23355
|
// src/terragucci-progress.ts
|
|
23217
|
-
import { readFileSync as
|
|
23356
|
+
import { readFileSync as readFileSync26, statSync as statSync12 } from "node:fs";
|
|
23218
23357
|
import { createRequire as createRequire6 } from "node:module";
|
|
23219
|
-
import { dirname as dirname12, join as
|
|
23358
|
+
import { dirname as dirname12, join as join37, resolve as resolve24 } from "node:path";
|
|
23220
23359
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
23221
23360
|
import { Ajv2020 as Ajv20203 } from "ajv/dist/2020.js";
|
|
23222
23361
|
var RUN_SCHEMA = "terragucci.run/v1";
|
|
@@ -23261,12 +23400,12 @@ function runValidator(dirs = []) {
|
|
|
23261
23400
|
for (const from of [...dirs, dirname12(fileURLToPath7(import.meta.url))]) {
|
|
23262
23401
|
let dist;
|
|
23263
23402
|
try {
|
|
23264
|
-
dist = dirname12(createRequire6(
|
|
23403
|
+
dist = dirname12(createRequire6(join37(resolve24(from), "noop.js")).resolve("@intentius/terragucci/report.schema.json"));
|
|
23265
23404
|
} catch {
|
|
23266
23405
|
continue;
|
|
23267
23406
|
}
|
|
23268
|
-
const pkg =
|
|
23269
|
-
const file =
|
|
23407
|
+
const pkg = join37(dist, "..", "package.json");
|
|
23408
|
+
const file = join37(dist, RUN_SCHEMA_FILE);
|
|
23270
23409
|
let stamp;
|
|
23271
23410
|
try {
|
|
23272
23411
|
stamp = [dist, ...[pkg, file].map((f) => {
|
|
@@ -23280,8 +23419,8 @@ function runValidator(dirs = []) {
|
|
|
23280
23419
|
if (hit) return hit;
|
|
23281
23420
|
let v;
|
|
23282
23421
|
try {
|
|
23283
|
-
const version = JSON.parse(
|
|
23284
|
-
v = compileRunSchema(JSON.parse(
|
|
23422
|
+
const version = JSON.parse(readFileSync26(pkg, "utf8")).version ?? "";
|
|
23423
|
+
v = compileRunSchema(JSON.parse(readFileSync26(file, "utf8")), version);
|
|
23285
23424
|
} catch {
|
|
23286
23425
|
continue;
|
|
23287
23426
|
}
|
|
@@ -23307,7 +23446,10 @@ function runKey(row) {
|
|
|
23307
23446
|
var zero = () => ({ done: 0, "in-flight": 0, waiting: 0, "not-applied": 0 });
|
|
23308
23447
|
var MARK_ORDER = ["not-applied", "in-flight", "waiting", "done"];
|
|
23309
23448
|
function unsettled(run3) {
|
|
23310
|
-
|
|
23449
|
+
if (run3.waves.some((w) => w.state === "applying" || (w.progress?.resources ?? []).some((r) => r.status === "in-flight" || r.status === "waiting"))) return true;
|
|
23450
|
+
if (run3.waves.some((w) => w.state === "waiting")) return true;
|
|
23451
|
+
if (run3.waves.some((w) => w.state === "failed" || w.state === "refused")) return false;
|
|
23452
|
+
return run3.waves.some((w) => w.state === "applied") && run3.waves.some((w) => w.state === "not-started");
|
|
23311
23453
|
}
|
|
23312
23454
|
function joinProgress(run3, nodes, roots) {
|
|
23313
23455
|
const byRoot = /* @__PURE__ */ new Map();
|
|
@@ -23350,12 +23492,36 @@ function joinProgress(run3, nodes, roots) {
|
|
|
23350
23492
|
}
|
|
23351
23493
|
waves.push({ number: w.number, state: w.state, gate: w.gate, read: w.progress.read, counts, resources });
|
|
23352
23494
|
}
|
|
23353
|
-
|
|
23495
|
+
const states = run3.waves.map((w) => ({
|
|
23496
|
+
number: w.number,
|
|
23497
|
+
state: w.state,
|
|
23498
|
+
gate: w.gate,
|
|
23499
|
+
roots: [...w.roots],
|
|
23500
|
+
reads: [...w.reads ?? []],
|
|
23501
|
+
...w.approval ? { approval: w.approval } : {},
|
|
23502
|
+
...w.digest !== void 0 ? { digest: w.digest } : {},
|
|
23503
|
+
// The run view writes the approve line while the wave waits; behold's own form when it does not.
|
|
23504
|
+
...w.state === "waiting" ? { command: w.command ?? terragucciApproveCommand(w.number, w.digest ?? null) } : {},
|
|
23505
|
+
...w.report ? { report: `${w.report}/report.html` } : {},
|
|
23506
|
+
...w.changed ? { changed: [...w.changed] } : {},
|
|
23507
|
+
...w.totals ? { totals: { ...w.totals } } : {},
|
|
23508
|
+
...w.totals_roots !== void 0 ? { totals_roots: w.totals_roots } : {},
|
|
23509
|
+
...w.composed ? {
|
|
23510
|
+
composed: {
|
|
23511
|
+
roots: [...w.composed.roots],
|
|
23512
|
+
...w.composed.digest !== void 0 ? { digest: w.composed.digest } : {},
|
|
23513
|
+
...w.composed.unknown ? { unknown: [...w.composed.unknown] } : {},
|
|
23514
|
+
...w.composed.members ? { members: w.composed.members.map((m) => ({ ...m })) } : {}
|
|
23515
|
+
}
|
|
23516
|
+
} : {}
|
|
23517
|
+
}));
|
|
23518
|
+
const reads2 = (run3.roots ?? []).map((r) => ({ root: r.root, wave: r.wave, reads: [...r.reads ?? []], ...r.composed ? { composed: { ...r.composed } } : {} }));
|
|
23519
|
+
return { states, reads: reads2, waves, cards, unmatched, watching: unsettled(run3) };
|
|
23354
23520
|
}
|
|
23355
23521
|
var ProgressGraphError = class extends Error {
|
|
23356
23522
|
};
|
|
23357
23523
|
var FILES = "/api/terragucci/file?key=";
|
|
23358
|
-
var progressKey = (a) => JSON.stringify([a.run, a.waves, a.unmatched]);
|
|
23524
|
+
var progressKey = (a) => JSON.stringify([a.run, a.waves, a.unmatched, a.states, a.reads]);
|
|
23359
23525
|
var TerragucciProgress = class {
|
|
23360
23526
|
constructor(o) {
|
|
23361
23527
|
this.o = o;
|
|
@@ -23410,6 +23576,8 @@ var TerragucciProgress = class {
|
|
|
23410
23576
|
...this.base(project, v?.run.validation),
|
|
23411
23577
|
run: null,
|
|
23412
23578
|
absent: `index.json lists no tf-apply run of ${project} yet, so there is no run view to read.`,
|
|
23579
|
+
states: [],
|
|
23580
|
+
reads: [],
|
|
23413
23581
|
waves: [],
|
|
23414
23582
|
cards: {},
|
|
23415
23583
|
unmatched: [],
|
|
@@ -23446,6 +23614,8 @@ var TerragucciProgress = class {
|
|
|
23446
23614
|
run: { commit, key, present: false },
|
|
23447
23615
|
...unreadable ? { unreadable: true } : {},
|
|
23448
23616
|
absent: unreadable ?? `${src.location} has no ${key}: terragucci writes the run view when reports.bucket is set, and a wave's progress per resource only for a choudoufu wave (terragucci 0.4.7 and newer). This says nothing about whether the apply moved.`,
|
|
23617
|
+
states: [],
|
|
23618
|
+
reads: [],
|
|
23449
23619
|
waves: [],
|
|
23450
23620
|
cards: {},
|
|
23451
23621
|
unmatched: [],
|
|
@@ -23516,6 +23686,90 @@ function terragucciProgressRoute(app, progress) {
|
|
|
23516
23686
|
});
|
|
23517
23687
|
}
|
|
23518
23688
|
|
|
23689
|
+
// src/terragucci-demo.ts
|
|
23690
|
+
import { readFileSync as readFileSync27 } from "node:fs";
|
|
23691
|
+
import { join as join38 } from "node:path";
|
|
23692
|
+
var DEMO_KEY = "demo.json";
|
|
23693
|
+
var DEMO_READ_TTL_MS = 2e3;
|
|
23694
|
+
var DEMO_STEPS = ["pull-request", "planned", "merged", "applying", "waiting", "approved", "failed", "resumed", "applied", "reset", "stalled"];
|
|
23695
|
+
var UTC = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/;
|
|
23696
|
+
var isRecord17 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
23697
|
+
var str13 = (v) => typeof v === "string";
|
|
23698
|
+
var int = (v) => typeof v === "number" && Number.isInteger(v);
|
|
23699
|
+
function demoProblem(doc) {
|
|
23700
|
+
if (!isRecord17(doc)) return "it is not an object";
|
|
23701
|
+
if (!int(doc.cycle)) return "cycle is not an integer";
|
|
23702
|
+
if (!str13(doc.scenario) || !doc.scenario) return "scenario is not a string";
|
|
23703
|
+
if (!str13(doc.title)) return "title is not a string";
|
|
23704
|
+
if (!str13(doc.step) || !DEMO_STEPS.includes(doc.step)) return `step is not one of ${DEMO_STEPS.join(", ")}`;
|
|
23705
|
+
if (!str13(doc.label)) return "label is not a string";
|
|
23706
|
+
if (!str13(doc.started_at) || !UTC.test(doc.started_at)) return "started_at is not a UTC time (YYYY-MM-DDTHH:MM:SSZ)";
|
|
23707
|
+
if (doc.next !== null) {
|
|
23708
|
+
if (!isRecord17(doc.next)) return "next is neither an object nor null";
|
|
23709
|
+
if (!str13(doc.next.action)) return "next.action is not a string";
|
|
23710
|
+
if (doc.next.at !== null && !(str13(doc.next.at) && UTC.test(doc.next.at))) return "next.at is neither a UTC time nor null";
|
|
23711
|
+
}
|
|
23712
|
+
if (doc.pull !== null && !int(doc.pull)) return "pull is neither an integer nor null";
|
|
23713
|
+
if (doc.commit !== null && !str13(doc.commit)) return "commit is neither a string nor null";
|
|
23714
|
+
return void 0;
|
|
23715
|
+
}
|
|
23716
|
+
async function readDemo(source2, now = /* @__PURE__ */ new Date()) {
|
|
23717
|
+
const base = { source: source2.location, read: now.toISOString() };
|
|
23718
|
+
let body;
|
|
23719
|
+
try {
|
|
23720
|
+
body = await source2.read(DEMO_KEY);
|
|
23721
|
+
} catch (e) {
|
|
23722
|
+
if (e instanceof TerragucciDenied) return { ...base, demo: null, error: deniedWords(e, "the demo's narration"), code: "terragucci-demo" };
|
|
23723
|
+
return { ...base, demo: null, error: `behold could not read ${DEMO_KEY} from ${source2.location}: ${e instanceof Error ? e.message : String(e)}`, code: "terragucci-demo" };
|
|
23724
|
+
}
|
|
23725
|
+
if (body === void 0) {
|
|
23726
|
+
return { ...base, demo: null, absent: `${source2.location} has no ${DEMO_KEY}: the demo's loop driver writes it at every step (terragucci's stack/demo.sh), beside index.json.` };
|
|
23727
|
+
}
|
|
23728
|
+
let doc;
|
|
23729
|
+
try {
|
|
23730
|
+
doc = JSON.parse(body);
|
|
23731
|
+
} catch {
|
|
23732
|
+
return { ...base, demo: null, error: `${DEMO_KEY} in ${source2.location} is not JSON.`, code: "terragucci-demo" };
|
|
23733
|
+
}
|
|
23734
|
+
const problem = demoProblem(doc);
|
|
23735
|
+
if (problem) return { ...base, demo: null, error: `${DEMO_KEY} in ${source2.location} is not the demo's format: ${problem}.`, code: "terragucci-demo" };
|
|
23736
|
+
const d = doc;
|
|
23737
|
+
return {
|
|
23738
|
+
...base,
|
|
23739
|
+
demo: {
|
|
23740
|
+
cycle: d.cycle,
|
|
23741
|
+
scenario: d.scenario,
|
|
23742
|
+
title: d.title,
|
|
23743
|
+
step: d.step,
|
|
23744
|
+
label: d.label,
|
|
23745
|
+
started_at: d.started_at,
|
|
23746
|
+
next: d.next ? { action: d.next.action, at: d.next.at } : null,
|
|
23747
|
+
pull: d.pull,
|
|
23748
|
+
commit: d.commit
|
|
23749
|
+
}
|
|
23750
|
+
};
|
|
23751
|
+
}
|
|
23752
|
+
function terragucciDemoRoute(app, source2, opts = {}) {
|
|
23753
|
+
let cached;
|
|
23754
|
+
app.get("/api/terragucci/demo", async (c) => {
|
|
23755
|
+
const at2 = Date.now();
|
|
23756
|
+
if (!cached || at2 - cached.at >= DEMO_READ_TTL_MS) cached = { at: at2, read: readDemo(source2, opts.now ? opts.now() : /* @__PURE__ */ new Date()) };
|
|
23757
|
+
const a = await cached.read;
|
|
23758
|
+
return c.json(a, "error" in a ? 502 : 200, { "cache-control": "no-store" });
|
|
23759
|
+
});
|
|
23760
|
+
}
|
|
23761
|
+
function kioskRoute(app, webRoot2, enabled) {
|
|
23762
|
+
app.get("/kiosk", (c) => {
|
|
23763
|
+
if (!enabled) {
|
|
23764
|
+
return c.html(
|
|
23765
|
+
`<!doctype html><meta charset=utf-8><title>behold kiosk</title><script type="module">import { initTheme } from "/theme.js"; initTheme();</script><body style="font:16px system-ui;background:var(--bg,#1e1e2e);color:var(--fg,#cdd6f4);padding:2rem"><h3>The kiosk follows a terragucci demo</h3><p style="color:var(--muted,#777a8c)">Start behold with <code>--terragucci <reports source></code>; the kiosk reads <code>demo.json</code> there.</p></body>`,
|
|
23766
|
+
404
|
|
23767
|
+
);
|
|
23768
|
+
}
|
|
23769
|
+
return new Response(readFileSync27(join38(webRoot2, "kiosk.html"), "utf8"), { headers: { "content-type": "text/html; charset=utf-8", "cache-control": "no-cache" } });
|
|
23770
|
+
});
|
|
23771
|
+
}
|
|
23772
|
+
|
|
23519
23773
|
// src/terragucci-view.ts
|
|
23520
23774
|
var TERRAGUCCI_READ_TTL_MS = 3e4;
|
|
23521
23775
|
function repoBase(dirs) {
|
|
@@ -23554,7 +23808,7 @@ function checkoutRoots(dirs, base = repoBase(dirs)) {
|
|
|
23554
23808
|
for (const m of estateMembers(dirs)) {
|
|
23555
23809
|
if (m.kind !== "terraform") continue;
|
|
23556
23810
|
for (const r of discoverTerraformRoots(m.dir).roots) {
|
|
23557
|
-
const path = relative8(base,
|
|
23811
|
+
const path = relative8(base, join39(m.dir, r.dir)).split(sep7).join("/") || ".";
|
|
23558
23812
|
out.push({ member: m.name, name: r.name, path });
|
|
23559
23813
|
}
|
|
23560
23814
|
}
|
|
@@ -23630,9 +23884,12 @@ function terragucciRoutes(app, opts, dirs) {
|
|
|
23630
23884
|
// Where a key opens: this server, which reads it from the same source.
|
|
23631
23885
|
files: "/api/terragucci/file?key=",
|
|
23632
23886
|
...r.estate ? { estate: r.estate } : {},
|
|
23887
|
+
// #568: the pull request stage, read from the newest tf-plan of a pull request.
|
|
23888
|
+
...r.pull_request ? { pull_request: r.pull_request } : {},
|
|
23633
23889
|
...marks
|
|
23634
23890
|
});
|
|
23635
23891
|
});
|
|
23892
|
+
terragucciDemoRoute(app, source2, opts.now ? { now: opts.now } : {});
|
|
23636
23893
|
terragucciTimelineRoute(app, source2, {
|
|
23637
23894
|
...opts.project ? { project: opts.project } : {},
|
|
23638
23895
|
checkoutProject: () => checkoutProject(repoBase(dirs())),
|
|
@@ -23697,7 +23954,7 @@ var heldByOf = (v) => {
|
|
|
23697
23954
|
return h && typeof h === "object" && typeof h.hook === "string" && typeof h.moment === "string" ? { hook: h.hook, moment: h.moment, ...typeof h.target === "string" ? { target: h.target } : {} } : void 0;
|
|
23698
23955
|
};
|
|
23699
23956
|
var modeOf = (v) => v === "ledger" || v === "pr-review" || v === "sealed" ? v : void 0;
|
|
23700
|
-
var
|
|
23957
|
+
var str14 = (v) => typeof v === "string" && v !== "" ? v : void 0;
|
|
23701
23958
|
var strings3 = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : void 0;
|
|
23702
23959
|
var time3 = (iso) => Date.parse(iso) || 0;
|
|
23703
23960
|
function rowOf(line) {
|
|
@@ -23713,14 +23970,14 @@ function rowOf(line) {
|
|
|
23713
23970
|
}
|
|
23714
23971
|
function sealOf(r) {
|
|
23715
23972
|
const s = r.seal;
|
|
23716
|
-
const signed = s && s.signature ? { sealed: true, ...
|
|
23973
|
+
const signed = s && s.signature ? { sealed: true, ...str14(s.signer) ? { signer: str14(s.signer) } : {} } : void 0;
|
|
23717
23974
|
if (r.review || r.via === "pr-review") return { seal: "review", ...signed };
|
|
23718
23975
|
return signed ? { seal: "sealed", ...signed } : { seal: "unsigned" };
|
|
23719
23976
|
}
|
|
23720
23977
|
function reviewOf(r) {
|
|
23721
23978
|
const rv = r.review ?? void 0;
|
|
23722
23979
|
const pr = typeof rv?.pr === "number" ? rv.pr : typeof r.pr === "number" ? r.pr : void 0;
|
|
23723
|
-
const head =
|
|
23980
|
+
const head = str14(rv?.head) ?? str14(r.head);
|
|
23724
23981
|
const approvers = strings3(rv?.approvers) ?? strings3(r.reviewers);
|
|
23725
23982
|
if (pr === void 0 && head === void 0 && approvers === void 0) return void 0;
|
|
23726
23983
|
return { ...pr !== void 0 ? { pr } : {}, ...head ? { head } : {}, ...approvers ? { approvers } : {} };
|
|
@@ -23746,7 +24003,7 @@ function decisionOf(d) {
|
|
|
23746
24003
|
const r = v;
|
|
23747
24004
|
if (!r || typeof r !== "object" || typeof r.wave !== "number" || typeof r.name !== "string" || typeof r.op !== "string" || typeof r.gate !== "string") return void 0;
|
|
23748
24005
|
const outcomes2 = Array.isArray(r.outcomes) ? r.outcomes.filter((o) => !!o && typeof o === "object" && typeof o.moment === "string" && typeof o.name === "string" && typeof o.exit === "number" && typeof o.outcome === "string") : void 0;
|
|
23749
|
-
return { wave: r.wave, name: r.name, op: r.op, gate: r.gate, ...
|
|
24006
|
+
return { wave: r.wave, name: r.name, op: r.op, gate: r.gate, ...str14(r.digest) ? { digest: str14(r.digest) } : {}, ...strings3(r.heldBy) ? { heldBy: strings3(r.heldBy) } : {}, ...r.heldOn !== void 0 ? { heldOn: r.heldOn } : {}, ...outcomes2 ? { outcomes: outcomes2 } : {} };
|
|
23750
24007
|
}
|
|
23751
24008
|
function deriveGateAudit(input) {
|
|
23752
24009
|
const isOverride = input.isOverride ?? isOverrideGate2;
|
|
@@ -23771,17 +24028,17 @@ function deriveGateAudit(input) {
|
|
|
23771
24028
|
const requests = [];
|
|
23772
24029
|
for (const p of parsed) {
|
|
23773
24030
|
if (!p.pending || p.line.removed) continue;
|
|
23774
|
-
const digest =
|
|
24031
|
+
const digest = str14(p.r.planDigest);
|
|
23775
24032
|
denials.set(`${String(p.r.gate)}
|
|
23776
24033
|
${digest ?? ""}`, p.r);
|
|
23777
|
-
requests.push({ op: ledger.op, gate: String(p.r.gate), at: time3(String(p.r.timestamp)), ...digest ? { digest } : {}, ...
|
|
24034
|
+
requests.push({ op: ledger.op, gate: String(p.r.gate), at: time3(String(p.r.timestamp)), ...digest ? { digest } : {}, ...str14(p.r.environment) ? { environment: str14(p.r.environment) } : {} });
|
|
23778
24035
|
}
|
|
23779
24036
|
for (const { line, n, r, pending } of parsed) {
|
|
23780
24037
|
const gate = String(r.gate);
|
|
23781
24038
|
const override = isOverride(ledger.op, gate);
|
|
23782
24039
|
const removed = line.removed;
|
|
23783
24040
|
if (pending && removed) continue;
|
|
23784
|
-
const digest =
|
|
24041
|
+
const digest = str14(r.planDigest);
|
|
23785
24042
|
const author = removed ?? line.added;
|
|
23786
24043
|
const source2 = {
|
|
23787
24044
|
path,
|
|
@@ -23800,7 +24057,7 @@ ${digest ?? ""}`, p.r);
|
|
|
23800
24057
|
...base,
|
|
23801
24058
|
kind: override ? "override-requested" : "approval-requested",
|
|
23802
24059
|
at: String(r.timestamp),
|
|
23803
|
-
...drop({ runId:
|
|
24060
|
+
...drop({ runId: str14(r.runId), expiresAt: str14(r.expiresAt), description: str14(r.description), url: str14(r.url), rules: strings3(r.rules), wave: waveOf(r.wave), heldBy: heldByOf(r.heldBy), approvalMode: modeOf(r.approvalMode) }),
|
|
23804
24061
|
detail: extra2
|
|
23805
24062
|
}));
|
|
23806
24063
|
continue;
|
|
@@ -23828,13 +24085,13 @@ ${digest ?? ""}`) : void 0;
|
|
|
23828
24085
|
at: String(r.timestamp),
|
|
23829
24086
|
by: String(r.resolvedBy),
|
|
23830
24087
|
seal,
|
|
23831
|
-
...drop({ sealed, signer, committedBy: line.added?.by, relayedBy:
|
|
24088
|
+
...drop({ sealed, signer, committedBy: line.added?.by, relayedBy: str14(r.relayedBy), approver: approverOf(r), note: str14(r.note), url: str14(r.url), review: reviewOf(r), rules }),
|
|
23832
24089
|
detail: extra2
|
|
23833
24090
|
});
|
|
23834
24091
|
entries.push(entry);
|
|
23835
24092
|
if (!override) {
|
|
23836
24093
|
const at2 = time3(entry.at);
|
|
23837
|
-
const env =
|
|
24094
|
+
const env = str14(r.environment);
|
|
23838
24095
|
const asked = requests.some((q) => q.gate === gate && q.at <= at2 && compatible(q.digest, digest) && (q.environment === void 0 || env === void 0 || q.environment === env));
|
|
23839
24096
|
if (!asked) {
|
|
23840
24097
|
gaps.push({ kind: "approval-without-request", op: ledger.op, gate, ...digest ? { digest } : {}, detail: `${path} holds an approval of ${gate} by ${entry.by} at ${entry.at} with no pending fact of that gate${digest ? " and digest" : ""} before it` });
|
|
@@ -24109,8 +24366,8 @@ function lifecycleHistoryRoute(app, base, opts = {}) {
|
|
|
24109
24366
|
}
|
|
24110
24367
|
|
|
24111
24368
|
// src/lifecycle-releases.ts
|
|
24112
|
-
import { existsSync as existsSync22, readFileSync as
|
|
24113
|
-
import { join as
|
|
24369
|
+
import { existsSync as existsSync22, readFileSync as readFileSync28 } from "node:fs";
|
|
24370
|
+
import { join as join40 } from "node:path";
|
|
24114
24371
|
|
|
24115
24372
|
// node_modules/@intentius/chant/src/lifecycle/legacy-digest.ts
|
|
24116
24373
|
import { resolve as resolve25 } from "node:path";
|
|
@@ -24223,10 +24480,10 @@ function memberNote(l) {
|
|
|
24223
24480
|
// src/lifecycle-releases.ts
|
|
24224
24481
|
var WAVES_SPEC_FILE = "yodel-waves.json";
|
|
24225
24482
|
function waveOrder(checkout) {
|
|
24226
|
-
const p =
|
|
24483
|
+
const p = join40(checkout, WAVES_SPEC_FILE);
|
|
24227
24484
|
if (!existsSync22(p)) return void 0;
|
|
24228
24485
|
try {
|
|
24229
|
-
const doc = JSON.parse(
|
|
24486
|
+
const doc = JSON.parse(readFileSync28(p, "utf8"));
|
|
24230
24487
|
const names = (doc.waves ?? []).map((w) => w?.name).filter((n) => typeof n === "string" && n.length > 0);
|
|
24231
24488
|
return names.length ? [...new Set(names)] : void 0;
|
|
24232
24489
|
} catch {
|
|
@@ -24234,7 +24491,7 @@ function waveOrder(checkout) {
|
|
|
24234
24491
|
}
|
|
24235
24492
|
}
|
|
24236
24493
|
function hasReleaseLedgerHint(checkout) {
|
|
24237
|
-
return existsSync22(
|
|
24494
|
+
return existsSync22(join40(checkout, "yodel.config.ts")) || existsSync22(join40(checkout, WAVES_SPEC_FILE));
|
|
24238
24495
|
}
|
|
24239
24496
|
function orderEnvironments(envs, waves) {
|
|
24240
24497
|
const seen = new Set(envs);
|
|
@@ -24276,7 +24533,7 @@ function releaseCells(ledgers, order, sources) {
|
|
|
24276
24533
|
}
|
|
24277
24534
|
async function copyCheckoutRef(git3, checkout, cache6) {
|
|
24278
24535
|
assertScratch(cache6.split(/[\\/]/).pop());
|
|
24279
|
-
if (!existsSync22(
|
|
24536
|
+
if (!existsSync22(join40(cache6, "HEAD"))) {
|
|
24280
24537
|
const init = await git3(["init", "--bare", "-q", cache6]);
|
|
24281
24538
|
if (init.code !== 0) return false;
|
|
24282
24539
|
}
|
|
@@ -24379,8 +24636,8 @@ function releasesRoutes(app, base, opts = {}) {
|
|
|
24379
24636
|
}
|
|
24380
24637
|
|
|
24381
24638
|
// src/sql-yodeler-lane.ts
|
|
24382
|
-
import { existsSync as existsSync23, readdirSync as readdirSync12, readFileSync as
|
|
24383
|
-
import { join as
|
|
24639
|
+
import { existsSync as existsSync23, readdirSync as readdirSync12, readFileSync as readFileSync29 } from "node:fs";
|
|
24640
|
+
import { join as join41 } from "node:path";
|
|
24384
24641
|
var YODEL_APPLY_OP = "yodel-apply";
|
|
24385
24642
|
var YODEL_HEAD_PLANS_DIR = headPlansPath(YODEL_APPLY_OP, "").dir;
|
|
24386
24643
|
function parseYodelWaves(text, path = WAVES_SPEC_FILE) {
|
|
@@ -24412,16 +24669,16 @@ function parseYodelWaves(text, path = WAVES_SPEC_FILE) {
|
|
|
24412
24669
|
return { path, waves };
|
|
24413
24670
|
}
|
|
24414
24671
|
function readYodelWaves(checkout) {
|
|
24415
|
-
const p =
|
|
24672
|
+
const p = join41(checkout, WAVES_SPEC_FILE);
|
|
24416
24673
|
if (!existsSync23(p)) return void 0;
|
|
24417
24674
|
try {
|
|
24418
|
-
return parseYodelWaves(
|
|
24675
|
+
return parseYodelWaves(readFileSync29(p, "utf8"));
|
|
24419
24676
|
} catch (e) {
|
|
24420
24677
|
return { path: WAVES_SPEC_FILE, waves: [], error: e instanceof Error ? e.message : String(e) };
|
|
24421
24678
|
}
|
|
24422
24679
|
}
|
|
24423
24680
|
function migrateOpEnvironments(checkout) {
|
|
24424
|
-
const dir =
|
|
24681
|
+
const dir = join41(checkout, "ops");
|
|
24425
24682
|
if (!existsSync23(dir)) return [];
|
|
24426
24683
|
try {
|
|
24427
24684
|
return readdirSync12(dir).map((f) => /^migrate-(.+)\.op\.ts$/.exec(f)?.[1]).filter((e) => !!e).sort();
|
|
@@ -24544,16 +24801,16 @@ function sqlYodelerLaneRoute(app, checkout, opts = {}) {
|
|
|
24544
24801
|
|
|
24545
24802
|
// src/terragucci-estate.ts
|
|
24546
24803
|
init_yaml();
|
|
24547
|
-
import { existsSync as existsSync24, readFileSync as
|
|
24804
|
+
import { existsSync as existsSync24, readFileSync as readFileSync30, statSync as statSync13 } from "node:fs";
|
|
24548
24805
|
import { createRequire as createRequire7 } from "node:module";
|
|
24549
|
-
import { dirname as dirname14, extname, join as
|
|
24806
|
+
import { dirname as dirname14, extname, join as join42, resolve as resolve26 } from "node:path";
|
|
24550
24807
|
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
24551
24808
|
import { Ajv2020 as Ajv20204 } from "ajv/dist/2020.js";
|
|
24552
24809
|
var RUN_SCHEMA2 = "terragucci.run/v1";
|
|
24553
24810
|
function configProjects(file) {
|
|
24554
24811
|
let text;
|
|
24555
24812
|
try {
|
|
24556
|
-
text =
|
|
24813
|
+
text = readFileSync30(file, "utf8");
|
|
24557
24814
|
} catch {
|
|
24558
24815
|
return void 0;
|
|
24559
24816
|
}
|
|
@@ -24581,21 +24838,21 @@ function controlRepo(dirs, roots = () => checkoutRoots(dirs).length) {
|
|
|
24581
24838
|
}
|
|
24582
24839
|
return n === 0 ? { config, projects } : void 0;
|
|
24583
24840
|
}
|
|
24584
|
-
var
|
|
24585
|
-
var
|
|
24586
|
-
var
|
|
24841
|
+
var isRecord18 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
24842
|
+
var str15 = (v) => typeof v === "string";
|
|
24843
|
+
var int2 = (v) => typeof v === "number" && Number.isInteger(v);
|
|
24587
24844
|
function structuralRun2(doc) {
|
|
24588
|
-
if (!
|
|
24845
|
+
if (!isRecord18(doc)) return "it is not a JSON object";
|
|
24589
24846
|
if (doc.schema !== RUN_SCHEMA2) return `its schema is ${JSON.stringify(doc.schema)}, not ${RUN_SCHEMA2}`;
|
|
24590
|
-
if (!
|
|
24847
|
+
if (!str15(doc.project) || !str15(doc.commit) || !str15(doc.updated)) return "it lacks project, commit or updated";
|
|
24591
24848
|
if (!Array.isArray(doc.roots) || !Array.isArray(doc.waves)) return "it has no roots or waves array";
|
|
24592
24849
|
for (const [i, r] of doc.roots.entries()) {
|
|
24593
|
-
if (!
|
|
24594
|
-
if (r.state !== void 0 && !(
|
|
24595
|
-
if (r.external !== void 0 && !(Array.isArray(r.external) && r.external.every((x) =>
|
|
24850
|
+
if (!isRecord18(r) || !str15(r.root) || !int2(r.wave) || !Array.isArray(r.reads) || !r.reads.every(str15)) return `root ${i} lacks root, wave or reads`;
|
|
24851
|
+
if (r.state !== void 0 && !(isRecord18(r.state) && str15(r.state.key))) return `root ${String(r.root)}'s state has no key`;
|
|
24852
|
+
if (r.external !== void 0 && !(Array.isArray(r.external) && r.external.every((x) => isRecord18(x) && str15(x.data) && str15(x.key)))) return `root ${String(r.root)}'s external reads lack data or key`;
|
|
24596
24853
|
}
|
|
24597
24854
|
for (const [i, w] of doc.waves.entries()) {
|
|
24598
|
-
if (!
|
|
24855
|
+
if (!isRecord18(w) || !int2(w.number) || !Array.isArray(w.roots) || !Array.isArray(w.reads) || !str15(w.state) || !str15(w.gate)) return `wave ${i} lacks number, roots, reads, state or gate`;
|
|
24599
24856
|
}
|
|
24600
24857
|
return void 0;
|
|
24601
24858
|
}
|
|
@@ -24604,16 +24861,16 @@ function runViewValidator(dirs = []) {
|
|
|
24604
24861
|
for (const from of [...dirs, dirname14(fileURLToPath8(import.meta.url))]) {
|
|
24605
24862
|
let entry;
|
|
24606
24863
|
try {
|
|
24607
|
-
entry = createRequire7(
|
|
24864
|
+
entry = createRequire7(join42(resolve26(from), "noop.js")).resolve("@intentius/terragucci/report.schema.json");
|
|
24608
24865
|
} catch {
|
|
24609
24866
|
continue;
|
|
24610
24867
|
}
|
|
24611
24868
|
const dist = dirname14(entry);
|
|
24612
|
-
const schema =
|
|
24869
|
+
const schema = join42(dist, "run.schema.json");
|
|
24613
24870
|
let stamp;
|
|
24614
24871
|
try {
|
|
24615
24872
|
const st = statSync13(schema);
|
|
24616
|
-
const pkg = statSync13(
|
|
24873
|
+
const pkg = statSync13(join42(dist, "..", "package.json"));
|
|
24617
24874
|
stamp = `${schema}\0${st.mtimeMs}:${st.size}\0${pkg.mtimeMs}`;
|
|
24618
24875
|
} catch {
|
|
24619
24876
|
continue;
|
|
@@ -24622,12 +24879,12 @@ function runViewValidator(dirs = []) {
|
|
|
24622
24879
|
if (hit) return hit;
|
|
24623
24880
|
let v;
|
|
24624
24881
|
try {
|
|
24625
|
-
const version = JSON.parse(
|
|
24626
|
-
const validate = new Ajv20204({ strict: false, allErrors: false }).compile(JSON.parse(
|
|
24882
|
+
const version = JSON.parse(readFileSync30(join42(dist, "..", "package.json"), "utf8")).version ?? "";
|
|
24883
|
+
const validate = new Ajv20204({ strict: false, allErrors: false }).compile(JSON.parse(readFileSync30(schema, "utf8")));
|
|
24627
24884
|
v = {
|
|
24628
24885
|
validation: { by: "schema", terragucci: version },
|
|
24629
24886
|
check(doc) {
|
|
24630
|
-
const id =
|
|
24887
|
+
const id = isRecord18(doc) ? doc.schema : void 0;
|
|
24631
24888
|
if (id !== RUN_SCHEMA2) return `its schema is ${JSON.stringify(id)}, not ${RUN_SCHEMA2}`;
|
|
24632
24889
|
if (validate(doc)) return void 0;
|
|
24633
24890
|
const e = validate.errors?.[0];
|
|
@@ -24678,8 +24935,8 @@ function controlEdges(views) {
|
|
|
24678
24935
|
function siblingCheckout(project, base, projectOf = checkoutProject) {
|
|
24679
24936
|
const name = project.split("/").filter(Boolean).pop();
|
|
24680
24937
|
if (!name) return void 0;
|
|
24681
|
-
const dir =
|
|
24682
|
-
if (dir === resolve26(base) || !existsSync24(
|
|
24938
|
+
const dir = join42(dirname14(resolve26(base)), name);
|
|
24939
|
+
if (dir === resolve26(base) || !existsSync24(join42(dir, ".git"))) return void 0;
|
|
24683
24940
|
return projectOf(dir) === project ? dir : void 0;
|
|
24684
24941
|
}
|
|
24685
24942
|
var shellWord = (s) => /^[A-Za-z0-9_./:@%+=,-]+$/.test(s) ? s : `'${s.replace(/'/g, `'\\''`)}'`;
|
|
@@ -24886,9 +25143,9 @@ function controlRepoInfo(dirs) {
|
|
|
24886
25143
|
}
|
|
24887
25144
|
|
|
24888
25145
|
// src/yodel-report.ts
|
|
24889
|
-
import { readdirSync as readdirSync13, readFileSync as
|
|
25146
|
+
import { readdirSync as readdirSync13, readFileSync as readFileSync31, statSync as statSync14 } from "node:fs";
|
|
24890
25147
|
import { createRequire as createRequire8 } from "node:module";
|
|
24891
|
-
import { basename as basename4, dirname as dirname15, join as
|
|
25148
|
+
import { basename as basename4, dirname as dirname15, join as join43, resolve as resolve27 } from "node:path";
|
|
24892
25149
|
import { fileURLToPath as fileURLToPath9 } from "node:url";
|
|
24893
25150
|
import { Ajv2020 as Ajv20205 } from "ajv/dist/2020.js";
|
|
24894
25151
|
|
|
@@ -25317,13 +25574,13 @@ var installed = /* @__PURE__ */ new Map();
|
|
|
25317
25574
|
function packageRoot(from) {
|
|
25318
25575
|
let entry;
|
|
25319
25576
|
try {
|
|
25320
|
-
entry = createRequire8(
|
|
25577
|
+
entry = createRequire8(join43(resolve27(from), "noop.js")).resolve("@intentius/sql-yodeler");
|
|
25321
25578
|
} catch {
|
|
25322
25579
|
return void 0;
|
|
25323
25580
|
}
|
|
25324
25581
|
for (let d = dirname15(entry); d !== dirname15(d); d = dirname15(d)) {
|
|
25325
25582
|
try {
|
|
25326
|
-
const pkg = JSON.parse(
|
|
25583
|
+
const pkg = JSON.parse(readFileSync31(join43(d, "package.json"), "utf8"));
|
|
25327
25584
|
if (pkg.name === "@intentius/sql-yodeler") return d;
|
|
25328
25585
|
} catch {
|
|
25329
25586
|
}
|
|
@@ -25335,11 +25592,11 @@ function yodelValidator(dirs = []) {
|
|
|
25335
25592
|
const root = packageRoot(from);
|
|
25336
25593
|
if (!root) continue;
|
|
25337
25594
|
try {
|
|
25338
|
-
const version = JSON.parse(
|
|
25595
|
+
const version = JSON.parse(readFileSync31(join43(root, "package.json"), "utf8")).version ?? "";
|
|
25339
25596
|
const key = `${root}\0${version}`;
|
|
25340
25597
|
let v = installed.get(key);
|
|
25341
25598
|
if (!v) {
|
|
25342
|
-
const load2 = (n) => JSON.parse(
|
|
25599
|
+
const load2 = (n) => JSON.parse(readFileSync31(join43(root, "schemas", `${n}.schema.json`), "utf8"));
|
|
25343
25600
|
v = compile({ common: load2("common"), report: load2("report"), drift: load2("drift") }, { by: "schema", from: "package", sqlYodeler: version });
|
|
25344
25601
|
installed.set(key, v);
|
|
25345
25602
|
}
|
|
@@ -25350,9 +25607,9 @@ function yodelValidator(dirs = []) {
|
|
|
25350
25607
|
pinned ??= compile({ common: common_schema_default, report: report_schema_default, drift: drift_schema_default }, { by: "schema", from: "pinned", commit: PINNED_COMMIT.slice(0, 7) });
|
|
25351
25608
|
return pinned;
|
|
25352
25609
|
}
|
|
25353
|
-
var
|
|
25354
|
-
var looksLikeReport = (d) =>
|
|
25355
|
-
var looksLikeDrift = (d) =>
|
|
25610
|
+
var isRecord19 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
25611
|
+
var looksLikeReport = (d) => isRecord19(d) && "generatedAt" in d && "environments" in d;
|
|
25612
|
+
var looksLikeDrift = (d) => isRecord19(d) && "environment" in d && ("drifted" in d || "changed" in d);
|
|
25356
25613
|
function filesOf(source2) {
|
|
25357
25614
|
let st;
|
|
25358
25615
|
try {
|
|
@@ -25361,7 +25618,7 @@ function filesOf(source2) {
|
|
|
25361
25618
|
return refuse14(`--yodel-report ${source2}: no such file or directory`, "Point --yodel-report at the file `yodel report --json` wrote, or the directory holding it.");
|
|
25362
25619
|
}
|
|
25363
25620
|
const dir = st.isDirectory() ? source2 : dirname15(source2);
|
|
25364
|
-
const files = readdirSync13(dir).filter((f) => f.endsWith(".json")).sort().map((f) =>
|
|
25621
|
+
const files = readdirSync13(dir).filter((f) => f.endsWith(".json")).sort().map((f) => join43(dir, f));
|
|
25365
25622
|
return st.isDirectory() ? { dir, files } : { dir, files, named: resolve27(source2) };
|
|
25366
25623
|
}
|
|
25367
25624
|
function readYodelReport(source2, validator2) {
|
|
@@ -25373,7 +25630,7 @@ function readYodelReport(source2, validator2) {
|
|
|
25373
25630
|
for (const file of files) {
|
|
25374
25631
|
let doc;
|
|
25375
25632
|
try {
|
|
25376
|
-
doc = JSON.parse(
|
|
25633
|
+
doc = JSON.parse(readFileSync31(file, "utf8"));
|
|
25377
25634
|
} catch (e) {
|
|
25378
25635
|
if (file === named) refuse14(`${basename4(file)} is not JSON: ${e instanceof Error ? e.message : String(e)}`, remedy);
|
|
25379
25636
|
ignored.push(basename4(file));
|
|
@@ -25460,24 +25717,24 @@ function yodelReportRoute(app, source2, dirs) {
|
|
|
25460
25717
|
|
|
25461
25718
|
// src/demo-pins.ts
|
|
25462
25719
|
init_terraform_member();
|
|
25463
|
-
import { existsSync as existsSync25, lstatSync as lstatSync2, mkdirSync as mkdirSync7, readFileSync as
|
|
25464
|
-
import { dirname as dirname16, join as
|
|
25465
|
-
var
|
|
25720
|
+
import { existsSync as existsSync25, lstatSync as lstatSync2, mkdirSync as mkdirSync7, readFileSync as readFileSync32, readlinkSync as readlinkSync2, symlinkSync as symlinkSync2, writeFileSync as writeFileSync6 } from "node:fs";
|
|
25721
|
+
import { dirname as dirname16, join as join44, resolve as resolve28 } from "node:path";
|
|
25722
|
+
var isRecord20 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
25466
25723
|
function linkDemoPins(work, probes = {}) {
|
|
25467
|
-
const file =
|
|
25724
|
+
const file = join44(work, "chant.workspace.json");
|
|
25468
25725
|
if (!existsSync25(file)) return { ok: true, linked: [] };
|
|
25469
25726
|
let decl;
|
|
25470
25727
|
try {
|
|
25471
|
-
decl = JSON.parse(
|
|
25728
|
+
decl = JSON.parse(readFileSync32(file, "utf8"));
|
|
25472
25729
|
} catch {
|
|
25473
25730
|
return { ok: true, linked: [] };
|
|
25474
25731
|
}
|
|
25475
|
-
if (!
|
|
25732
|
+
if (!isRecord20(decl) || !Array.isArray(decl.pins)) return { ok: true, linked: [] };
|
|
25476
25733
|
const find = probes.resolve ?? ((pkg) => resolvedPackage(pkg));
|
|
25477
25734
|
const linked = [];
|
|
25478
25735
|
let rewrite = false;
|
|
25479
25736
|
for (const pin of decl.pins) {
|
|
25480
|
-
if (!
|
|
25737
|
+
if (!isRecord20(pin) || typeof pin.package !== "string") continue;
|
|
25481
25738
|
const pkg = pin.package;
|
|
25482
25739
|
if (pkg === TERRAFORM_LEXICON_PKG) {
|
|
25483
25740
|
const refusal2 = (probes.reader ?? terraformReaderState)().refusal;
|
|
@@ -25491,7 +25748,7 @@ function linkDemoPins(work, probes = {}) {
|
|
|
25491
25748
|
remedy: `Install ${pkg} beside behold, then load the demo again.`
|
|
25492
25749
|
};
|
|
25493
25750
|
}
|
|
25494
|
-
const link =
|
|
25751
|
+
const link = join44(work, "node_modules", ...pkg.split("/"));
|
|
25495
25752
|
if (!present(link)) {
|
|
25496
25753
|
mkdirSync7(dirname16(link), { recursive: true });
|
|
25497
25754
|
symlinkSync2(found.dir, link, "junction");
|
|
@@ -25526,13 +25783,13 @@ function linksTo(link, target) {
|
|
|
25526
25783
|
}
|
|
25527
25784
|
|
|
25528
25785
|
// src/demos.ts
|
|
25529
|
-
import { readFileSync as
|
|
25530
|
-
import { dirname as dirname17, isAbsolute as isAbsolute5, join as
|
|
25786
|
+
import { readFileSync as readFileSync33, existsSync as existsSync26, cpSync, mkdirSync as mkdirSync8 } from "node:fs";
|
|
25787
|
+
import { dirname as dirname17, isAbsolute as isAbsolute5, join as join45, relative as relative9, resolve as resolve29, sep as sep8 } from "node:path";
|
|
25531
25788
|
import { spawn as spawn8, spawnSync as spawnSync3 } from "node:child_process";
|
|
25532
25789
|
function readCatalog(file, catalog) {
|
|
25533
25790
|
let raw;
|
|
25534
25791
|
try {
|
|
25535
|
-
raw = JSON.parse(
|
|
25792
|
+
raw = JSON.parse(readFileSync33(file, "utf8"));
|
|
25536
25793
|
} catch {
|
|
25537
25794
|
return [];
|
|
25538
25795
|
}
|
|
@@ -25572,10 +25829,10 @@ function validEntry(e) {
|
|
|
25572
25829
|
return true;
|
|
25573
25830
|
}
|
|
25574
25831
|
function loadDemoRegistry(pkgRoot3) {
|
|
25575
|
-
const merged = readCatalog(
|
|
25832
|
+
const merged = readCatalog(join45(pkgRoot3, "demos.json"), "demos");
|
|
25576
25833
|
const seen = new Set(merged.map((e) => e.name));
|
|
25577
25834
|
const named = process.env.BEHOLD_WORKBENCH;
|
|
25578
|
-
for (const e of [...readCatalog(
|
|
25835
|
+
for (const e of [...readCatalog(join45(pkgRoot3, "workbench.json"), "workbench"), ...named ? readCatalog(resolve29(named), "workbench") : []]) {
|
|
25579
25836
|
if (seen.has(e.name)) {
|
|
25580
25837
|
process.stderr.write(`behold: workbench entry "${e.name}" collides with a demo already in the catalog \u2014 dropped
|
|
25581
25838
|
`);
|
|
@@ -25622,7 +25879,7 @@ async function loadDemo(entry, opts) {
|
|
|
25622
25879
|
say(`serving ${work} where it is \u2014 nothing copied`);
|
|
25623
25880
|
} else if (!existsSync26(target)) {
|
|
25624
25881
|
if (entry.source === "bundled" || entry.source === "local" && local) {
|
|
25625
|
-
const from = entry.source === "bundled" ?
|
|
25882
|
+
const from = entry.source === "bundled" ? join45(pkgRoot3, entry.dir) : local;
|
|
25626
25883
|
if (!existsSync26(from)) return { ok: false, error: entry.source === "bundled" ? `this install has no bundled ${entry.dir}` : `${entry.path} is not checked out (looked in ${from})` };
|
|
25627
25884
|
say(`copying to ${target} (it's yours \u2014 edit it)`);
|
|
25628
25885
|
try {
|
|
@@ -25654,7 +25911,7 @@ async function loadDemo(entry, opts) {
|
|
|
25654
25911
|
if (!pins.ok) return { ok: false, error: `${pins.error} ${pins.remedy}` };
|
|
25655
25912
|
for (const p of pins.linked) say(`linked ${p.pkg} ${p.version} into the copy${p.was ? ` (the declaration pinned ${p.was}; now ${p.version})` : ""}`);
|
|
25656
25913
|
}
|
|
25657
|
-
if (!inPlace && existsSync26(
|
|
25914
|
+
if (!inPlace && existsSync26(join45(work, "package.json")) && !existsSync26(join45(work, "node_modules"))) {
|
|
25658
25915
|
say("npm install\u2026");
|
|
25659
25916
|
if (await runStep("npm", ["install"], { cwd: work, shell: process.platform === "win32" }) !== 0) {
|
|
25660
25917
|
return { ok: false, error: `npm install failed in ${work}` };
|
|
@@ -25671,7 +25928,7 @@ async function loadDemo(entry, opts) {
|
|
|
25671
25928
|
return { ok: false, error: `setup failed (${entry.setup})` };
|
|
25672
25929
|
}
|
|
25673
25930
|
}
|
|
25674
|
-
return { ok: true, serveDirs: entry.serve.dirs?.length ? entry.serve.dirs.map((d) =>
|
|
25931
|
+
return { ok: true, serveDirs: entry.serve.dirs?.length ? entry.serve.dirs.map((d) => join45(work, d)) : [work] };
|
|
25675
25932
|
}
|
|
25676
25933
|
function runStep(cmd, args, opts = {}) {
|
|
25677
25934
|
return new Promise((res) => {
|
|
@@ -25682,8 +25939,8 @@ function runStep(cmd, args, opts = {}) {
|
|
|
25682
25939
|
}
|
|
25683
25940
|
|
|
25684
25941
|
// src/server.ts
|
|
25685
|
-
var pkgRoot =
|
|
25686
|
-
var webRoot =
|
|
25942
|
+
var pkgRoot = join46(dirname18(fileURLToPath10(import.meta.url)), "..");
|
|
25943
|
+
var webRoot = join46(pkgRoot, "web");
|
|
25687
25944
|
var execFileP = async (cmd, args) => (await promisify3(execFile5)(cmd, args, { encoding: "utf8", timeout: 1e4 })).stdout;
|
|
25688
25945
|
function optsFromQuery(url, tierEnvVar, projectDir) {
|
|
25689
25946
|
const q = url.searchParams;
|
|
@@ -25734,7 +25991,7 @@ function tierFailure(tier, message2) {
|
|
|
25734
25991
|
function beholdVersion() {
|
|
25735
25992
|
try {
|
|
25736
25993
|
const here = dirname18(fileURLToPath10(import.meta.url));
|
|
25737
|
-
return JSON.parse(
|
|
25994
|
+
return JSON.parse(readFileSync34(join46(here, "..", "package.json"), "utf8")).version ?? "unknown";
|
|
25738
25995
|
} catch {
|
|
25739
25996
|
return "unknown";
|
|
25740
25997
|
}
|
|
@@ -25781,7 +26038,7 @@ async function captureFrame(projectDir, env, frames, broadcaster) {
|
|
|
25781
26038
|
}
|
|
25782
26039
|
}
|
|
25783
26040
|
function carveRoutes(app, reportPath, demo) {
|
|
25784
|
-
const load2 = () => readCarveReport(reportPath, (p) =>
|
|
26041
|
+
const load2 = () => readCarveReport(reportPath, (p) => readFileSync34(p, "utf8"));
|
|
25785
26042
|
const carveDirs = [dirname18(reportPath), ...demo ? [demo.out] : []];
|
|
25786
26043
|
const carveStates = () => readCarveStates(carveDirs, nodeCarveIo, 0);
|
|
25787
26044
|
const emittedNodes = async (states) => {
|
|
@@ -26017,13 +26274,18 @@ function cardPrefixesOf(ir) {
|
|
|
26017
26274
|
const byStack = ir.groups.byStack;
|
|
26018
26275
|
return byStack && Object.keys(byStack).length ? byStack : void 0;
|
|
26019
26276
|
}
|
|
26020
|
-
function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffer(),
|
|
26021
|
-
|
|
26022
|
-
|
|
26023
|
-
|
|
26024
|
-
|
|
26277
|
+
function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffer(), given) {
|
|
26278
|
+
const runner = cfg.public ? new ReadOnlyRunner() : given ?? new OpRunner({
|
|
26279
|
+
projectDir: cfg.projectDir,
|
|
26280
|
+
broadcaster,
|
|
26281
|
+
onDone: (opEnv) => captureFrame(cfg.projectDir, opEnv ?? cfg.env, frames, broadcaster)
|
|
26282
|
+
});
|
|
26025
26283
|
const app = new Hono();
|
|
26026
|
-
const guard = {
|
|
26284
|
+
const guard = {
|
|
26285
|
+
allowedHosts: allowedHostsFrom(cfg.allowedHosts ?? []),
|
|
26286
|
+
...cfg.host ? { boundHost: cfg.host } : {},
|
|
26287
|
+
...cfg.public ? { public: true } : {}
|
|
26288
|
+
};
|
|
26027
26289
|
app.use("*", async (c, next) => {
|
|
26028
26290
|
const verdict3 = guardRequest(
|
|
26029
26291
|
{
|
|
@@ -26034,9 +26296,13 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
|
|
|
26034
26296
|
},
|
|
26035
26297
|
guard
|
|
26036
26298
|
);
|
|
26037
|
-
if (!verdict3.ok)
|
|
26299
|
+
if (!verdict3.ok) {
|
|
26300
|
+
if (verdict3.status === 405) c.header("allow", "GET, HEAD, OPTIONS");
|
|
26301
|
+
return c.json({ error: verdict3.error, code: verdict3.status === 405 ? "public" : "request-refused" }, verdict3.status);
|
|
26302
|
+
}
|
|
26038
26303
|
await next();
|
|
26039
26304
|
});
|
|
26305
|
+
if (cfg.public) publicRoutes(app, webRoot);
|
|
26040
26306
|
setChoudoufuRunner(cfg.choudoufu?.run);
|
|
26041
26307
|
setEstateLexiconRead(cfg.choudoufu ? cfg.choudoufu.lexicon ?? null : void 0);
|
|
26042
26308
|
setServedWorkspace(cfg.workspace);
|
|
@@ -26094,6 +26360,7 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
|
|
|
26094
26360
|
};
|
|
26095
26361
|
const estateSourceRoots = async (opts) => (await Promise.all((cfg.projectDirs ?? []).map(async (dir) => [await graphPath(dir, opts), dir]))).flat();
|
|
26096
26362
|
app.get("/healthz", (c) => c.json({ ok: true, projectDir: cfg.projectDir, env: cfg.env ?? null, frames: frames.size }));
|
|
26363
|
+
kioskRoute(app, webRoot, !!cfg.terragucci);
|
|
26097
26364
|
app.get("/api/frames", (c) => c.json({ frames: frames.summaries() }));
|
|
26098
26365
|
app.get("/lanes", (c) => {
|
|
26099
26366
|
const all = frames.all();
|
|
@@ -26177,8 +26444,8 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
|
|
|
26177
26444
|
return c.json({ started: true, name, ran: label });
|
|
26178
26445
|
});
|
|
26179
26446
|
app.post("/api/local/reset", (c) => {
|
|
26180
|
-
const down =
|
|
26181
|
-
const up =
|
|
26447
|
+
const down = join46(cfg.projectDir, "scripts/local/local-down.sh");
|
|
26448
|
+
const up = join46(cfg.projectDir, "scripts/local/local-up.sh");
|
|
26182
26449
|
if (!existsSync27(down) || !existsSync27(up)) {
|
|
26183
26450
|
return c.json({ error: "no local-down.sh / local-up.sh in scripts/local \u2014 reset is only for local emulator projects" }, 400);
|
|
26184
26451
|
}
|
|
@@ -26200,7 +26467,7 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
|
|
|
26200
26467
|
const dirs = [];
|
|
26201
26468
|
const declared = [];
|
|
26202
26469
|
for (const f of discoverOpIrs(estateDirs)) {
|
|
26203
|
-
const parsed = readOpIr(f.path, (p) =>
|
|
26470
|
+
const parsed = readOpIr(f.path, (p) => readFileSync34(p, "utf8"));
|
|
26204
26471
|
if (!parsed.ok) continue;
|
|
26205
26472
|
const mine = declaredConvergeOps([parsed.ir]);
|
|
26206
26473
|
if (!mine.length) continue;
|
|
@@ -26380,7 +26647,7 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
|
|
|
26380
26647
|
});
|
|
26381
26648
|
app.get("/api/project", async (c) => {
|
|
26382
26649
|
const { environments: declaredEnvs, lexicons, stacks, k8sProfiles } = await detectProject(cfg.projectDir);
|
|
26383
|
-
const environments = declaredEnvs.length === 0 && (cfg.projectDirs ?? [cfg.projectDir]).some((d) => memberKindOf(d) === "choudoufu") ? ["live"] : declaredEnvs;
|
|
26650
|
+
const environments = cfg.public ? [] : declaredEnvs.length === 0 && (cfg.projectDirs ?? [cfg.projectDir]).some((d) => memberKindOf(d) === "choudoufu") ? ["live"] : declaredEnvs;
|
|
26384
26651
|
const k8sTarget = lexicons.includes("k8s") ? resolveK8sTarget(k8sProfiles, cfg.env, await loadKubeconfig()) : void 0;
|
|
26385
26652
|
const axes = deployAxes(tierEnvVar, lexicons, k8sTarget);
|
|
26386
26653
|
const emittedOps = discoverOpIrs(estateDirs).length;
|
|
@@ -26414,6 +26681,8 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
|
|
|
26414
26681
|
currentEnv: cfg.env ?? null,
|
|
26415
26682
|
// v0.1.0 preview: the SPA hides git/PR ops + arbitrary-project affordances.
|
|
26416
26683
|
...cfg.previewMode ? { previewMode: true } : {},
|
|
26684
|
+
// #569: a public view; the SPA hides every control that writes.
|
|
26685
|
+
...cfg.public ? { publicMode: true } : {},
|
|
26417
26686
|
// #489: a terragucci repo is applied by its pipeline; the SPA offers no
|
|
26418
26687
|
// write at all, and the write routes refuse with the same words.
|
|
26419
26688
|
...tgConfig() ? { terragucci: { config: tgConfig(), ...terragucciRefusal(tgConfig(), "deploying from behold") } } : {},
|
|
@@ -26478,7 +26747,8 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
|
|
|
26478
26747
|
// true is a flag the SPA can read as "absent means no".
|
|
26479
26748
|
memberKinds,
|
|
26480
26749
|
...runtimeCapable ? { runtimeCapable: true } : {},
|
|
26481
|
-
|
|
26750
|
+
// #569: a public view offers no target: `?target=` is refused there.
|
|
26751
|
+
targets: cfg.public ? [] : deployTargets(lexicons, k8sTarget),
|
|
26482
26752
|
// Where the k8s binding came from, so the SPA never implies behold chose
|
|
26483
26753
|
// it (#106). Absent for a project with no k8s lexicon or no resolvable
|
|
26484
26754
|
// context.
|
|
@@ -26491,7 +26761,7 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
|
|
|
26491
26761
|
const body = await c.req.json().catch(() => ({}));
|
|
26492
26762
|
const dir = typeof body.dir === "string" && body.dir.trim() ? resolve30(body.dir.trim()) : "";
|
|
26493
26763
|
if (!dir || !existsSync27(dir)) return c.json({ error: `no such directory: ${dir || "(no dir given)"}` }, 400);
|
|
26494
|
-
if (!existsSync27(
|
|
26764
|
+
if (!existsSync27(join46(dir, "chant.config.ts"))) {
|
|
26495
26765
|
return c.json({ error: `${dir} doesn't look like a chant project \u2014 no chant.config.ts` }, 400);
|
|
26496
26766
|
}
|
|
26497
26767
|
switchServedProject([dir]);
|
|
@@ -26604,6 +26874,7 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
|
|
|
26604
26874
|
const layoutDir = () => cfg.workspace?.root ?? cfg.projectDir;
|
|
26605
26875
|
const layoutWriteBlock = () => {
|
|
26606
26876
|
if (cfg.previewMode) return "the layout sidecar is read-only in preview mode";
|
|
26877
|
+
if (cfg.public) return "a public view doesn't write to the project";
|
|
26607
26878
|
if (cfg.carveReport) return "a carve report isn't a project \u2014 there's nowhere to keep a hand layout";
|
|
26608
26879
|
if (cfg.layoutWrites === false) return "a static export captures a snapshot \u2014 it doesn't write to the project";
|
|
26609
26880
|
return unwritableReason(layoutDir());
|
|
@@ -26794,7 +27065,7 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
|
|
|
26794
27065
|
}
|
|
26795
27066
|
const parsedOps = [];
|
|
26796
27067
|
for (const f of files) {
|
|
26797
|
-
const parsed = readOpIr(f.path, (p) =>
|
|
27068
|
+
const parsed = readOpIr(f.path, (p) => readFileSync34(p, "utf8"));
|
|
26798
27069
|
if (!parsed.ok) return c.json(parsed.refusal, 422);
|
|
26799
27070
|
parsedOps.push(parsed.ir);
|
|
26800
27071
|
}
|
|
@@ -26807,8 +27078,8 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
|
|
|
26807
27078
|
}
|
|
26808
27079
|
const runState = runner.runState;
|
|
26809
27080
|
const ranOp = runState.op ? parsedOps.find((o) => o.name === runState.op) : void 0;
|
|
26810
|
-
const
|
|
26811
|
-
const opsIr =
|
|
27081
|
+
const join51 = ranOp ? joinRun(ranOp, runState.records, runState.gate) : null;
|
|
27082
|
+
const opsIr = join51 ? paintPlayhead(declaredIr, join51, runState.gate) : declaredIr;
|
|
26812
27083
|
const { svg: svg2 } = renderGraph(opsIr, { boxes: "byStack" });
|
|
26813
27084
|
const operator = runner.noteDeclaredConvergeOps(declaredConvergeOps(parsedOps));
|
|
26814
27085
|
return c.json({
|
|
@@ -26821,7 +27092,7 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
|
|
|
26821
27092
|
target: null,
|
|
26822
27093
|
mode: "ops",
|
|
26823
27094
|
ops: parsedOps.length,
|
|
26824
|
-
note: `${opsNote(parsedOps, opsIr)} ${playheadNote(runState,
|
|
27095
|
+
note: `${opsNote(parsedOps, opsIr)} ${playheadNote(runState, join51)} ${operatorNote(operator)}`,
|
|
26825
27096
|
// The operator strip's own model: the ConvergeOps this project
|
|
26826
27097
|
// declares, plus whatever the last status read said. `declared`
|
|
26827
27098
|
// without a `strip` is the honest starting state — a loop exists and
|
|
@@ -26932,7 +27203,7 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
|
|
|
26932
27203
|
srcCompositeEdgesAttached = 0;
|
|
26933
27204
|
}
|
|
26934
27205
|
}
|
|
26935
|
-
if (!multi && !components && !logical && !opts.lens && !ownKindVia && ir.nodes.length === 0 && !existsSync27(
|
|
27206
|
+
if (!multi && !components && !logical && !opts.lens && !ownKindVia && ir.nodes.length === 0 && !existsSync27(join46(cfg.projectDir, "chant.config.ts"))) {
|
|
26936
27207
|
return c.json(noProjectError(cfg.projectDir), 404);
|
|
26937
27208
|
}
|
|
26938
27209
|
const radial = new URL(c.req.url).searchParams.get("radial") === "1";
|
|
@@ -27569,7 +27840,7 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
|
|
|
27569
27840
|
if (!c.req.path.startsWith("/api/") && c.res.ok && !c.res.headers.has("cache-control")) c.res.headers.set("cache-control", "no-cache");
|
|
27570
27841
|
});
|
|
27571
27842
|
app.use("/*", serveStatic({ root: rel }));
|
|
27572
|
-
app.get("/", serveStatic({ path:
|
|
27843
|
+
app.get("/", serveStatic({ path: join46(rel, "index.html") }));
|
|
27573
27844
|
return app;
|
|
27574
27845
|
}
|
|
27575
27846
|
async function bootLocalEmulators(dir, who) {
|
|
@@ -27598,11 +27869,11 @@ async function bootLocalEmulators(dir, who) {
|
|
|
27598
27869
|
async function startServer(cfg) {
|
|
27599
27870
|
if (cfg.local) cfg.emulators = await bootLocalEmulators(cfg.projectDir, "behold serve --local");
|
|
27600
27871
|
for (const dir of cfg.projectDirs ?? [cfg.projectDir]) {
|
|
27601
|
-
if (existsSync27(
|
|
27872
|
+
if (existsSync27(join46(dir, "chant.config.ts"))) addRecent(dir);
|
|
27602
27873
|
}
|
|
27603
27874
|
const broadcaster = new Broadcaster();
|
|
27604
27875
|
const frames = new FrameBuffer();
|
|
27605
|
-
const runner = new OpRunner({
|
|
27876
|
+
const runner = cfg.public ? new ReadOnlyRunner() : new OpRunner({
|
|
27606
27877
|
projectDir: cfg.projectDir,
|
|
27607
27878
|
broadcaster,
|
|
27608
27879
|
onDone: (opEnv) => captureFrame(cfg.projectDir, opEnv ?? cfg.env, frames, broadcaster)
|
|
@@ -27610,7 +27881,7 @@ async function startServer(cfg) {
|
|
|
27610
27881
|
if (!cfg.host && process.env.BEHOLD_HOST) cfg.host = process.env.BEHOLD_HOST;
|
|
27611
27882
|
const app = createApp(cfg, broadcaster, frames, runner);
|
|
27612
27883
|
const autoSync = cfg.autoSync ?? "off";
|
|
27613
|
-
if (!cfg.carveReport && !servedProductRepo(cfg.projectDirs ?? [cfg.projectDir])) {
|
|
27884
|
+
if (!cfg.public && !cfg.carveReport && !servedProductRepo(cfg.projectDirs ?? [cfg.projectDir])) {
|
|
27614
27885
|
void readoptDispatchedRun(cfg, runner).then((result) => {
|
|
27615
27886
|
if (result.outcome === "readopted") {
|
|
27616
27887
|
process.stdout.write(` re-adopted dispatched run ${result.run.runId} (${result.run.workflow}) \u2014 following via gh
|
|
@@ -27634,7 +27905,7 @@ async function startServer(cfg) {
|
|
|
27634
27905
|
const memberTag = (dir) => (cfg.projectDirs?.length ?? 0) > 1 ? ` [${basename5(dir)}]` : "";
|
|
27635
27906
|
const onPollDrift = (dir, movedLexicons) => {
|
|
27636
27907
|
broadcaster.emit("changed", dir);
|
|
27637
|
-
if (autoSync === "off") return;
|
|
27908
|
+
if (autoSync === "off" || cfg.public) return;
|
|
27638
27909
|
const guarded = servedProductRepo(cfg.projectDirs ?? [cfg.projectDir]);
|
|
27639
27910
|
if (guarded) {
|
|
27640
27911
|
broadcaster.emit("op", `\u27F3 auto-sync (${autoSync})${memberTag(dir)} declined: ${productRefusal(guarded, "auto-sync").error}`);
|
|
@@ -27743,7 +28014,8 @@ async function startServer(cfg) {
|
|
|
27743
28014
|
`behold \u2192 http://localhost:${info.port}
|
|
27744
28015
|
project: ${cfg.projectDir}${cfg.env ? ` env: ${cfg.env}` : ""}${auto}${localTag}
|
|
27745
28016
|
read-only, watching for edits${poll}. lanes: /lanes. Ctrl-C to stop.
|
|
27746
|
-
`
|
|
28017
|
+
` + (cfg.public ? ` public: GET, HEAD and OPTIONS only; no op runner; answers ${["localhost", ...allowedHostsFrom(cfg.allowedHosts ?? [])].join(", ")}.
|
|
28018
|
+
` : "")
|
|
27747
28019
|
);
|
|
27748
28020
|
void detectProject(cfg.projectDir).then(({ environments, lexicons }) => {
|
|
27749
28021
|
const envs = environments.length ? environments.join(", ") : "(none declared \u2014 env picker shows only source)";
|
|
@@ -27771,8 +28043,8 @@ init_chant();
|
|
|
27771
28043
|
|
|
27772
28044
|
// src/workspace-convert.ts
|
|
27773
28045
|
init_chant();
|
|
27774
|
-
import { existsSync as existsSync28, mkdirSync as mkdirSync9, readFileSync as
|
|
27775
|
-
import { basename as basename6, dirname as dirname19, join as
|
|
28046
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync9, readFileSync as readFileSync35, renameSync as renameSync2, writeFileSync as writeFileSync7 } from "node:fs";
|
|
28047
|
+
import { basename as basename6, dirname as dirname19, join as join47, relative as relative11, resolve as resolve31, sep as sep10 } from "node:path";
|
|
27776
28048
|
import { shortStackNames as shortStackNames2 } from "@intentius/pinhole";
|
|
27777
28049
|
init_project();
|
|
27778
28050
|
init_terraform_member();
|
|
@@ -27790,10 +28062,10 @@ function unique(name, taken) {
|
|
|
27790
28062
|
var posix4 = (p) => p.split(sep10).join("/");
|
|
27791
28063
|
function installedVersion(pkg, dir) {
|
|
27792
28064
|
for (let at2 = resolve31(dir); ; at2 = dirname19(at2)) {
|
|
27793
|
-
const manifest =
|
|
28065
|
+
const manifest = join47(at2, "node_modules", ...pkg.split("/"), "package.json");
|
|
27794
28066
|
if (existsSync28(manifest)) {
|
|
27795
28067
|
try {
|
|
27796
|
-
const v = JSON.parse(
|
|
28068
|
+
const v = JSON.parse(readFileSync35(manifest, "utf8")).version;
|
|
27797
28069
|
return typeof v === "string" ? v : void 0;
|
|
27798
28070
|
} catch {
|
|
27799
28071
|
return void 0;
|
|
@@ -27804,7 +28076,7 @@ function installedVersion(pkg, dir) {
|
|
|
27804
28076
|
}
|
|
27805
28077
|
function planConversion(rootArg) {
|
|
27806
28078
|
const root = resolve31(rootArg);
|
|
27807
|
-
const existing = DECLARATION_FILES.find((f) => existsSync28(
|
|
28079
|
+
const existing = DECLARATION_FILES.find((f) => existsSync28(join47(root, f)));
|
|
27808
28080
|
if (existing) return { ok: false, error: `${root} already has ${existing}; there is nothing to convert.` };
|
|
27809
28081
|
const shape = detectProjectShape(root);
|
|
27810
28082
|
if (shape.kind !== "estate" || !shape.members?.length || shape.membersFrom === "itself") {
|
|
@@ -27825,10 +28097,10 @@ function planConversion(rootArg) {
|
|
|
27825
28097
|
const scan = discoverTerraformRoots(abs[i]);
|
|
27826
28098
|
for (const r of scan.roots) {
|
|
27827
28099
|
const name2 = unique(declarationName(r.name), taken);
|
|
27828
|
-
members.push({ name: name2, dir: r.dir === "." ? dir : posix4(
|
|
28100
|
+
members.push({ name: name2, dir: r.dir === "." ? dir : posix4(join47(dir, r.dir)), kind: "terraform" });
|
|
27829
28101
|
moves.push({ from: `${short}/${r.name}/`, to: `${name2}/${name2}/` });
|
|
27830
28102
|
}
|
|
27831
|
-
for (const s of scan.skipped) notes.push(`${posix4(
|
|
28103
|
+
for (const s of scan.skipped) notes.push(`${posix4(join47(dir, s.dir))} is not declared: ${s.why}.`);
|
|
27832
28104
|
if (scan.roots.length > 1) notes.push(`${dir} held ${scan.roots.length} Terraform roots; each is its own member now, since a chant terraform member is one root.`);
|
|
27833
28105
|
return;
|
|
27834
28106
|
}
|
|
@@ -27871,7 +28143,7 @@ function moveLayoutIds(file, moves) {
|
|
|
27871
28143
|
return { version: 1, lenses };
|
|
27872
28144
|
}
|
|
27873
28145
|
function applyConversion(plan) {
|
|
27874
|
-
const declarationFile =
|
|
28146
|
+
const declarationFile = join47(plan.root, DECLARATION_FILES[0]);
|
|
27875
28147
|
if (existsSync28(declarationFile)) throw new Error(`${declarationFile} appeared while converting; nothing was written`);
|
|
27876
28148
|
writeFileSync7(declarationFile, `${JSON.stringify(plan.declaration, null, 2)}
|
|
27877
28149
|
`);
|
|
@@ -27882,7 +28154,7 @@ function applyConversion(plan) {
|
|
|
27882
28154
|
else if (existsSync28(target)) layout = { skipped: `${target} already exists; the layout in ${plan.layoutFrom} was left alone` };
|
|
27883
28155
|
else {
|
|
27884
28156
|
const moved = moveLayoutIds(readLayoutFile(dirname19(dirname19(plan.layoutFrom))), plan.moves);
|
|
27885
|
-
mkdirSync9(
|
|
28157
|
+
mkdirSync9(join47(plan.root, LAYOUT_DIR), { recursive: true });
|
|
27886
28158
|
const tmp = `${target}.tmp`;
|
|
27887
28159
|
writeFileSync7(tmp, `${JSON.stringify(moved, null, 2)}
|
|
27888
28160
|
`);
|
|
@@ -27893,9 +28165,9 @@ function applyConversion(plan) {
|
|
|
27893
28165
|
}
|
|
27894
28166
|
|
|
27895
28167
|
// src/export.ts
|
|
27896
|
-
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync8, copyFileSync, cpSync as cpSync2, readFileSync as
|
|
28168
|
+
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync8, copyFileSync, cpSync as cpSync2, readFileSync as readFileSync36, readdirSync as readdirSync14, existsSync as existsSync29, realpathSync as realpathSync2 } from "node:fs";
|
|
27897
28169
|
import { homedir as homedir3 } from "node:os";
|
|
27898
|
-
import { join as
|
|
28170
|
+
import { join as join48, dirname as dirname20, basename as basename7, relative as relative12, resolve as resolve32, sep as sep11 } from "node:path";
|
|
27899
28171
|
import { createHash as createHash11 } from "node:crypto";
|
|
27900
28172
|
import { fileURLToPath as fileURLToPath11 } from "node:url";
|
|
27901
28173
|
var LENS_PARAMS = ["collapse", "components", "detail", "env", "logical", "ops", "radial", "stacks", "tier"];
|
|
@@ -27952,24 +28224,24 @@ function captureKeys(axes) {
|
|
|
27952
28224
|
return [...keys];
|
|
27953
28225
|
}
|
|
27954
28226
|
function webDir() {
|
|
27955
|
-
return
|
|
28227
|
+
return join48(dirname20(fileURLToPath11(import.meta.url)), "..", "web");
|
|
27956
28228
|
}
|
|
27957
28229
|
function pkgRoot2() {
|
|
27958
|
-
return
|
|
28230
|
+
return join48(dirname20(fileURLToPath11(import.meta.url)), "..");
|
|
27959
28231
|
}
|
|
27960
28232
|
var NOTICE_FILES = ["LICENSE", "THIRD_PARTY.md"];
|
|
27961
28233
|
function copyNotices(outDir, root = pkgRoot2()) {
|
|
27962
28234
|
const copied = [];
|
|
27963
28235
|
for (const f of NOTICE_FILES) {
|
|
27964
|
-
if (!existsSync29(
|
|
27965
|
-
copyFileSync(
|
|
28236
|
+
if (!existsSync29(join48(root, f))) continue;
|
|
28237
|
+
copyFileSync(join48(root, f), join48(outDir, f));
|
|
27966
28238
|
copied.push(f);
|
|
27967
28239
|
}
|
|
27968
|
-
const lic =
|
|
28240
|
+
const lic = join48(root, "licenses");
|
|
27969
28241
|
if (existsSync29(lic)) {
|
|
27970
|
-
mkdirSync10(
|
|
28242
|
+
mkdirSync10(join48(outDir, "licenses"), { recursive: true });
|
|
27971
28243
|
for (const f of readdirSync14(lic)) {
|
|
27972
|
-
copyFileSync(
|
|
28244
|
+
copyFileSync(join48(lic, f), join48(outDir, "licenses", f));
|
|
27973
28245
|
copied.push(`licenses/${f}`);
|
|
27974
28246
|
}
|
|
27975
28247
|
}
|
|
@@ -27994,7 +28266,7 @@ async function runExport(cfg, outDir, opts = {}) {
|
|
|
27994
28266
|
...proj.sqlYodeler ? { sqlYodeler: true } : {}
|
|
27995
28267
|
};
|
|
27996
28268
|
const scrub = exportScrubber(cfg);
|
|
27997
|
-
const snapDir =
|
|
28269
|
+
const snapDir = join48(outDir, "snapshots");
|
|
27998
28270
|
mkdirSync10(snapDir, { recursive: true });
|
|
27999
28271
|
const keyToFile = {};
|
|
28000
28272
|
let ok = 0;
|
|
@@ -28003,7 +28275,7 @@ async function runExport(cfg, outDir, opts = {}) {
|
|
|
28003
28275
|
const res = await app.request(`${key}${key.includes("?") ? "&" : "?"}layout=1`);
|
|
28004
28276
|
const body = scrub(shapeSnapshot(key, await res.text(), opts));
|
|
28005
28277
|
const file = slug3(key, body);
|
|
28006
|
-
writeFileSync8(
|
|
28278
|
+
writeFileSync8(join48(snapDir, file), body);
|
|
28007
28279
|
keyToFile[key] = `snapshots/${file}`;
|
|
28008
28280
|
if (res.ok) ok++;
|
|
28009
28281
|
else failed++;
|
|
@@ -28025,29 +28297,29 @@ async function runExport(cfg, outDir, opts = {}) {
|
|
|
28025
28297
|
axes,
|
|
28026
28298
|
keyToFile
|
|
28027
28299
|
};
|
|
28028
|
-
writeFileSync8(
|
|
28029
|
-
const html =
|
|
28300
|
+
writeFileSync8(join48(outDir, "manifest.json"), JSON.stringify(manifest, null, 2));
|
|
28301
|
+
const html = readFileSync36(join48(webDir(), "index.html"), "utf8").replace(
|
|
28030
28302
|
/<\/head>/i,
|
|
28031
28303
|
` <script>window.__BEHOLD_STATIC__ = true;</script>
|
|
28032
28304
|
</head>`
|
|
28033
28305
|
);
|
|
28034
|
-
writeFileSync8(
|
|
28306
|
+
writeFileSync8(join48(outDir, "index.html"), html);
|
|
28035
28307
|
for (const f of readdirSync14(webDir())) {
|
|
28036
28308
|
if (f === "index.html" || f.endsWith(".test.js")) continue;
|
|
28037
|
-
cpSync2(
|
|
28309
|
+
cpSync2(join48(webDir(), f), join48(outDir, f), { recursive: true });
|
|
28038
28310
|
}
|
|
28039
|
-
writeFileSync8(
|
|
28311
|
+
writeFileSync8(join48(outDir, "README.md"), BUNDLE_README);
|
|
28040
28312
|
copyNotices(outDir);
|
|
28041
28313
|
const name = workerName(cfg.projectDir, opts.name);
|
|
28042
28314
|
writeFileSync8(
|
|
28043
|
-
|
|
28315
|
+
join48(outDir, "wrangler.jsonc"),
|
|
28044
28316
|
JSON.stringify(
|
|
28045
28317
|
{ $schema: "node_modules/wrangler/config-schema.json", name, compatibility_date: "2025-06-01", assets: { directory: "." } },
|
|
28046
28318
|
null,
|
|
28047
28319
|
2
|
|
28048
28320
|
) + "\n"
|
|
28049
28321
|
);
|
|
28050
|
-
writeFileSync8(
|
|
28322
|
+
writeFileSync8(join48(outDir, "manifest.json"), JSON.stringify({ ...manifest, files: bundleFiles(outDir).filter((f) => f !== "manifest.json") }, null, 2));
|
|
28051
28323
|
process.stdout.write(
|
|
28052
28324
|
`behold export \u2192 ${outDir}
|
|
28053
28325
|
${ok} snapshots${failed ? ` (${failed} endpoint error(s) captured as-is)` : ""}
|
|
@@ -28056,11 +28328,11 @@ async function runExport(cfg, outDir, opts = {}) {
|
|
|
28056
28328
|
`
|
|
28057
28329
|
);
|
|
28058
28330
|
}
|
|
28059
|
-
var
|
|
28331
|
+
var isRecord21 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
28060
28332
|
function dropSource(v) {
|
|
28061
28333
|
if (Array.isArray(v)) return v.forEach(dropSource);
|
|
28062
|
-
if (!
|
|
28063
|
-
if (
|
|
28334
|
+
if (!isRecord21(v)) return;
|
|
28335
|
+
if (isRecord21(v.attrs)) delete v.attrs.source;
|
|
28064
28336
|
for (const x of Object.values(v)) dropSource(x);
|
|
28065
28337
|
}
|
|
28066
28338
|
function shapeSnapshot(key, body, opts = {}) {
|
|
@@ -28070,7 +28342,7 @@ function shapeSnapshot(key, body, opts = {}) {
|
|
|
28070
28342
|
} catch {
|
|
28071
28343
|
return body;
|
|
28072
28344
|
}
|
|
28073
|
-
if (!
|
|
28345
|
+
if (!isRecord21(doc)) return body;
|
|
28074
28346
|
if (key === "/api/project") {
|
|
28075
28347
|
delete doc.recents;
|
|
28076
28348
|
delete doc.approver;
|
|
@@ -28140,11 +28412,11 @@ function manifestFiles(text) {
|
|
|
28140
28412
|
} catch {
|
|
28141
28413
|
return [];
|
|
28142
28414
|
}
|
|
28143
|
-
const map =
|
|
28415
|
+
const map = isRecord21(doc) && isRecord21(doc.keyToFile) ? doc.keyToFile : {};
|
|
28144
28416
|
return Object.values(map).filter((f) => typeof f === "string" && /^snapshots\/[A-Za-z0-9=_.-]+$/.test(f) && !f.includes(".."));
|
|
28145
28417
|
}
|
|
28146
28418
|
function bundleFiles(outDir) {
|
|
28147
|
-
return readdirSync14(outDir, { recursive: true, withFileTypes: true }).filter((d) => d.isFile()).map((d) => relative12(outDir,
|
|
28419
|
+
return readdirSync14(outDir, { recursive: true, withFileTypes: true }).filter((d) => d.isFile()).map((d) => relative12(outDir, join48(d.parentPath, d.name)).split(sep11).join("/")).sort();
|
|
28148
28420
|
}
|
|
28149
28421
|
async function publishBundle(outDir, target, client) {
|
|
28150
28422
|
const files = bundleFiles(outDir);
|
|
@@ -28160,7 +28432,7 @@ async function publishBundle(outDir, target, client) {
|
|
|
28160
28432
|
}
|
|
28161
28433
|
const last = ["manifest.json", "index.html"];
|
|
28162
28434
|
const order = [...files.filter((f) => !last.includes(f)), ...last.filter((f) => files.includes(f))];
|
|
28163
|
-
for (const f of order) await client.put(at2(f),
|
|
28435
|
+
for (const f of order) await client.put(at2(f), readFileSync36(join48(outDir, f)), contentType(f), CONTENT_ADDRESSED.test(f) ? IMMUTABLE : NO_CACHE);
|
|
28164
28436
|
const wrote = new Set(files);
|
|
28165
28437
|
const removed = [];
|
|
28166
28438
|
for (const f of previous) {
|
|
@@ -28212,8 +28484,8 @@ It's just files \u2014 GitHub Pages, S3, nginx, or Cloudflare Pages
|
|
|
28212
28484
|
|
|
28213
28485
|
// src/publish-snapshots.ts
|
|
28214
28486
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
28215
|
-
import { readFileSync as
|
|
28216
|
-
import { join as
|
|
28487
|
+
import { readFileSync as readFileSync37 } from "node:fs";
|
|
28488
|
+
import { join as join49 } from "node:path";
|
|
28217
28489
|
var DEFAULT_KEEP = 30;
|
|
28218
28490
|
var SNAPSHOT_REPORTS_BASE = "../../../";
|
|
28219
28491
|
var COMMIT = /^[0-9a-f]{7,64}$/;
|
|
@@ -28238,12 +28510,12 @@ function resolveCommit2(dir, given) {
|
|
|
28238
28510
|
return { sha };
|
|
28239
28511
|
}
|
|
28240
28512
|
}
|
|
28241
|
-
var
|
|
28513
|
+
var isRecord22 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
28242
28514
|
function parseHistory(text) {
|
|
28243
28515
|
const doc = JSON.parse(text);
|
|
28244
28516
|
if (!Array.isArray(doc)) throw new Error("history.json is not an array");
|
|
28245
|
-
const
|
|
28246
|
-
return doc.filter((e) =>
|
|
28517
|
+
const str16 = (v) => typeof v === "string" ? v : "";
|
|
28518
|
+
return doc.filter((e) => isRecord22(e) && typeof e.commit === "string" && COMMIT.test(e.commit)).map((e) => ({ commit: e.commit, at: str16(e.at), project: str16(e.project), generated: str16(e.generated) }));
|
|
28247
28519
|
}
|
|
28248
28520
|
function nextHistory(old, entry, keep) {
|
|
28249
28521
|
const all = [entry, ...old.filter((e) => e.commit !== entry.commit)];
|
|
@@ -28255,9 +28527,9 @@ var BUNDLE_PATH = /^[A-Za-z0-9=_.@+-]+(\/[A-Za-z0-9=_.@+-]+)*$/;
|
|
|
28255
28527
|
var insideBundle = (f) => typeof f === "string" && BUNDLE_PATH.test(f) && !f.split("/").some((p) => p === "." || p === "..");
|
|
28256
28528
|
function commitFiles(manifestText) {
|
|
28257
28529
|
const doc = JSON.parse(manifestText);
|
|
28258
|
-
if (!
|
|
28530
|
+
if (!isRecord22(doc)) return ["manifest.json"];
|
|
28259
28531
|
const listed = Array.isArray(doc.files) ? doc.files.filter(insideBundle) : [];
|
|
28260
|
-
const snaps =
|
|
28532
|
+
const snaps = isRecord22(doc.keyToFile) ? Object.values(doc.keyToFile).filter(insideBundle) : [];
|
|
28261
28533
|
const rest = [.../* @__PURE__ */ new Set([...listed, ...snaps])].filter((f) => f !== "index.html" && f !== "manifest.json").sort();
|
|
28262
28534
|
return ["index.html", ...rest, "manifest.json"];
|
|
28263
28535
|
}
|
|
@@ -28301,7 +28573,7 @@ async function publishSnapshot(outDir, target, commit, client, opts = {}) {
|
|
|
28301
28573
|
const keep = opts.keep ?? DEFAULT_KEEP;
|
|
28302
28574
|
if (!Number.isInteger(keep) || keep < 1) throw new Error(`--keep takes a whole number of at least 1, not ${keep}`);
|
|
28303
28575
|
const at2 = (f) => `${target.prefix}/${f}`;
|
|
28304
|
-
const local = JSON.parse(
|
|
28576
|
+
const local = JSON.parse(readFileSync37(join49(outDir, "manifest.json"), "utf8"));
|
|
28305
28577
|
const generated = local.capturedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
28306
28578
|
const entry = { commit: sha, at: commit.at ?? generated, project: opts.project ?? local.projectDir ?? "", generated };
|
|
28307
28579
|
const bundle = await publishBundle(outDir, { bucket: target.bucket, prefix: at2(sha) }, client);
|
|
@@ -28369,6 +28641,7 @@ Usage:
|
|
|
28369
28641
|
[--yodel-report <file|dir>]
|
|
28370
28642
|
[--publish s3://<bucket>/<prefix>/views/<name> [--commit <sha>] [--keep <n>]]
|
|
28371
28643
|
behold serve <project-dir\u2026> [--port <n>] [--host <addr>] [--allow-host <name,\u2026>] [--hud <url>] [--env <name>] [--poll <secs>] [--local]
|
|
28644
|
+
[--public] [--terragucci <src> [--terragucci-project <p>] [--terragucci-poll <secs>]]
|
|
28372
28645
|
[--yodel-report <file|dir>]
|
|
28373
28646
|
behold carve <report.json> [--port <n>]
|
|
28374
28647
|
|
|
@@ -28434,6 +28707,19 @@ Options:
|
|
|
28434
28707
|
--allow-host <n,\u2026> serve only: names besides loopback behold answers to, and
|
|
28435
28708
|
whose pages may write (BEHOLD_ALLOWED_HOSTS too). For a
|
|
28436
28709
|
proxy that frames behold, or --host 0.0.0.0.
|
|
28710
|
+
--public serve only: a read-only view anyone may load, for a
|
|
28711
|
+
public host (#569). behold builds no op runner, refuses
|
|
28712
|
+
every method but GET, HEAD and OPTIONS (405), and refuses
|
|
28713
|
+
(403) every read that runs a command or uses your
|
|
28714
|
+
credentials and isn't part of the view: the op tools,
|
|
28715
|
+
the live overlay and its plan, diff, reconcile, CI,
|
|
28716
|
+
doctor, substrates, history, workspace gates and why,
|
|
28717
|
+
choudoufu moves. The page shows no control that writes
|
|
28718
|
+
or asks for those. One project directory only; not with
|
|
28719
|
+
--env, --poll, --local or --auto-sync. It still binds
|
|
28720
|
+
127.0.0.1: put a proxy in front and pass the public name
|
|
28721
|
+
with --allow-host. With --terragucci, the poll defaults
|
|
28722
|
+
to 10 s.
|
|
28437
28723
|
--hud <url> serve only: where hud reviews this workspace's records
|
|
28438
28724
|
(BEHOLD_HUD_URL too). A proposed decision in a member's
|
|
28439
28725
|
or a card's "why" links to its review there (#471).
|
|
@@ -28452,7 +28738,8 @@ Options:
|
|
|
28452
28738
|
serve with --terragucci only: while a page is open, ask
|
|
28453
28739
|
whether index.json changed (If-None-Match, or its mtime)
|
|
28454
28740
|
and fetch chant/lifecycle (the gates and locks) every
|
|
28455
|
-
<secs> (default 30), and push what
|
|
28741
|
+
<secs> (default 30, or 10 with --public), and push what
|
|
28742
|
+
moved to the page.
|
|
28456
28743
|
0 turns it off. The fetch goes into behold's own cache
|
|
28457
28744
|
(<tmpdir>/behold-lifecycle-*), never into the checkout.
|
|
28458
28745
|
--yodel-report <file|dir>
|
|
@@ -28573,6 +28860,7 @@ ${USAGE}`);
|
|
|
28573
28860
|
let terragucciProject;
|
|
28574
28861
|
let terragucciPoll;
|
|
28575
28862
|
let yodelReport;
|
|
28863
|
+
let isPublic = false;
|
|
28576
28864
|
const allowHosts = [];
|
|
28577
28865
|
for (let i = 0; i < rest.length; i++) {
|
|
28578
28866
|
const a = rest[i];
|
|
@@ -28587,6 +28875,7 @@ ${USAGE}`);
|
|
|
28587
28875
|
else if (a === "--env") env = rest[++i];
|
|
28588
28876
|
else if (a === "--poll") pollSecs = Number(rest[++i]);
|
|
28589
28877
|
else if (a === "--local") local = true;
|
|
28878
|
+
else if (a === "--public") isPublic = true;
|
|
28590
28879
|
else if (a === "--auto-sync") {
|
|
28591
28880
|
const m = rest[++i];
|
|
28592
28881
|
if (!m || !isAutoSyncMode(m)) {
|
|
@@ -28646,6 +28935,23 @@ ${USAGE}`);
|
|
|
28646
28935
|
process.stderr.write("behold serve: --terragucci-poll needs --terragucci\n");
|
|
28647
28936
|
process.exit(2);
|
|
28648
28937
|
}
|
|
28938
|
+
if (isPublic && projectDirs.length > 1) {
|
|
28939
|
+
process.stderr.write("behold serve: --public serves one project directory\n");
|
|
28940
|
+
process.exit(2);
|
|
28941
|
+
}
|
|
28942
|
+
if (isPublic && local) {
|
|
28943
|
+
process.stderr.write("behold serve: --public can't --local (it boots emulators and deploys to them)\n");
|
|
28944
|
+
process.exit(2);
|
|
28945
|
+
}
|
|
28946
|
+
if (isPublic && (env || pollSecs !== void 0)) {
|
|
28947
|
+
process.stderr.write("behold serve: --public can't --env or --poll (a live read uses your credentials)\n");
|
|
28948
|
+
process.exit(2);
|
|
28949
|
+
}
|
|
28950
|
+
if (isPublic && autoSync !== "off") {
|
|
28951
|
+
process.stderr.write("behold serve: --public can't --auto-sync (it runs Ops)\n");
|
|
28952
|
+
process.exit(2);
|
|
28953
|
+
}
|
|
28954
|
+
if (isPublic && terragucci !== void 0 && terragucciPoll === void 0) terragucciPoll = PUBLIC_TERRAGUCCI_POLL_SECS;
|
|
28649
28955
|
if (yodelReport !== void 0 && !yodelReport) {
|
|
28650
28956
|
process.stderr.write("behold serve: --yodel-report needs the file `yodel report --json` wrote, or the directory holding it\n");
|
|
28651
28957
|
process.exit(2);
|
|
@@ -28661,11 +28967,13 @@ ${USAGE}`);
|
|
|
28661
28967
|
...local ? { local: true } : {},
|
|
28662
28968
|
...host ? { host } : {},
|
|
28663
28969
|
...allowHosts.length ? { allowedHosts: allowHosts } : {},
|
|
28970
|
+
...isPublic ? { public: true, layoutWrites: false } : {},
|
|
28664
28971
|
...target.workspace && hud ? { hud } : {},
|
|
28665
28972
|
...yodelReport ? { yodelReport: resolve33(yodelReport) } : {},
|
|
28666
28973
|
...terragucci ? { terragucci: { source: terragucci, ...terragucciProject ? { project: terragucciProject } : {}, ...terragucciPoll !== void 0 ? { pollSecs: terragucciPoll } : {} } } : {}
|
|
28667
28974
|
});
|
|
28668
28975
|
}
|
|
28976
|
+
var PUBLIC_TERRAGUCCI_POLL_SECS = 10;
|
|
28669
28977
|
async function serveTarget(dirs, verb) {
|
|
28670
28978
|
const workspace = dirs.length === 1 && hasDeclaration(dirs[0]) ? await declaredWorkspace(dirs[0], verb) : void 0;
|
|
28671
28979
|
if (workspace && workspace.members.length > 0) {
|
|
@@ -28717,7 +29025,7 @@ function warnIfNotChantProject(dir) {
|
|
|
28717
29025
|
if (shape.kind === "estate") {
|
|
28718
29026
|
process.stderr.write(
|
|
28719
29027
|
`behold: warning \u2014 ${dir} is an estate root, not a chant project itself.
|
|
28720
|
-
Serve its members composed: behold serve ${shape.members.map((m) =>
|
|
29028
|
+
Serve its members composed: behold serve ${shape.members.map((m) => join50(dir, m.dir)).join(" ")}
|
|
28721
29029
|
`
|
|
28722
29030
|
);
|
|
28723
29031
|
return;
|
|
@@ -28753,7 +29061,7 @@ async function runCarve(rest) {
|
|
|
28753
29061
|
process.exit(2);
|
|
28754
29062
|
}
|
|
28755
29063
|
const reportPath = resolve33(fileArg);
|
|
28756
|
-
const parsed = readCarveReport(reportPath, (p) =>
|
|
29064
|
+
const parsed = readCarveReport(reportPath, (p) => readFileSync38(p, "utf8"));
|
|
28757
29065
|
if (!parsed.ok) {
|
|
28758
29066
|
process.stderr.write(`behold carve: ${parsed.refusal.error}
|
|
28759
29067
|
${parsed.refusal.remedy}
|
|
@@ -28819,7 +29127,7 @@ ${check.stdout || check.stderr}
|
|
|
28819
29127
|
if (check.code !== 0) process.exitCode = 1;
|
|
28820
29128
|
}
|
|
28821
29129
|
async function runDemo(rest) {
|
|
28822
|
-
const pkgRoot3 =
|
|
29130
|
+
const pkgRoot3 = join50(dirname21(fileURLToPath12(import.meta.url)), "..");
|
|
28823
29131
|
const registry2 = loadDemoRegistry(pkgRoot3);
|
|
28824
29132
|
let port = 4600;
|
|
28825
29133
|
let live2 = false;
|
|
@@ -28954,7 +29262,7 @@ async function serveCarveDemo(target, carve, port, live2 = false) {
|
|
|
28954
29262
|
};
|
|
28955
29263
|
process.stdout.write("behold demo carve --live \u2192 the tfstate is real now; the advisor reads what terraform wrote.\n");
|
|
28956
29264
|
}
|
|
28957
|
-
if (existsSync30(
|
|
29265
|
+
if (existsSync30(join50(project, "package.json")) && !existsSync30(join50(project, "node_modules"))) {
|
|
28958
29266
|
process.stdout.write(`behold demo carve \u2192 npm install in ${carve.project}/ (the chant this walkthrough shells)\u2026
|
|
28959
29267
|
`);
|
|
28960
29268
|
const code = await spawnStep("npm", ["install"], project);
|
|
@@ -28965,7 +29273,7 @@ async function serveCarveDemo(target, carve, port, live2 = false) {
|
|
|
28965
29273
|
}
|
|
28966
29274
|
}
|
|
28967
29275
|
let degraded;
|
|
28968
|
-
if (!existsSync30(
|
|
29276
|
+
if (!existsSync30(join50(target, "node_modules", ...HCL_PARSER_PKG.split("/")))) {
|
|
28969
29277
|
process.stdout.write(`behold demo carve \u2192 npm install ${HCL_PARSER_PKG} (chant's HCL parser, ~2MB, once)\u2026
|
|
28970
29278
|
`);
|
|
28971
29279
|
const code = await spawnStep("npm", ["install", "--no-save", "--no-package-lock", HCL_PARSER_PKG], target);
|