@intentius/behold 0.27.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -0
- package/dist/cli.js +702 -285
- package/package.json +4 -4
- package/web/app.js +158 -34
- package/web/edge-kind.js +31 -0
- package/web/edge-kind.test.js +31 -1
- 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
|
}
|
|
@@ -9133,6 +9133,11 @@ var init_workspace_kinds_schema = __esm({
|
|
|
9133
9133
|
},
|
|
9134
9134
|
fields: {
|
|
9135
9135
|
$ref: "#/$defs/fields"
|
|
9136
|
+
},
|
|
9137
|
+
reader: {
|
|
9138
|
+
type: "string",
|
|
9139
|
+
pattern: "^[a-z][a-z0-9-]{0,39}$",
|
|
9140
|
+
description: "A link reader chant ships, by name, that derives a member's links from the member's own files (#3932): dbt-project reads a dbt project's sources. The reader is chant's code and runs offline, parsing files as data; the package supplies only the name. A name this chant doesn't have keeps the kind without a reader, and the load reports it. A chant older than this field fails a kinds file that uses it."
|
|
9136
9141
|
}
|
|
9137
9142
|
},
|
|
9138
9143
|
patternProperties: {
|
|
@@ -9542,12 +9547,12 @@ var init_identity_gate_rule = __esm({
|
|
|
9542
9547
|
});
|
|
9543
9548
|
|
|
9544
9549
|
// node_modules/@intentius/chant/src/workspace/class-data.ts
|
|
9545
|
-
import { existsSync as existsSync17, readFileSync as
|
|
9546
|
-
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";
|
|
9547
9552
|
function diskFiles(dir) {
|
|
9548
9553
|
return (path) => {
|
|
9549
9554
|
try {
|
|
9550
|
-
return
|
|
9555
|
+
return readFileSync20(join27(dir, ...path.split("/")), "utf-8");
|
|
9551
9556
|
} catch {
|
|
9552
9557
|
return void 0;
|
|
9553
9558
|
}
|
|
@@ -9555,9 +9560,9 @@ function diskFiles(dir) {
|
|
|
9555
9560
|
}
|
|
9556
9561
|
function findInstalledPackage(name, fromDir) {
|
|
9557
9562
|
for (let dir = resolve14(fromDir); ; dir = dirname7(dir)) {
|
|
9558
|
-
const candidate =
|
|
9563
|
+
const candidate = join27(dir, "node_modules", ...name.split("/"));
|
|
9559
9564
|
try {
|
|
9560
|
-
if (statSync9(candidate).isDirectory() && existsSync17(
|
|
9565
|
+
if (statSync9(candidate).isDirectory() && existsSync17(join27(candidate, "package.json"))) return candidate;
|
|
9561
9566
|
} catch {
|
|
9562
9567
|
}
|
|
9563
9568
|
if (dirname7(dir) === dir) return void 0;
|
|
@@ -10007,10 +10012,10 @@ var init_jsonc = __esm({
|
|
|
10007
10012
|
});
|
|
10008
10013
|
|
|
10009
10014
|
// node_modules/@intentius/chant/src/package-version.ts
|
|
10010
|
-
import { readFileSync as
|
|
10015
|
+
import { readFileSync as readFileSync21 } from "node:fs";
|
|
10011
10016
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
10012
10017
|
function readChantPackageVersion() {
|
|
10013
|
-
return JSON.parse(
|
|
10018
|
+
return JSON.parse(readFileSync21(fileURLToPath4(new URL("../package.json", import.meta.url)), "utf-8")).version;
|
|
10014
10019
|
}
|
|
10015
10020
|
var init_package_version = __esm({
|
|
10016
10021
|
"node_modules/@intentius/chant/src/package-version.ts"() {
|
|
@@ -10809,8 +10814,8 @@ var init_declaration = __esm({
|
|
|
10809
10814
|
});
|
|
10810
10815
|
|
|
10811
10816
|
// src/cli.ts
|
|
10812
|
-
import { resolve as resolve33, dirname as dirname21, join as
|
|
10813
|
-
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";
|
|
10814
10819
|
import { fileURLToPath as fileURLToPath12 } from "node:url";
|
|
10815
10820
|
import { spawn as spawn9, spawnSync as spawnSync4 } from "node:child_process";
|
|
10816
10821
|
|
|
@@ -10868,8 +10873,8 @@ import { serve } from "@hono/node-server";
|
|
|
10868
10873
|
import { fileURLToPath as fileURLToPath10 } from "node:url";
|
|
10869
10874
|
import { execFile as execFile5 } from "node:child_process";
|
|
10870
10875
|
import { promisify as promisify3 } from "node:util";
|
|
10871
|
-
import { existsSync as existsSync27, readFileSync as
|
|
10872
|
-
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";
|
|
10873
10878
|
|
|
10874
10879
|
// src/recents.ts
|
|
10875
10880
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
@@ -13275,10 +13280,10 @@ function joinNamespaceBindings(members, isDir = realIsDir) {
|
|
|
13275
13280
|
if (!winner || tied) continue;
|
|
13276
13281
|
const prev = best.get(winner.dir);
|
|
13277
13282
|
if (prev === "ambiguous") continue;
|
|
13278
|
-
const
|
|
13279
|
-
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 });
|
|
13280
13285
|
else if (prev.join.namespace !== b.namespace) best.set(winner.dir, "ambiguous");
|
|
13281
|
-
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 });
|
|
13282
13287
|
}
|
|
13283
13288
|
return [...best.values()].filter((v) => v !== "ambiguous").map((v) => v.join);
|
|
13284
13289
|
}
|
|
@@ -15776,7 +15781,9 @@ function estateFace(irIn, boxesIn, opts = {}) {
|
|
|
15776
15781
|
const face = cardFaces(irIn, boxesIn, opts.prefixes ? { prefixes: opts.prefixes } : {});
|
|
15777
15782
|
const ir = face.ir;
|
|
15778
15783
|
if (Object.keys(face.overrides).length) faceOverrides.set(ir, face.overrides);
|
|
15779
|
-
const
|
|
15784
|
+
const toDisplay = (record) => record ? Object.fromEntries(Object.entries(record).map(([key, ids]) => [key, ids.map((id) => face.display.get(id) ?? id)])) : void 0;
|
|
15785
|
+
const boxes = toDisplay(boxesIn);
|
|
15786
|
+
const subgroups = boxes ? toDisplay(opts.subgroups) : void 0;
|
|
15780
15787
|
const realBand = moduleBandOf(irIn);
|
|
15781
15788
|
const toReal = new Map(ir.nodes.map((n, i) => [n.id, irIn.nodes[i].id]));
|
|
15782
15789
|
const bandOf = realBand ? (id) => realBand(toReal.get(id) ?? id) : void 0;
|
|
@@ -15786,13 +15793,14 @@ function estateFace(irIn, boxesIn, opts = {}) {
|
|
|
15786
15793
|
...opts.nodesep !== void 0 ? { nodesep: opts.nodesep } : {},
|
|
15787
15794
|
...opts.ranksep !== void 0 ? { ranksep: opts.ranksep } : {}
|
|
15788
15795
|
}) : { ir, grids: /* @__PURE__ */ new Map() };
|
|
15789
|
-
return { ir, overrides: face.overrides, boxes, display: face.display, bandOf, chains, restore: face.restore };
|
|
15796
|
+
return { ir, overrides: face.overrides, boxes, subgroups, display: face.display, bandOf, chains, restore: face.restore };
|
|
15790
15797
|
}
|
|
15791
15798
|
function packEstateBoxes(layout, face, opts = {}) {
|
|
15792
15799
|
const boxes = face.boxes;
|
|
15793
15800
|
if (!boxes) return;
|
|
15794
|
-
const boxGrids = packBoxComponents(layout, face.ir, boxes, face.bandOf);
|
|
15795
|
-
if (opts.members) packMemberBoxes(layout, face.ir, boxes);
|
|
15801
|
+
const boxGrids = packBoxComponents(layout, face.ir, boxes, face.bandOf, face.subgroups);
|
|
15802
|
+
if (opts.members) packMemberBoxes(layout, face.ir, { ...topLevelSubgroups(layout.groups, face.subgroups), ...boxes });
|
|
15803
|
+
if (face.subgroups) fitNestedBoxes(layout, face.ir, boxes, face.subgroups);
|
|
15796
15804
|
addBandBoxes(layout, new Map([...boxGrids, ...face.chains.grids]), footprints(face.ir, face.overrides));
|
|
15797
15805
|
applyBadges(layout.groups, opts.badges, opts.marks);
|
|
15798
15806
|
}
|
|
@@ -15826,17 +15834,27 @@ function renderGraph(irIn, opts = {}) {
|
|
|
15826
15834
|
const groupsIn = irIn.groups;
|
|
15827
15835
|
const pipelines = groupsIn.byPipeline && Object.keys(groupsIn.byPipeline).length ? groupsIn.byPipeline : void 0;
|
|
15828
15836
|
const boxKey = groupsIn.byWave ? "byWave" : opts.boxes ?? (pipelines ? "byPipeline" : void 0);
|
|
15829
|
-
const
|
|
15837
|
+
const nested = boxKey === "byStack" ? pipelines : void 0;
|
|
15838
|
+
const face = estateFace(irIn, boxKey ? groupsIn[boxKey] : void 0, nested ? { subgroups: nested } : {});
|
|
15830
15839
|
const ir = face.ir;
|
|
15831
15840
|
const boxes = face.boxes;
|
|
15832
15841
|
const chains = face.chains;
|
|
15833
|
-
const layout = layoutIr(chains.ir, {
|
|
15842
|
+
const layout = layoutIr(chains.ir, {
|
|
15843
|
+
fit: true,
|
|
15844
|
+
overrides: face.overrides,
|
|
15845
|
+
...boxes ? { groups: boxes } : {},
|
|
15846
|
+
...face.subgroups ? { subgroups: face.subgroups } : {}
|
|
15847
|
+
});
|
|
15834
15848
|
if (opts.radial && !boxes) radializeLayout(layout, groupKeyByNode(ir), footprints(ir, face.overrides));
|
|
15835
15849
|
else if (!boxes) packComponents(layout, ir);
|
|
15836
15850
|
packEstateBoxes(layout, face, { members: boxKey === "byStack", ...opts.groupBadges ? { badges: opts.groupBadges } : {} });
|
|
15837
|
-
if (boxKey === "byPipeline" && pipelines) {
|
|
15851
|
+
if ((boxKey === "byPipeline" || nested) && pipelines) {
|
|
15838
15852
|
const titles = pipelineTitles(irIn, pipelines);
|
|
15839
|
-
for (const box of layout.groups ?? [])
|
|
15853
|
+
for (const box of layout.groups ?? []) {
|
|
15854
|
+
if (box.id === void 0 || titles[box.id] === void 0) continue;
|
|
15855
|
+
const title = titles[box.id];
|
|
15856
|
+
box.title = title === box.id && box.parent !== void 0 && title.startsWith(`${box.parent}/`) ? title.slice(box.parent.length + 1) : title;
|
|
15857
|
+
}
|
|
15840
15858
|
}
|
|
15841
15859
|
const svg = renderSvg(ir, layout, {
|
|
15842
15860
|
fit: true,
|
|
@@ -15849,9 +15867,16 @@ function renderGraph(irIn, opts = {}) {
|
|
|
15849
15867
|
}
|
|
15850
15868
|
function pipelineTitles(ir, byPipeline) {
|
|
15851
15869
|
const nodes = new Map(ir.nodes.map((n) => [n.id, n]));
|
|
15870
|
+
const workflowOf = (ids) => {
|
|
15871
|
+
const workflows = ids.flatMap((id) => {
|
|
15872
|
+
const n = nodes.get(id);
|
|
15873
|
+
return n && /::Workflow$/.test(n.kind) ? [n] : [];
|
|
15874
|
+
});
|
|
15875
|
+
return workflows.length === 1 ? workflows[0] : void 0;
|
|
15876
|
+
};
|
|
15852
15877
|
return Object.fromEntries(
|
|
15853
|
-
Object.
|
|
15854
|
-
const name = nodes.get(key)?.attrs?.["name"];
|
|
15878
|
+
Object.entries(byPipeline).map(([key, ids]) => {
|
|
15879
|
+
const name = (nodes.get(key) ?? workflowOf(ids))?.attrs?.["name"];
|
|
15855
15880
|
return [key, typeof name === "string" && name.trim() ? name : key];
|
|
15856
15881
|
})
|
|
15857
15882
|
);
|
|
@@ -15901,6 +15926,39 @@ function addBandBoxes(layout, grids, sizes) {
|
|
|
15901
15926
|
boxes.push(...added);
|
|
15902
15927
|
}
|
|
15903
15928
|
var BAND_PAD = 14;
|
|
15929
|
+
var NEST_PAD_X = 50;
|
|
15930
|
+
var NEST_PAD_Y = 36;
|
|
15931
|
+
var NEST_OUTER = 36;
|
|
15932
|
+
function topLevelSubgroups(boxes, subgroups) {
|
|
15933
|
+
if (!subgroups) return {};
|
|
15934
|
+
return Object.fromEntries(
|
|
15935
|
+
(boxes ?? []).flatMap((b) => b.parent === void 0 && b.id !== void 0 && subgroups[b.id] ? [[b.id, subgroups[b.id]]] : [])
|
|
15936
|
+
);
|
|
15937
|
+
}
|
|
15938
|
+
function fitNestedBoxes(layout, ir, boxes, subgroups, only2) {
|
|
15939
|
+
const at2 = new Map(layout.nodes.map((n) => [n.id, n]));
|
|
15940
|
+
const ownerOf2 = /* @__PURE__ */ new Map();
|
|
15941
|
+
for (const [key, ids] of Object.entries(boxes)) for (const id of ids) if (at2.has(id)) ownerOf2.set(id, key);
|
|
15942
|
+
const sizes = footprints(ir);
|
|
15943
|
+
for (const box of layout.groups ?? []) {
|
|
15944
|
+
if (box.parent === void 0 || box.id === void 0 || only2 !== void 0 && box.parent !== only2) continue;
|
|
15945
|
+
const cards = (subgroups[box.id] ?? []).flatMap((id) => {
|
|
15946
|
+
const p = at2.get(id);
|
|
15947
|
+
if (!p || ownerOf2.get(id) !== box.parent) return [];
|
|
15948
|
+
const s = sizes.get(id) ?? { w: NODE_W, h: NODE_H };
|
|
15949
|
+
return [{ x: p.x, y: p.y, w: s.w, h: s.h }];
|
|
15950
|
+
});
|
|
15951
|
+
if (!cards.length) continue;
|
|
15952
|
+
const x0 = Math.min(...cards.map((c) => c.x - c.w / 2)) - NEST_PAD_X;
|
|
15953
|
+
const x1 = Math.max(...cards.map((c) => c.x + c.w / 2)) + NEST_PAD_X;
|
|
15954
|
+
const y0 = Math.min(...cards.map((c) => c.y - c.h / 2)) - NEST_PAD_Y;
|
|
15955
|
+
const y1 = Math.max(...cards.map((c) => c.y + c.h / 2)) + NEST_PAD_Y;
|
|
15956
|
+
box.x = (x0 + x1) / 2;
|
|
15957
|
+
box.y = (y0 + y1) / 2;
|
|
15958
|
+
box.w = x1 - x0;
|
|
15959
|
+
box.h = y1 - y0;
|
|
15960
|
+
}
|
|
15961
|
+
}
|
|
15904
15962
|
function clampInside(r, box) {
|
|
15905
15963
|
const inset = 6;
|
|
15906
15964
|
const left = box.x - box.w / 2 + inset;
|
|
@@ -16216,7 +16274,8 @@ function packComponents(layout, ir) {
|
|
|
16216
16274
|
layout.height = Math.max(...nodes.map((n) => n.y + halfH(n))) + pad;
|
|
16217
16275
|
}
|
|
16218
16276
|
function packMemberBoxes(layout, ir, members) {
|
|
16219
|
-
const
|
|
16277
|
+
const allBoxes = layout.groups ?? [];
|
|
16278
|
+
const boxes = allBoxes.filter((b) => b.parent === void 0);
|
|
16220
16279
|
if (boxes.length < 4) return;
|
|
16221
16280
|
const nodes = layout.nodes;
|
|
16222
16281
|
const size = footprints(ir);
|
|
@@ -16332,7 +16391,7 @@ function gridComponent(one, sizes) {
|
|
|
16332
16391
|
const rows = Math.ceil(order.length / cols);
|
|
16333
16392
|
return { local, w: Math.min(cols, order.length) * (cell.w + sep12.x) - sep12.x, h: rows * (cell.h + sep12.y) - sep12.y };
|
|
16334
16393
|
}
|
|
16335
|
-
function packBoxComponents(layout, ir, boxes, bandOf) {
|
|
16394
|
+
function packBoxComponents(layout, ir, boxes, bandOf, subgroups) {
|
|
16336
16395
|
const grids = /* @__PURE__ */ new Map();
|
|
16337
16396
|
const groupBoxes = layout.groups ?? [];
|
|
16338
16397
|
const nodes = layout.nodes;
|
|
@@ -16363,6 +16422,10 @@ function packBoxComponents(layout, ir, boxes, bandOf) {
|
|
|
16363
16422
|
const b = idxOf.get(e.to);
|
|
16364
16423
|
if (a !== void 0 && b !== void 0) parent[find(a)] = find(b);
|
|
16365
16424
|
}
|
|
16425
|
+
for (const ids2 of Object.values(subgroups ?? {})) {
|
|
16426
|
+
const inside = ids2.flatMap((id) => idxOf.get(id) ?? []);
|
|
16427
|
+
for (const i of inside.slice(1)) parent[find(i)] = find(inside[0]);
|
|
16428
|
+
}
|
|
16366
16429
|
const comps = /* @__PURE__ */ new Map();
|
|
16367
16430
|
own.forEach((n, i) => (comps.get(find(i)) ?? comps.set(find(i), []).get(find(i))).push(n));
|
|
16368
16431
|
if (comps.size < 2) continue;
|
|
@@ -16375,12 +16438,19 @@ function packBoxComponents(layout, ir, boxes, bandOf) {
|
|
|
16375
16438
|
edges: ir.edges.filter((e) => inside.has(e.from) && inside.has(e.to)),
|
|
16376
16439
|
groups: {}
|
|
16377
16440
|
};
|
|
16378
|
-
|
|
16441
|
+
const nest = Object.fromEntries(
|
|
16442
|
+
Object.entries(subgroups ?? {}).flatMap(([k, ids2]) => {
|
|
16443
|
+
const mine = ids2.filter((id) => inside.has(id));
|
|
16444
|
+
return mine.length ? [[k, mine]] : [];
|
|
16445
|
+
})
|
|
16446
|
+
);
|
|
16447
|
+
const nests = Object.keys(nest).length > 0;
|
|
16448
|
+
let one = layoutIr(sub, { fit: true, ...nests ? { groups: nest } : {} });
|
|
16379
16449
|
if (one.width > one.height * BOX_ASPECT_MAX) {
|
|
16380
|
-
const sideways = layoutIr(sub, { fit: true, rankdir: "LR" });
|
|
16450
|
+
const sideways = layoutIr(sub, { fit: true, rankdir: "LR", ...nests ? { groups: nest } : {} });
|
|
16381
16451
|
if (offAspect(sideways) < offAspect(one)) one = sideways;
|
|
16382
16452
|
}
|
|
16383
|
-
const placed = offAspect(one) > GRID_COMPONENT ? gridComponent(one, size) : { local: new Map(one.nodes.map((n) => [n.id, { x: n.x, y: one.height - n.y }])), w: one.width, h: one.height };
|
|
16453
|
+
const placed = !nests && offAspect(one) > GRID_COMPONENT ? gridComponent(one, size) : { local: new Map(one.nodes.map((n) => [n.id, { x: n.x, y: one.height - n.y }])), w: one.width, h: one.height };
|
|
16384
16454
|
laid.set(root, { ...placed, ns });
|
|
16385
16455
|
}
|
|
16386
16456
|
const order = [void 0];
|
|
@@ -16396,8 +16466,9 @@ function packBoxComponents(layout, ir, boxes, bandOf) {
|
|
|
16396
16466
|
const all = [...laid.values()];
|
|
16397
16467
|
const area = all.reduce((s, c) => s + (c.w + COMPONENT_GAP) * (c.h + COMPONENT_GAP), 0);
|
|
16398
16468
|
const targetW = Math.max(Math.max(...all.map((c) => c.w)), Math.sqrt(area * ROW_ASPECT));
|
|
16399
|
-
const
|
|
16400
|
-
const
|
|
16469
|
+
const holdsNested = groupBoxes.some((b) => b.parent === key);
|
|
16470
|
+
const originX = box.x - box.w / 2 + (holdsNested ? NEST_OUTER : BAND_PAD);
|
|
16471
|
+
const originY = box.y - box.h / 2 + (holdsNested ? NEST_OUTER : TITLE);
|
|
16401
16472
|
let shelfX = 0;
|
|
16402
16473
|
let shelfY = 0;
|
|
16403
16474
|
let shelfH = 0;
|
|
@@ -16429,10 +16500,18 @@ function packBoxComponents(layout, ir, boxes, bandOf) {
|
|
|
16429
16500
|
}
|
|
16430
16501
|
bands.push({ key: band, ids: ids2 });
|
|
16431
16502
|
}
|
|
16432
|
-
|
|
16433
|
-
const
|
|
16434
|
-
|
|
16435
|
-
|
|
16503
|
+
if (subgroups) fitNestedBoxes(layout, ir, boxes, subgroups, key);
|
|
16504
|
+
const inner = groupBoxes.filter((b) => b.parent === key);
|
|
16505
|
+
let x0 = Math.min(...own.map((n) => n.x - halfW(n))) - BAND_PAD;
|
|
16506
|
+
let x1 = Math.max(...own.map((n) => n.x + halfW(n))) + BAND_PAD;
|
|
16507
|
+
let y0 = Math.min(...own.map((n) => n.y - halfH(n))) - TITLE;
|
|
16508
|
+
let y1 = Math.max(...own.map((n) => n.y + halfH(n))) + BAND_PAD;
|
|
16509
|
+
for (const b of inner) {
|
|
16510
|
+
x0 = Math.min(x0, b.x - b.w / 2 - NEST_OUTER);
|
|
16511
|
+
x1 = Math.max(x1, b.x + b.w / 2 + NEST_OUTER);
|
|
16512
|
+
y0 = Math.min(y0, b.y - b.h / 2 - NEST_OUTER);
|
|
16513
|
+
y1 = Math.max(y1, b.y + b.h / 2 + NEST_OUTER);
|
|
16514
|
+
}
|
|
16436
16515
|
box.x = (x0 + x1) / 2;
|
|
16437
16516
|
box.y = (y0 + y1) / 2;
|
|
16438
16517
|
box.w = x1 - x0;
|
|
@@ -17411,11 +17490,11 @@ var RAN = { ok: "ok", fail: "failed", skipped: "skipped" };
|
|
|
17411
17490
|
function tookLabel(ms) {
|
|
17412
17491
|
return ms < 1e3 ? `${ms}ms` : `${(ms / 1e3).toFixed(1)}s`;
|
|
17413
17492
|
}
|
|
17414
|
-
function paintPlayhead(ir,
|
|
17415
|
-
if (!
|
|
17493
|
+
function paintPlayhead(ir, join51, gate) {
|
|
17494
|
+
if (!join51.byNode.size && !join51.at) return ir;
|
|
17416
17495
|
const nodes = ir.nodes.map((node) => {
|
|
17417
|
-
const run3 =
|
|
17418
|
-
const here =
|
|
17496
|
+
const run3 = join51.byNode.get(node.id);
|
|
17497
|
+
const here = join51.at === node.id;
|
|
17419
17498
|
if (!run3 && !here) return node;
|
|
17420
17499
|
const isGate = node.attrs?._step === "gate";
|
|
17421
17500
|
const attrs = { ...node.attrs };
|
|
@@ -17430,7 +17509,7 @@ function paintPlayhead(ir, join49, gate) {
|
|
|
17430
17509
|
if (here) {
|
|
17431
17510
|
attrs._playhead = true;
|
|
17432
17511
|
attrs._status = isGate && gate ? "warn" : "accent";
|
|
17433
|
-
attrs.run = isGate && gate ? `waiting for approval since ${gate.since}` :
|
|
17512
|
+
attrs.run = isGate && gate ? `waiting for approval since ${gate.since}` : join51.atReason ?? "here";
|
|
17434
17513
|
if (isGate && gate?.description) attrs.gateDescription = gate.description;
|
|
17435
17514
|
}
|
|
17436
17515
|
return { ...node, attrs };
|
|
@@ -17452,11 +17531,11 @@ function pendingGateCard(op, gate, ir) {
|
|
|
17452
17531
|
}
|
|
17453
17532
|
};
|
|
17454
17533
|
}
|
|
17455
|
-
function playheadNote(state,
|
|
17534
|
+
function playheadNote(state, join51) {
|
|
17456
17535
|
if (!state.op) {
|
|
17457
17536
|
return "No run state: nothing has been run from this behold session, so every step paints as declared.";
|
|
17458
17537
|
}
|
|
17459
|
-
const settled =
|
|
17538
|
+
const settled = join51 ? join51.byNode.size : state.records.length;
|
|
17460
17539
|
const bits = [];
|
|
17461
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`;
|
|
17462
17541
|
if (state.status === "running") {
|
|
@@ -17472,9 +17551,9 @@ function playheadNote(state, join49) {
|
|
|
17472
17551
|
} else if (state.gateNote) {
|
|
17473
17552
|
bits.push(`gate state unavailable \u2014 ${state.gateNote}`);
|
|
17474
17553
|
}
|
|
17475
|
-
if (
|
|
17554
|
+
if (join51?.unplaced.length) {
|
|
17476
17555
|
bits.push(
|
|
17477
|
-
`${
|
|
17556
|
+
`${join51.unplaced.length} record${join51.unplaced.length === 1 ? "" : "s"} matched no declared step and ${join51.unplaced.length === 1 ? "is" : "are"} not painted`
|
|
17478
17557
|
);
|
|
17479
17558
|
}
|
|
17480
17559
|
if (!bits.length) return `${head}.`;
|
|
@@ -18144,10 +18223,90 @@ var OpRunner = class {
|
|
|
18144
18223
|
}
|
|
18145
18224
|
};
|
|
18146
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
|
+
|
|
18147
18306
|
// src/substrates.ts
|
|
18148
18307
|
import { spawn as spawn5 } from "node:child_process";
|
|
18149
|
-
import { existsSync as existsSync14, readFileSync as
|
|
18150
|
-
import { join as
|
|
18308
|
+
import { existsSync as existsSync14, readFileSync as readFileSync16 } from "node:fs";
|
|
18309
|
+
import { join as join21 } from "node:path";
|
|
18151
18310
|
import { platform } from "node:os";
|
|
18152
18311
|
var DOCKER_DOWN_DETAIL = "docker is down";
|
|
18153
18312
|
function probe(cmd, args) {
|
|
@@ -18176,11 +18335,11 @@ async function dockerRunning(run3, nameFilter) {
|
|
|
18176
18335
|
return out.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
18177
18336
|
}
|
|
18178
18337
|
function scriptBringUp(projectDir, relPath, label) {
|
|
18179
|
-
return existsSync14(
|
|
18338
|
+
return existsSync14(join21(projectDir, relPath)) ? { label, cmd: "bash", args: [relPath] } : void 0;
|
|
18180
18339
|
}
|
|
18181
18340
|
function projectLexicons(projectDir) {
|
|
18182
18341
|
try {
|
|
18183
|
-
const src =
|
|
18342
|
+
const src = readFileSync16(join21(projectDir, "chant.config.ts"), "utf-8");
|
|
18184
18343
|
const m = src.match(/lexicons\s*:\s*\[([^\]]*)\]/);
|
|
18185
18344
|
if (!m) return [];
|
|
18186
18345
|
return [...m[1].matchAll(/["']([^"']+)["']/g)].map((x) => x[1]);
|
|
@@ -18232,7 +18391,7 @@ async function detectSubstrates(projectDir, preview = false, boundContext, probe
|
|
|
18232
18391
|
["forgejo", "Forgejo", ".forgejo", "test/forgejo-runtime-e2e.sh"]
|
|
18233
18392
|
];
|
|
18234
18393
|
for (const [name, label, marker, script] of forges) {
|
|
18235
|
-
if (!existsSync14(
|
|
18394
|
+
if (!existsSync14(join21(projectDir, marker))) continue;
|
|
18236
18395
|
const c = docker ? await dockerRunning(run3, name) : [];
|
|
18237
18396
|
const d = dep(c.length > 0, "container up", "on-demand (pipeline run)");
|
|
18238
18397
|
subs.push({
|
|
@@ -18266,7 +18425,7 @@ async function detectSubstrates(projectDir, preview = false, boundContext, probe
|
|
|
18266
18425
|
detail: endpoint ? `targeting ${endpoint}` : "real Fly (FLY_FLAPS_BASE_URL unset)"
|
|
18267
18426
|
});
|
|
18268
18427
|
}
|
|
18269
|
-
if (existsSync14(
|
|
18428
|
+
if (existsSync14(join21(projectDir, ".github", "workflows"))) {
|
|
18270
18429
|
const gh = await run3("gh", ["auth", "status"]);
|
|
18271
18430
|
const ready = gh.code === 0;
|
|
18272
18431
|
subs.push({
|
|
@@ -18279,7 +18438,7 @@ async function detectSubstrates(projectDir, preview = false, boundContext, probe
|
|
|
18279
18438
|
if (lexicons.includes("temporal")) {
|
|
18280
18439
|
let hasProfiles = false;
|
|
18281
18440
|
try {
|
|
18282
|
-
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"));
|
|
18283
18442
|
} catch {
|
|
18284
18443
|
}
|
|
18285
18444
|
subs.push({
|
|
@@ -18422,8 +18581,8 @@ function statusVocabulary(kinds) {
|
|
|
18422
18581
|
}
|
|
18423
18582
|
|
|
18424
18583
|
// src/behaviour.ts
|
|
18425
|
-
import { readFileSync as
|
|
18426
|
-
import { join as
|
|
18584
|
+
import { readFileSync as readFileSync17 } from "node:fs";
|
|
18585
|
+
import { join as join22 } from "node:path";
|
|
18427
18586
|
var rec5 = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : void 0;
|
|
18428
18587
|
var str6 = (v) => typeof v === "string" && v.trim().length > 0 ? v : void 0;
|
|
18429
18588
|
var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
@@ -18530,7 +18689,7 @@ var FIRST_TOTAL_SHOWN = "two members stated an estate total; the first is shown"
|
|
|
18530
18689
|
var behaviourFileName = (env) => `behaviour.${env}.json`;
|
|
18531
18690
|
var realRead = (path) => {
|
|
18532
18691
|
try {
|
|
18533
|
-
return
|
|
18692
|
+
return readFileSync17(path, "utf8");
|
|
18534
18693
|
} catch {
|
|
18535
18694
|
return void 0;
|
|
18536
18695
|
}
|
|
@@ -18588,7 +18747,7 @@ function attachBehaviour(ir, members, env, readFile2 = realRead) {
|
|
|
18588
18747
|
}
|
|
18589
18748
|
const file = behaviourFileName(env);
|
|
18590
18749
|
looked.push(`${label}/${file}`);
|
|
18591
|
-
const raw = readFile2(
|
|
18750
|
+
const raw = readFile2(join22(m.dir, file));
|
|
18592
18751
|
if (raw === void 0) {
|
|
18593
18752
|
sources.set(label, "none");
|
|
18594
18753
|
continue;
|
|
@@ -19024,7 +19183,7 @@ init_choudoufu_live();
|
|
|
19024
19183
|
init_choudoufu_member();
|
|
19025
19184
|
import { mkdtempSync, rmSync as rmSync3 } from "node:fs";
|
|
19026
19185
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
19027
|
-
import { join as
|
|
19186
|
+
import { join as join23 } from "node:path";
|
|
19028
19187
|
var isRecord10 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
19029
19188
|
var refuse7 = (error, remedy) => ({ ok: false, refusal: { error, code: "choudoufu-live-check", remedy } });
|
|
19030
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.";
|
|
@@ -19098,8 +19257,8 @@ var planArgv = (out) => ({
|
|
|
19098
19257
|
show: ["show", "-json", out]
|
|
19099
19258
|
});
|
|
19100
19259
|
async function readChoudoufuPlan(dir, run3 = captureChoudoufu) {
|
|
19101
|
-
const scratch = mkdtempSync(
|
|
19102
|
-
const file =
|
|
19260
|
+
const scratch = mkdtempSync(join23(tmpdir2(), "behold-choudoufu-plan-"));
|
|
19261
|
+
const file = join23(scratch, "plan.tfplan");
|
|
19103
19262
|
const argv = planArgv(file);
|
|
19104
19263
|
try {
|
|
19105
19264
|
const planRun = await run3(argv.plan, dir);
|
|
@@ -19131,8 +19290,8 @@ init_choudoufu_refs();
|
|
|
19131
19290
|
// src/choudoufu-moves.ts
|
|
19132
19291
|
init_choudoufu_member();
|
|
19133
19292
|
init_choudoufu_live();
|
|
19134
|
-
import { existsSync as existsSync15, readFileSync as
|
|
19135
|
-
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";
|
|
19136
19295
|
var isRecord11 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
19137
19296
|
var refuse8 = (error, remedy) => ({ ok: false, refusal: { error, code: "choudoufu-live-check", remedy } });
|
|
19138
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.";
|
|
@@ -19152,7 +19311,7 @@ function parseCarvePlan(value) {
|
|
|
19152
19311
|
}
|
|
19153
19312
|
};
|
|
19154
19313
|
}
|
|
19155
|
-
function readCarvePlan(path, readFile2 = (p) =>
|
|
19314
|
+
function readCarvePlan(path, readFile2 = (p) => readFileSync18(p, "utf8")) {
|
|
19156
19315
|
let text;
|
|
19157
19316
|
try {
|
|
19158
19317
|
text = readFile2(path);
|
|
@@ -19168,7 +19327,7 @@ function readCarvePlan(path, readFile2 = (p) => readFileSync17(p, "utf8")) {
|
|
|
19168
19327
|
function discoverCarvePlans(memberDirs, base) {
|
|
19169
19328
|
const out = [];
|
|
19170
19329
|
for (const dir of memberDirs) {
|
|
19171
|
-
const p =
|
|
19330
|
+
const p = join24(dir, "carve.json");
|
|
19172
19331
|
if (existsSync15(p)) out.push(relative5(base, p));
|
|
19173
19332
|
}
|
|
19174
19333
|
return out;
|
|
@@ -19399,24 +19558,24 @@ function getRuntime() {
|
|
|
19399
19558
|
|
|
19400
19559
|
// node_modules/@intentius/chant/src/lifecycle/member-ledger.ts
|
|
19401
19560
|
import { existsSync as existsSync18 } from "node:fs";
|
|
19402
|
-
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";
|
|
19403
19562
|
|
|
19404
19563
|
// node_modules/@intentius/chant/src/project-root.ts
|
|
19405
19564
|
import { existsSync as existsSync16 } from "fs";
|
|
19406
|
-
import { dirname as dirname6, join as
|
|
19565
|
+
import { dirname as dirname6, join as join25, resolve as resolve13 } from "path";
|
|
19407
19566
|
var WORKSPACE_DECLARATION_FILES = ["chant.workspace.json", "chant.workspace.jsonc"];
|
|
19408
19567
|
function findWorkspaceRoot(startDir) {
|
|
19409
19568
|
const start = resolve13(startDir);
|
|
19410
19569
|
let top;
|
|
19411
19570
|
for (let dir = start; ; dir = dirname6(dir)) {
|
|
19412
|
-
if (existsSync16(
|
|
19571
|
+
if (existsSync16(join25(dir, ".git"))) {
|
|
19413
19572
|
top = dir;
|
|
19414
19573
|
break;
|
|
19415
19574
|
}
|
|
19416
19575
|
if (dirname6(dir) === dir) break;
|
|
19417
19576
|
}
|
|
19418
19577
|
for (let dir = start; ; dir = dirname6(dir)) {
|
|
19419
|
-
const [json2, jsonc] = WORKSPACE_DECLARATION_FILES.map((name) =>
|
|
19578
|
+
const [json2, jsonc] = WORKSPACE_DECLARATION_FILES.map((name) => join25(dir, name));
|
|
19420
19579
|
const hasJson = existsSync16(json2);
|
|
19421
19580
|
const hasJsonc = existsSync16(jsonc);
|
|
19422
19581
|
if (hasJson && hasJsonc) return { dir, file: json2, conflicting: jsonc };
|
|
@@ -19478,7 +19637,7 @@ async function ledgerIn(root, dir) {
|
|
|
19478
19637
|
}
|
|
19479
19638
|
let outer = FLAT;
|
|
19480
19639
|
const parent = dirname8(root);
|
|
19481
|
-
if (parent !== root && !existsSync18(
|
|
19640
|
+
if (parent !== root && !existsSync18(join28(root, ".git"))) {
|
|
19482
19641
|
const outerFound = findWorkspaceRoot(parent);
|
|
19483
19642
|
if (outerFound) outer = await ledgerIn(outerFound.dir, root);
|
|
19484
19643
|
}
|
|
@@ -19486,6 +19645,17 @@ async function ledgerIn(root, dir) {
|
|
|
19486
19645
|
return { members, prefix: memberLedgerPrefix(members) };
|
|
19487
19646
|
}
|
|
19488
19647
|
|
|
19648
|
+
// node_modules/@intentius/chant/src/lifecycle/no-record.ts
|
|
19649
|
+
var NO_RECORD_ENV = "CHANT_NO_RECORD";
|
|
19650
|
+
function ledgerRecordingOff(env = process.env) {
|
|
19651
|
+
const value = env[NO_RECORD_ENV]?.trim().toLowerCase();
|
|
19652
|
+
return value !== void 0 && value !== "" && value !== "0" && value !== "false";
|
|
19653
|
+
}
|
|
19654
|
+
var overlays = /* @__PURE__ */ new Map();
|
|
19655
|
+
function overlayTip(key) {
|
|
19656
|
+
return overlays.get(key)?.tip;
|
|
19657
|
+
}
|
|
19658
|
+
|
|
19489
19659
|
// node_modules/@intentius/chant/src/lifecycle/git.ts
|
|
19490
19660
|
var STATE_BRANCH = "chant/lifecycle";
|
|
19491
19661
|
async function ledgerDir(dir, opts) {
|
|
@@ -19502,7 +19672,7 @@ async function readBlobFromPath(environment, filename, opts) {
|
|
|
19502
19672
|
const rt = getRuntime();
|
|
19503
19673
|
const dir = await ledgerDir(environment, opts);
|
|
19504
19674
|
const result = await rt.spawn(
|
|
19505
|
-
["git", "show", `${
|
|
19675
|
+
["git", "show", `${await ledgerRevision(opts?.cwd)}:${dir}/${filename}`],
|
|
19506
19676
|
{ cwd: opts?.cwd }
|
|
19507
19677
|
);
|
|
19508
19678
|
if (result.exitCode !== 0) return null;
|
|
@@ -19527,7 +19697,7 @@ async function listLedgerTrees(opts) {
|
|
|
19527
19697
|
const rt = getRuntime();
|
|
19528
19698
|
const base = await ledgerDir("", opts);
|
|
19529
19699
|
const rootResult = await rt.spawn(
|
|
19530
|
-
["git", "ls-tree", "--full-tree", base === "" ?
|
|
19700
|
+
["git", "ls-tree", "--full-tree", base === "" ? tip : `${tip}:${base}`],
|
|
19531
19701
|
{ cwd: opts?.cwd }
|
|
19532
19702
|
);
|
|
19533
19703
|
if (rootResult.exitCode !== 0) return [];
|
|
@@ -19538,7 +19708,12 @@ async function listLedgerTrees(opts) {
|
|
|
19538
19708
|
}
|
|
19539
19709
|
return trees;
|
|
19540
19710
|
}
|
|
19711
|
+
var C_LOCALE_ENV = { LC_ALL: "C", LANGUAGE: "" };
|
|
19541
19712
|
async function getStateBranchTip(cwd) {
|
|
19713
|
+
if (ledgerRecordingOff()) {
|
|
19714
|
+
const tip = overlayTip(await overlayKey(cwd));
|
|
19715
|
+
if (tip !== void 0) return tip;
|
|
19716
|
+
}
|
|
19542
19717
|
const rt = getRuntime();
|
|
19543
19718
|
const result = await rt.spawn(
|
|
19544
19719
|
["git", "rev-parse", "--verify", `refs/heads/${STATE_BRANCH}`],
|
|
@@ -19547,6 +19722,20 @@ async function getStateBranchTip(cwd) {
|
|
|
19547
19722
|
if (result.exitCode !== 0) return null;
|
|
19548
19723
|
return result.stdout.trim();
|
|
19549
19724
|
}
|
|
19725
|
+
async function ledgerRevision(cwd) {
|
|
19726
|
+
if (!ledgerRecordingOff()) return STATE_BRANCH;
|
|
19727
|
+
return overlayTip(await overlayKey(cwd)) ?? STATE_BRANCH;
|
|
19728
|
+
}
|
|
19729
|
+
var overlayKeys = /* @__PURE__ */ new Map();
|
|
19730
|
+
async function overlayKey(cwd) {
|
|
19731
|
+
const from = cwd ?? process.cwd();
|
|
19732
|
+
const cached = overlayKeys.get(from);
|
|
19733
|
+
if (cached !== void 0) return cached;
|
|
19734
|
+
const result = await getRuntime().spawn(["git", "rev-parse", "--path-format=absolute", "--git-common-dir"], { cwd: from, env: C_LOCALE_ENV });
|
|
19735
|
+
const key = result.exitCode === 0 && result.stdout.trim() !== "" ? result.stdout.trim() : from;
|
|
19736
|
+
overlayKeys.set(from, key);
|
|
19737
|
+
return key;
|
|
19738
|
+
}
|
|
19550
19739
|
|
|
19551
19740
|
// node_modules/@intentius/chant/src/content-digest.ts
|
|
19552
19741
|
import { createHash as createHash6 } from "node:crypto";
|
|
@@ -20003,7 +20192,7 @@ function decideOverride(input) {
|
|
|
20003
20192
|
|
|
20004
20193
|
// src/terragucci-repo.ts
|
|
20005
20194
|
import { existsSync as existsSync19 } from "node:fs";
|
|
20006
|
-
import { dirname as dirname9, join as
|
|
20195
|
+
import { dirname as dirname9, join as join29, resolve as resolve16 } from "node:path";
|
|
20007
20196
|
var TERRAGUCCI_CONFIG_NAMES = ["terragucci.yml", "terragucci.yaml", "terragucci.json", "terragucci.ts"];
|
|
20008
20197
|
function terragucciConfig(dir) {
|
|
20009
20198
|
return walkUp(dir, TERRAGUCCI_CONFIG_NAMES);
|
|
@@ -20012,10 +20201,10 @@ function walkUp(dir, names) {
|
|
|
20012
20201
|
let at2 = resolve16(dir);
|
|
20013
20202
|
for (; ; ) {
|
|
20014
20203
|
for (const name of names) {
|
|
20015
|
-
const file =
|
|
20204
|
+
const file = join29(at2, name);
|
|
20016
20205
|
if (existsSync19(file)) return file;
|
|
20017
20206
|
}
|
|
20018
|
-
if (existsSync19(
|
|
20207
|
+
if (existsSync19(join29(at2, ".git"))) return void 0;
|
|
20019
20208
|
const up = dirname9(at2);
|
|
20020
20209
|
if (up === at2) return void 0;
|
|
20021
20210
|
at2 = up;
|
|
@@ -20420,6 +20609,9 @@ function guardRequest(req, cfg) {
|
|
|
20420
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.` };
|
|
20421
20610
|
}
|
|
20422
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
|
+
}
|
|
20423
20615
|
if ((req.secFetchSite || "").toLowerCase() === "cross-site") {
|
|
20424
20616
|
return { ok: false, status: 403, error: "a cross-site page can't write to behold" };
|
|
20425
20617
|
}
|
|
@@ -20815,7 +21007,7 @@ init_foreign();
|
|
|
20815
21007
|
|
|
20816
21008
|
// src/events.ts
|
|
20817
21009
|
import { watch, existsSync as existsSync20 } from "node:fs";
|
|
20818
|
-
import { join as
|
|
21010
|
+
import { join as join30 } from "node:path";
|
|
20819
21011
|
var Broadcaster = class {
|
|
20820
21012
|
listeners = /* @__PURE__ */ new Set();
|
|
20821
21013
|
subscribe(fn) {
|
|
@@ -20833,7 +21025,7 @@ var Broadcaster = class {
|
|
|
20833
21025
|
};
|
|
20834
21026
|
var IGNORE = /(^|[\\/])(node_modules|dist|\.git)([\\/]|$)/;
|
|
20835
21027
|
function watchSource(projectDir, onChange, debounceMs = 200) {
|
|
20836
|
-
const dir = existsSync20(
|
|
21028
|
+
const dir = existsSync20(join30(projectDir, "src")) ? join30(projectDir, "src") : projectDir;
|
|
20837
21029
|
let timer;
|
|
20838
21030
|
const watcher = watch(dir, { recursive: true }, (_event, file) => {
|
|
20839
21031
|
const name = typeof file === "string" ? file : "";
|
|
@@ -21136,13 +21328,13 @@ async function emulatorDown(projectDir) {
|
|
|
21136
21328
|
|
|
21137
21329
|
// src/terragucci-view.ts
|
|
21138
21330
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
21139
|
-
import { dirname as dirname13, join as
|
|
21331
|
+
import { dirname as dirname13, join as join39, relative as relative8, sep as sep7 } from "node:path";
|
|
21140
21332
|
init_terraform_member();
|
|
21141
21333
|
|
|
21142
21334
|
// src/terragucci-reports.ts
|
|
21143
|
-
import { readFileSync as
|
|
21335
|
+
import { readFileSync as readFileSync23, statSync as statSync10 } from "node:fs";
|
|
21144
21336
|
import { createRequire as createRequire4 } from "node:module";
|
|
21145
|
-
import { dirname as dirname10, join as
|
|
21337
|
+
import { dirname as dirname10, join as join33, resolve as resolve20 } from "node:path";
|
|
21146
21338
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
21147
21339
|
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
21148
21340
|
|
|
@@ -21151,12 +21343,12 @@ import { execFile as execFile4 } from "node:child_process";
|
|
|
21151
21343
|
import { createHash as createHash8 } from "node:crypto";
|
|
21152
21344
|
import { existsSync as existsSync21, mkdirSync as mkdirSync6, readdirSync as readdirSync11 } from "node:fs";
|
|
21153
21345
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
21154
|
-
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";
|
|
21155
21347
|
|
|
21156
21348
|
// node_modules/@intentius/chant/src/gate-decision.ts
|
|
21157
21349
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
21158
|
-
import { readFileSync as
|
|
21159
|
-
import { join as
|
|
21350
|
+
import { readFileSync as readFileSync22 } from "node:fs";
|
|
21351
|
+
import { join as join31, posix as posix3 } from "node:path";
|
|
21160
21352
|
|
|
21161
21353
|
// node_modules/@intentius/chant/src/lifecycle/gate-applied.ts
|
|
21162
21354
|
function appliedGatePath(op) {
|
|
@@ -21265,7 +21457,7 @@ function pinnedClassesAt(repo, base, pins, packages) {
|
|
|
21265
21457
|
}
|
|
21266
21458
|
let installed2;
|
|
21267
21459
|
try {
|
|
21268
|
-
installed2 = JSON.parse(
|
|
21460
|
+
installed2 = JSON.parse(readFileSync22(join31(dir, "package.json"), "utf-8")).version;
|
|
21269
21461
|
} catch {
|
|
21270
21462
|
installed2 = void 0;
|
|
21271
21463
|
}
|
|
@@ -21860,7 +22052,7 @@ var realGit = (args, o = {}) => new Promise((res) => {
|
|
|
21860
22052
|
});
|
|
21861
22053
|
var redact = (s) => s.replace(/(\b[a-z][a-z0-9+.-]*:\/\/)[^/@\s]+@/gi, "$1");
|
|
21862
22054
|
function cacheDir(origin, root = tmpdir3()) {
|
|
21863
|
-
return
|
|
22055
|
+
return join32(root, `${CACHE_PREFIX}${createHash8("sha256").update(origin).digest("hex").slice(0, 16)}`);
|
|
21864
22056
|
}
|
|
21865
22057
|
var inflight = /* @__PURE__ */ new Map();
|
|
21866
22058
|
function serially(key, fn) {
|
|
@@ -21879,7 +22071,7 @@ function fetchable(url, checkout) {
|
|
|
21879
22071
|
async function fetchIntoCache(git3, origin, cache6) {
|
|
21880
22072
|
assertScratch(basename3(cache6));
|
|
21881
22073
|
return serially(cache6, async () => {
|
|
21882
|
-
if (!existsSync21(
|
|
22074
|
+
if (!existsSync21(join32(cache6, "HEAD"))) {
|
|
21883
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` };
|
|
21884
22076
|
mkdirSync6(cache6, { recursive: true });
|
|
21885
22077
|
const init = await git3(["init", "--bare", "-q", cache6]);
|
|
@@ -22067,7 +22259,7 @@ function structural(name, doc) {
|
|
|
22067
22259
|
var compiledSchemas = /* @__PURE__ */ new Map();
|
|
22068
22260
|
function schemaStamp(dist) {
|
|
22069
22261
|
try {
|
|
22070
|
-
const files = [
|
|
22262
|
+
const files = [join33(dist, "..", "package.json"), ...Object.values(SCHEMA_FILES).map((f) => join33(dist, f))];
|
|
22071
22263
|
return [dist, ...files.map((f) => {
|
|
22072
22264
|
const st = statSync10(f);
|
|
22073
22265
|
return `${st.mtimeMs}:${st.size}`;
|
|
@@ -22077,10 +22269,10 @@ function schemaStamp(dist) {
|
|
|
22077
22269
|
}
|
|
22078
22270
|
}
|
|
22079
22271
|
function compileSchemas(dist) {
|
|
22080
|
-
const version = JSON.parse(
|
|
22272
|
+
const version = JSON.parse(readFileSync23(join33(dist, "..", "package.json"), "utf8")).version ?? "";
|
|
22081
22273
|
const ajv = new Ajv2020({ strict: false, allErrors: false });
|
|
22082
22274
|
const compiled4 = Object.fromEntries(
|
|
22083
|
-
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")))])
|
|
22084
22276
|
);
|
|
22085
22277
|
return {
|
|
22086
22278
|
validation: { by: "schema", terragucci: version },
|
|
@@ -22097,7 +22289,7 @@ function terragucciValidator(dirs = []) {
|
|
|
22097
22289
|
for (const from of [...dirs, dirname10(fileURLToPath5(import.meta.url))]) {
|
|
22098
22290
|
let entry;
|
|
22099
22291
|
try {
|
|
22100
|
-
entry = createRequire4(
|
|
22292
|
+
entry = createRequire4(join33(resolve20(from), "noop.js")).resolve("@intentius/terragucci/report.schema.json");
|
|
22101
22293
|
} catch {
|
|
22102
22294
|
continue;
|
|
22103
22295
|
}
|
|
@@ -22221,6 +22413,10 @@ async function readTerragucci(source2, opts = {}) {
|
|
|
22221
22413
|
}
|
|
22222
22414
|
}
|
|
22223
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"));
|
|
22224
22420
|
const estateDoc = await readDoc(source2, "estate.json", "estate", v);
|
|
22225
22421
|
const estateProject = estateDoc?.projects.find((p) => p.project === project);
|
|
22226
22422
|
const waiting = [];
|
|
@@ -22266,6 +22462,7 @@ async function readTerragucci(source2, opts = {}) {
|
|
|
22266
22462
|
reports: cache6.size,
|
|
22267
22463
|
roots,
|
|
22268
22464
|
waiting,
|
|
22465
|
+
...pullRequest ? { pull_request: pullRequest } : {},
|
|
22269
22466
|
...estateDoc && estateProject ? {
|
|
22270
22467
|
estate: {
|
|
22271
22468
|
generated: estateDoc.generated,
|
|
@@ -22278,6 +22475,57 @@ async function readTerragucci(source2, opts = {}) {
|
|
|
22278
22475
|
} : {}
|
|
22279
22476
|
};
|
|
22280
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
|
+
}
|
|
22281
22529
|
|
|
22282
22530
|
// src/terragucci-overlay.ts
|
|
22283
22531
|
function blockPath(address) {
|
|
@@ -22382,11 +22630,11 @@ function joinTerragucci(read, nodes, roots) {
|
|
|
22382
22630
|
// src/terragucci-source.ts
|
|
22383
22631
|
import { spawn as spawn7 } from "node:child_process";
|
|
22384
22632
|
import { readFile } from "node:fs/promises";
|
|
22385
|
-
import { join as
|
|
22633
|
+
import { join as join34, resolve as resolve21 } from "node:path";
|
|
22386
22634
|
|
|
22387
22635
|
// src/s3-object.ts
|
|
22388
22636
|
import { createHash as createHash9, createHmac } from "node:crypto";
|
|
22389
|
-
import { readFileSync as
|
|
22637
|
+
import { readFileSync as readFileSync24 } from "node:fs";
|
|
22390
22638
|
var S3Error = class extends Error {
|
|
22391
22639
|
/** The HTTP status S3 answered, when it answered. */
|
|
22392
22640
|
status;
|
|
@@ -22433,7 +22681,7 @@ function s3FromEnv(bucket, env = process.env) {
|
|
|
22433
22681
|
async function assumeRole(w, fetchFn) {
|
|
22434
22682
|
let token;
|
|
22435
22683
|
try {
|
|
22436
|
-
token =
|
|
22684
|
+
token = readFileSync24(w.tokenFile, "utf8").trim();
|
|
22437
22685
|
} catch (e) {
|
|
22438
22686
|
throw new S3Error(`cannot read the OIDC token in ${w.tokenFile}: ${e.message}`);
|
|
22439
22687
|
}
|
|
@@ -22630,7 +22878,7 @@ function terragucciSource(spec2, opts = {}) {
|
|
|
22630
22878
|
location: dir,
|
|
22631
22879
|
async read(key) {
|
|
22632
22880
|
try {
|
|
22633
|
-
return await readFile(
|
|
22881
|
+
return await readFile(join34(dir, ...trim(key).split("/")), "utf8");
|
|
22634
22882
|
} catch (e) {
|
|
22635
22883
|
if (e.code === "ENOENT") return void 0;
|
|
22636
22884
|
throw e;
|
|
@@ -22640,9 +22888,9 @@ function terragucciSource(spec2, opts = {}) {
|
|
|
22640
22888
|
}
|
|
22641
22889
|
|
|
22642
22890
|
// src/terragucci-timeline.ts
|
|
22643
|
-
import { readFileSync as
|
|
22891
|
+
import { readFileSync as readFileSync25, statSync as statSync11 } from "node:fs";
|
|
22644
22892
|
import { createRequire as createRequire5 } from "node:module";
|
|
22645
|
-
import { dirname as dirname11, join as
|
|
22893
|
+
import { dirname as dirname11, join as join35, resolve as resolve22 } from "node:path";
|
|
22646
22894
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
22647
22895
|
import { Ajv2020 as Ajv20202 } from "ajv/dist/2020.js";
|
|
22648
22896
|
var AUDIT_SCHEMA = "terragucci.audit/v1";
|
|
@@ -22683,12 +22931,12 @@ function auditValidator(dirs = []) {
|
|
|
22683
22931
|
for (const from of [...dirs, dirname11(fileURLToPath6(import.meta.url))]) {
|
|
22684
22932
|
let dist;
|
|
22685
22933
|
try {
|
|
22686
|
-
dist = dirname11(createRequire5(
|
|
22934
|
+
dist = dirname11(createRequire5(join35(resolve22(from), "noop.js")).resolve("@intentius/terragucci/report.schema.json"));
|
|
22687
22935
|
} catch {
|
|
22688
22936
|
continue;
|
|
22689
22937
|
}
|
|
22690
|
-
const pkg =
|
|
22691
|
-
const file =
|
|
22938
|
+
const pkg = join35(dist, "..", "package.json");
|
|
22939
|
+
const file = join35(dist, AUDIT_SCHEMA_FILE);
|
|
22692
22940
|
let stamp;
|
|
22693
22941
|
try {
|
|
22694
22942
|
stamp = [dist, ...[pkg, file].map((f) => {
|
|
@@ -22702,8 +22950,8 @@ function auditValidator(dirs = []) {
|
|
|
22702
22950
|
if (hit) return hit;
|
|
22703
22951
|
let v;
|
|
22704
22952
|
try {
|
|
22705
|
-
const version = JSON.parse(
|
|
22706
|
-
v = compileAuditSchema(JSON.parse(
|
|
22953
|
+
const version = JSON.parse(readFileSync25(pkg, "utf8")).version ?? "";
|
|
22954
|
+
v = compileAuditSchema(JSON.parse(readFileSync25(file, "utf8")), version);
|
|
22707
22955
|
} catch {
|
|
22708
22956
|
continue;
|
|
22709
22957
|
}
|
|
@@ -22899,7 +23147,7 @@ function terragucciTimelineRoute(app, source2, opts = {}) {
|
|
|
22899
23147
|
// src/terragucci-poll.ts
|
|
22900
23148
|
import { createHash as createHash10 } from "node:crypto";
|
|
22901
23149
|
import { stat } from "node:fs/promises";
|
|
22902
|
-
import { join as
|
|
23150
|
+
import { join as join36, resolve as resolve23 } from "node:path";
|
|
22903
23151
|
import { streamSSE } from "hono/streaming";
|
|
22904
23152
|
var TERRAGUCCI_POLL_SECS = 30;
|
|
22905
23153
|
var INDEX_KEY = "index.json";
|
|
@@ -22950,7 +23198,7 @@ function indexProbe(spec2, opts = {}) {
|
|
|
22950
23198
|
return verdict2(prev, etag);
|
|
22951
23199
|
};
|
|
22952
23200
|
}
|
|
22953
|
-
const file =
|
|
23201
|
+
const file = join36(resolve23(opts.cwd ?? process.cwd(), spec2), name);
|
|
22954
23202
|
return async (prev) => {
|
|
22955
23203
|
try {
|
|
22956
23204
|
const s = await stat(file);
|
|
@@ -23105,9 +23353,9 @@ function terragucciLaneRoutes(app, o, base, forget) {
|
|
|
23105
23353
|
}
|
|
23106
23354
|
|
|
23107
23355
|
// src/terragucci-progress.ts
|
|
23108
|
-
import { readFileSync as
|
|
23356
|
+
import { readFileSync as readFileSync26, statSync as statSync12 } from "node:fs";
|
|
23109
23357
|
import { createRequire as createRequire6 } from "node:module";
|
|
23110
|
-
import { dirname as dirname12, join as
|
|
23358
|
+
import { dirname as dirname12, join as join37, resolve as resolve24 } from "node:path";
|
|
23111
23359
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
23112
23360
|
import { Ajv2020 as Ajv20203 } from "ajv/dist/2020.js";
|
|
23113
23361
|
var RUN_SCHEMA = "terragucci.run/v1";
|
|
@@ -23152,12 +23400,12 @@ function runValidator(dirs = []) {
|
|
|
23152
23400
|
for (const from of [...dirs, dirname12(fileURLToPath7(import.meta.url))]) {
|
|
23153
23401
|
let dist;
|
|
23154
23402
|
try {
|
|
23155
|
-
dist = dirname12(createRequire6(
|
|
23403
|
+
dist = dirname12(createRequire6(join37(resolve24(from), "noop.js")).resolve("@intentius/terragucci/report.schema.json"));
|
|
23156
23404
|
} catch {
|
|
23157
23405
|
continue;
|
|
23158
23406
|
}
|
|
23159
|
-
const pkg =
|
|
23160
|
-
const file =
|
|
23407
|
+
const pkg = join37(dist, "..", "package.json");
|
|
23408
|
+
const file = join37(dist, RUN_SCHEMA_FILE);
|
|
23161
23409
|
let stamp;
|
|
23162
23410
|
try {
|
|
23163
23411
|
stamp = [dist, ...[pkg, file].map((f) => {
|
|
@@ -23171,8 +23419,8 @@ function runValidator(dirs = []) {
|
|
|
23171
23419
|
if (hit) return hit;
|
|
23172
23420
|
let v;
|
|
23173
23421
|
try {
|
|
23174
|
-
const version = JSON.parse(
|
|
23175
|
-
v = compileRunSchema(JSON.parse(
|
|
23422
|
+
const version = JSON.parse(readFileSync26(pkg, "utf8")).version ?? "";
|
|
23423
|
+
v = compileRunSchema(JSON.parse(readFileSync26(file, "utf8")), version);
|
|
23176
23424
|
} catch {
|
|
23177
23425
|
continue;
|
|
23178
23426
|
}
|
|
@@ -23198,7 +23446,10 @@ function runKey(row) {
|
|
|
23198
23446
|
var zero = () => ({ done: 0, "in-flight": 0, waiting: 0, "not-applied": 0 });
|
|
23199
23447
|
var MARK_ORDER = ["not-applied", "in-flight", "waiting", "done"];
|
|
23200
23448
|
function unsettled(run3) {
|
|
23201
|
-
|
|
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");
|
|
23202
23453
|
}
|
|
23203
23454
|
function joinProgress(run3, nodes, roots) {
|
|
23204
23455
|
const byRoot = /* @__PURE__ */ new Map();
|
|
@@ -23241,12 +23492,36 @@ function joinProgress(run3, nodes, roots) {
|
|
|
23241
23492
|
}
|
|
23242
23493
|
waves.push({ number: w.number, state: w.state, gate: w.gate, read: w.progress.read, counts, resources });
|
|
23243
23494
|
}
|
|
23244
|
-
|
|
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) };
|
|
23245
23520
|
}
|
|
23246
23521
|
var ProgressGraphError = class extends Error {
|
|
23247
23522
|
};
|
|
23248
23523
|
var FILES = "/api/terragucci/file?key=";
|
|
23249
|
-
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]);
|
|
23250
23525
|
var TerragucciProgress = class {
|
|
23251
23526
|
constructor(o) {
|
|
23252
23527
|
this.o = o;
|
|
@@ -23301,6 +23576,8 @@ var TerragucciProgress = class {
|
|
|
23301
23576
|
...this.base(project, v?.run.validation),
|
|
23302
23577
|
run: null,
|
|
23303
23578
|
absent: `index.json lists no tf-apply run of ${project} yet, so there is no run view to read.`,
|
|
23579
|
+
states: [],
|
|
23580
|
+
reads: [],
|
|
23304
23581
|
waves: [],
|
|
23305
23582
|
cards: {},
|
|
23306
23583
|
unmatched: [],
|
|
@@ -23337,6 +23614,8 @@ var TerragucciProgress = class {
|
|
|
23337
23614
|
run: { commit, key, present: false },
|
|
23338
23615
|
...unreadable ? { unreadable: true } : {},
|
|
23339
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: [],
|
|
23340
23619
|
waves: [],
|
|
23341
23620
|
cards: {},
|
|
23342
23621
|
unmatched: [],
|
|
@@ -23407,6 +23686,90 @@ function terragucciProgressRoute(app, progress) {
|
|
|
23407
23686
|
});
|
|
23408
23687
|
}
|
|
23409
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
|
+
|
|
23410
23773
|
// src/terragucci-view.ts
|
|
23411
23774
|
var TERRAGUCCI_READ_TTL_MS = 3e4;
|
|
23412
23775
|
function repoBase(dirs) {
|
|
@@ -23445,7 +23808,7 @@ function checkoutRoots(dirs, base = repoBase(dirs)) {
|
|
|
23445
23808
|
for (const m of estateMembers(dirs)) {
|
|
23446
23809
|
if (m.kind !== "terraform") continue;
|
|
23447
23810
|
for (const r of discoverTerraformRoots(m.dir).roots) {
|
|
23448
|
-
const path = relative8(base,
|
|
23811
|
+
const path = relative8(base, join39(m.dir, r.dir)).split(sep7).join("/") || ".";
|
|
23449
23812
|
out.push({ member: m.name, name: r.name, path });
|
|
23450
23813
|
}
|
|
23451
23814
|
}
|
|
@@ -23521,9 +23884,12 @@ function terragucciRoutes(app, opts, dirs) {
|
|
|
23521
23884
|
// Where a key opens: this server, which reads it from the same source.
|
|
23522
23885
|
files: "/api/terragucci/file?key=",
|
|
23523
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 } : {},
|
|
23524
23889
|
...marks
|
|
23525
23890
|
});
|
|
23526
23891
|
});
|
|
23892
|
+
terragucciDemoRoute(app, source2, opts.now ? { now: opts.now } : {});
|
|
23527
23893
|
terragucciTimelineRoute(app, source2, {
|
|
23528
23894
|
...opts.project ? { project: opts.project } : {},
|
|
23529
23895
|
checkoutProject: () => checkoutProject(repoBase(dirs())),
|
|
@@ -23588,7 +23954,7 @@ var heldByOf = (v) => {
|
|
|
23588
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;
|
|
23589
23955
|
};
|
|
23590
23956
|
var modeOf = (v) => v === "ledger" || v === "pr-review" || v === "sealed" ? v : void 0;
|
|
23591
|
-
var
|
|
23957
|
+
var str14 = (v) => typeof v === "string" && v !== "" ? v : void 0;
|
|
23592
23958
|
var strings3 = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : void 0;
|
|
23593
23959
|
var time3 = (iso) => Date.parse(iso) || 0;
|
|
23594
23960
|
function rowOf(line) {
|
|
@@ -23604,14 +23970,14 @@ function rowOf(line) {
|
|
|
23604
23970
|
}
|
|
23605
23971
|
function sealOf(r) {
|
|
23606
23972
|
const s = r.seal;
|
|
23607
|
-
const signed = s && s.signature ? { sealed: true, ...
|
|
23973
|
+
const signed = s && s.signature ? { sealed: true, ...str14(s.signer) ? { signer: str14(s.signer) } : {} } : void 0;
|
|
23608
23974
|
if (r.review || r.via === "pr-review") return { seal: "review", ...signed };
|
|
23609
23975
|
return signed ? { seal: "sealed", ...signed } : { seal: "unsigned" };
|
|
23610
23976
|
}
|
|
23611
23977
|
function reviewOf(r) {
|
|
23612
23978
|
const rv = r.review ?? void 0;
|
|
23613
23979
|
const pr = typeof rv?.pr === "number" ? rv.pr : typeof r.pr === "number" ? r.pr : void 0;
|
|
23614
|
-
const head =
|
|
23980
|
+
const head = str14(rv?.head) ?? str14(r.head);
|
|
23615
23981
|
const approvers = strings3(rv?.approvers) ?? strings3(r.reviewers);
|
|
23616
23982
|
if (pr === void 0 && head === void 0 && approvers === void 0) return void 0;
|
|
23617
23983
|
return { ...pr !== void 0 ? { pr } : {}, ...head ? { head } : {}, ...approvers ? { approvers } : {} };
|
|
@@ -23637,7 +24003,7 @@ function decisionOf(d) {
|
|
|
23637
24003
|
const r = v;
|
|
23638
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;
|
|
23639
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;
|
|
23640
|
-
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 } : {} };
|
|
23641
24007
|
}
|
|
23642
24008
|
function deriveGateAudit(input) {
|
|
23643
24009
|
const isOverride = input.isOverride ?? isOverrideGate2;
|
|
@@ -23662,17 +24028,17 @@ function deriveGateAudit(input) {
|
|
|
23662
24028
|
const requests = [];
|
|
23663
24029
|
for (const p of parsed) {
|
|
23664
24030
|
if (!p.pending || p.line.removed) continue;
|
|
23665
|
-
const digest =
|
|
24031
|
+
const digest = str14(p.r.planDigest);
|
|
23666
24032
|
denials.set(`${String(p.r.gate)}
|
|
23667
24033
|
${digest ?? ""}`, p.r);
|
|
23668
|
-
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) } : {} });
|
|
23669
24035
|
}
|
|
23670
24036
|
for (const { line, n, r, pending } of parsed) {
|
|
23671
24037
|
const gate = String(r.gate);
|
|
23672
24038
|
const override = isOverride(ledger.op, gate);
|
|
23673
24039
|
const removed = line.removed;
|
|
23674
24040
|
if (pending && removed) continue;
|
|
23675
|
-
const digest =
|
|
24041
|
+
const digest = str14(r.planDigest);
|
|
23676
24042
|
const author = removed ?? line.added;
|
|
23677
24043
|
const source2 = {
|
|
23678
24044
|
path,
|
|
@@ -23691,7 +24057,7 @@ ${digest ?? ""}`, p.r);
|
|
|
23691
24057
|
...base,
|
|
23692
24058
|
kind: override ? "override-requested" : "approval-requested",
|
|
23693
24059
|
at: String(r.timestamp),
|
|
23694
|
-
...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) }),
|
|
23695
24061
|
detail: extra2
|
|
23696
24062
|
}));
|
|
23697
24063
|
continue;
|
|
@@ -23719,13 +24085,13 @@ ${digest ?? ""}`) : void 0;
|
|
|
23719
24085
|
at: String(r.timestamp),
|
|
23720
24086
|
by: String(r.resolvedBy),
|
|
23721
24087
|
seal,
|
|
23722
|
-
...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 }),
|
|
23723
24089
|
detail: extra2
|
|
23724
24090
|
});
|
|
23725
24091
|
entries.push(entry);
|
|
23726
24092
|
if (!override) {
|
|
23727
24093
|
const at2 = time3(entry.at);
|
|
23728
|
-
const env =
|
|
24094
|
+
const env = str14(r.environment);
|
|
23729
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));
|
|
23730
24096
|
if (!asked) {
|
|
23731
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` });
|
|
@@ -24000,8 +24366,8 @@ function lifecycleHistoryRoute(app, base, opts = {}) {
|
|
|
24000
24366
|
}
|
|
24001
24367
|
|
|
24002
24368
|
// src/lifecycle-releases.ts
|
|
24003
|
-
import { existsSync as existsSync22, readFileSync as
|
|
24004
|
-
import { join as
|
|
24369
|
+
import { existsSync as existsSync22, readFileSync as readFileSync28 } from "node:fs";
|
|
24370
|
+
import { join as join40 } from "node:path";
|
|
24005
24371
|
|
|
24006
24372
|
// node_modules/@intentius/chant/src/lifecycle/legacy-digest.ts
|
|
24007
24373
|
import { resolve as resolve25 } from "node:path";
|
|
@@ -24114,10 +24480,10 @@ function memberNote(l) {
|
|
|
24114
24480
|
// src/lifecycle-releases.ts
|
|
24115
24481
|
var WAVES_SPEC_FILE = "yodel-waves.json";
|
|
24116
24482
|
function waveOrder(checkout) {
|
|
24117
|
-
const p =
|
|
24483
|
+
const p = join40(checkout, WAVES_SPEC_FILE);
|
|
24118
24484
|
if (!existsSync22(p)) return void 0;
|
|
24119
24485
|
try {
|
|
24120
|
-
const doc = JSON.parse(
|
|
24486
|
+
const doc = JSON.parse(readFileSync28(p, "utf8"));
|
|
24121
24487
|
const names = (doc.waves ?? []).map((w) => w?.name).filter((n) => typeof n === "string" && n.length > 0);
|
|
24122
24488
|
return names.length ? [...new Set(names)] : void 0;
|
|
24123
24489
|
} catch {
|
|
@@ -24125,7 +24491,7 @@ function waveOrder(checkout) {
|
|
|
24125
24491
|
}
|
|
24126
24492
|
}
|
|
24127
24493
|
function hasReleaseLedgerHint(checkout) {
|
|
24128
|
-
return existsSync22(
|
|
24494
|
+
return existsSync22(join40(checkout, "yodel.config.ts")) || existsSync22(join40(checkout, WAVES_SPEC_FILE));
|
|
24129
24495
|
}
|
|
24130
24496
|
function orderEnvironments(envs, waves) {
|
|
24131
24497
|
const seen = new Set(envs);
|
|
@@ -24167,7 +24533,7 @@ function releaseCells(ledgers, order, sources) {
|
|
|
24167
24533
|
}
|
|
24168
24534
|
async function copyCheckoutRef(git3, checkout, cache6) {
|
|
24169
24535
|
assertScratch(cache6.split(/[\\/]/).pop());
|
|
24170
|
-
if (!existsSync22(
|
|
24536
|
+
if (!existsSync22(join40(cache6, "HEAD"))) {
|
|
24171
24537
|
const init = await git3(["init", "--bare", "-q", cache6]);
|
|
24172
24538
|
if (init.code !== 0) return false;
|
|
24173
24539
|
}
|
|
@@ -24270,8 +24636,8 @@ function releasesRoutes(app, base, opts = {}) {
|
|
|
24270
24636
|
}
|
|
24271
24637
|
|
|
24272
24638
|
// src/sql-yodeler-lane.ts
|
|
24273
|
-
import { existsSync as existsSync23, readdirSync as readdirSync12, readFileSync as
|
|
24274
|
-
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";
|
|
24275
24641
|
var YODEL_APPLY_OP = "yodel-apply";
|
|
24276
24642
|
var YODEL_HEAD_PLANS_DIR = headPlansPath(YODEL_APPLY_OP, "").dir;
|
|
24277
24643
|
function parseYodelWaves(text, path = WAVES_SPEC_FILE) {
|
|
@@ -24303,16 +24669,16 @@ function parseYodelWaves(text, path = WAVES_SPEC_FILE) {
|
|
|
24303
24669
|
return { path, waves };
|
|
24304
24670
|
}
|
|
24305
24671
|
function readYodelWaves(checkout) {
|
|
24306
|
-
const p =
|
|
24672
|
+
const p = join41(checkout, WAVES_SPEC_FILE);
|
|
24307
24673
|
if (!existsSync23(p)) return void 0;
|
|
24308
24674
|
try {
|
|
24309
|
-
return parseYodelWaves(
|
|
24675
|
+
return parseYodelWaves(readFileSync29(p, "utf8"));
|
|
24310
24676
|
} catch (e) {
|
|
24311
24677
|
return { path: WAVES_SPEC_FILE, waves: [], error: e instanceof Error ? e.message : String(e) };
|
|
24312
24678
|
}
|
|
24313
24679
|
}
|
|
24314
24680
|
function migrateOpEnvironments(checkout) {
|
|
24315
|
-
const dir =
|
|
24681
|
+
const dir = join41(checkout, "ops");
|
|
24316
24682
|
if (!existsSync23(dir)) return [];
|
|
24317
24683
|
try {
|
|
24318
24684
|
return readdirSync12(dir).map((f) => /^migrate-(.+)\.op\.ts$/.exec(f)?.[1]).filter((e) => !!e).sort();
|
|
@@ -24435,16 +24801,16 @@ function sqlYodelerLaneRoute(app, checkout, opts = {}) {
|
|
|
24435
24801
|
|
|
24436
24802
|
// src/terragucci-estate.ts
|
|
24437
24803
|
init_yaml();
|
|
24438
|
-
import { existsSync as existsSync24, readFileSync as
|
|
24804
|
+
import { existsSync as existsSync24, readFileSync as readFileSync30, statSync as statSync13 } from "node:fs";
|
|
24439
24805
|
import { createRequire as createRequire7 } from "node:module";
|
|
24440
|
-
import { dirname as dirname14, extname, join as
|
|
24806
|
+
import { dirname as dirname14, extname, join as join42, resolve as resolve26 } from "node:path";
|
|
24441
24807
|
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
24442
24808
|
import { Ajv2020 as Ajv20204 } from "ajv/dist/2020.js";
|
|
24443
24809
|
var RUN_SCHEMA2 = "terragucci.run/v1";
|
|
24444
24810
|
function configProjects(file) {
|
|
24445
24811
|
let text;
|
|
24446
24812
|
try {
|
|
24447
|
-
text =
|
|
24813
|
+
text = readFileSync30(file, "utf8");
|
|
24448
24814
|
} catch {
|
|
24449
24815
|
return void 0;
|
|
24450
24816
|
}
|
|
@@ -24472,21 +24838,21 @@ function controlRepo(dirs, roots = () => checkoutRoots(dirs).length) {
|
|
|
24472
24838
|
}
|
|
24473
24839
|
return n === 0 ? { config, projects } : void 0;
|
|
24474
24840
|
}
|
|
24475
|
-
var
|
|
24476
|
-
var
|
|
24477
|
-
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);
|
|
24478
24844
|
function structuralRun2(doc) {
|
|
24479
|
-
if (!
|
|
24845
|
+
if (!isRecord18(doc)) return "it is not a JSON object";
|
|
24480
24846
|
if (doc.schema !== RUN_SCHEMA2) return `its schema is ${JSON.stringify(doc.schema)}, not ${RUN_SCHEMA2}`;
|
|
24481
|
-
if (!
|
|
24847
|
+
if (!str15(doc.project) || !str15(doc.commit) || !str15(doc.updated)) return "it lacks project, commit or updated";
|
|
24482
24848
|
if (!Array.isArray(doc.roots) || !Array.isArray(doc.waves)) return "it has no roots or waves array";
|
|
24483
24849
|
for (const [i, r] of doc.roots.entries()) {
|
|
24484
|
-
if (!
|
|
24485
|
-
if (r.state !== void 0 && !(
|
|
24486
|
-
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`;
|
|
24487
24853
|
}
|
|
24488
24854
|
for (const [i, w] of doc.waves.entries()) {
|
|
24489
|
-
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`;
|
|
24490
24856
|
}
|
|
24491
24857
|
return void 0;
|
|
24492
24858
|
}
|
|
@@ -24495,16 +24861,16 @@ function runViewValidator(dirs = []) {
|
|
|
24495
24861
|
for (const from of [...dirs, dirname14(fileURLToPath8(import.meta.url))]) {
|
|
24496
24862
|
let entry;
|
|
24497
24863
|
try {
|
|
24498
|
-
entry = createRequire7(
|
|
24864
|
+
entry = createRequire7(join42(resolve26(from), "noop.js")).resolve("@intentius/terragucci/report.schema.json");
|
|
24499
24865
|
} catch {
|
|
24500
24866
|
continue;
|
|
24501
24867
|
}
|
|
24502
24868
|
const dist = dirname14(entry);
|
|
24503
|
-
const schema =
|
|
24869
|
+
const schema = join42(dist, "run.schema.json");
|
|
24504
24870
|
let stamp;
|
|
24505
24871
|
try {
|
|
24506
24872
|
const st = statSync13(schema);
|
|
24507
|
-
const pkg = statSync13(
|
|
24873
|
+
const pkg = statSync13(join42(dist, "..", "package.json"));
|
|
24508
24874
|
stamp = `${schema}\0${st.mtimeMs}:${st.size}\0${pkg.mtimeMs}`;
|
|
24509
24875
|
} catch {
|
|
24510
24876
|
continue;
|
|
@@ -24513,12 +24879,12 @@ function runViewValidator(dirs = []) {
|
|
|
24513
24879
|
if (hit) return hit;
|
|
24514
24880
|
let v;
|
|
24515
24881
|
try {
|
|
24516
|
-
const version = JSON.parse(
|
|
24517
|
-
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")));
|
|
24518
24884
|
v = {
|
|
24519
24885
|
validation: { by: "schema", terragucci: version },
|
|
24520
24886
|
check(doc) {
|
|
24521
|
-
const id =
|
|
24887
|
+
const id = isRecord18(doc) ? doc.schema : void 0;
|
|
24522
24888
|
if (id !== RUN_SCHEMA2) return `its schema is ${JSON.stringify(id)}, not ${RUN_SCHEMA2}`;
|
|
24523
24889
|
if (validate(doc)) return void 0;
|
|
24524
24890
|
const e = validate.errors?.[0];
|
|
@@ -24569,8 +24935,8 @@ function controlEdges(views) {
|
|
|
24569
24935
|
function siblingCheckout(project, base, projectOf = checkoutProject) {
|
|
24570
24936
|
const name = project.split("/").filter(Boolean).pop();
|
|
24571
24937
|
if (!name) return void 0;
|
|
24572
|
-
const dir =
|
|
24573
|
-
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;
|
|
24574
24940
|
return projectOf(dir) === project ? dir : void 0;
|
|
24575
24941
|
}
|
|
24576
24942
|
var shellWord = (s) => /^[A-Za-z0-9_./:@%+=,-]+$/.test(s) ? s : `'${s.replace(/'/g, `'\\''`)}'`;
|
|
@@ -24777,9 +25143,9 @@ function controlRepoInfo(dirs) {
|
|
|
24777
25143
|
}
|
|
24778
25144
|
|
|
24779
25145
|
// src/yodel-report.ts
|
|
24780
|
-
import { readdirSync as readdirSync13, readFileSync as
|
|
25146
|
+
import { readdirSync as readdirSync13, readFileSync as readFileSync31, statSync as statSync14 } from "node:fs";
|
|
24781
25147
|
import { createRequire as createRequire8 } from "node:module";
|
|
24782
|
-
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";
|
|
24783
25149
|
import { fileURLToPath as fileURLToPath9 } from "node:url";
|
|
24784
25150
|
import { Ajv2020 as Ajv20205 } from "ajv/dist/2020.js";
|
|
24785
25151
|
|
|
@@ -25208,13 +25574,13 @@ var installed = /* @__PURE__ */ new Map();
|
|
|
25208
25574
|
function packageRoot(from) {
|
|
25209
25575
|
let entry;
|
|
25210
25576
|
try {
|
|
25211
|
-
entry = createRequire8(
|
|
25577
|
+
entry = createRequire8(join43(resolve27(from), "noop.js")).resolve("@intentius/sql-yodeler");
|
|
25212
25578
|
} catch {
|
|
25213
25579
|
return void 0;
|
|
25214
25580
|
}
|
|
25215
25581
|
for (let d = dirname15(entry); d !== dirname15(d); d = dirname15(d)) {
|
|
25216
25582
|
try {
|
|
25217
|
-
const pkg = JSON.parse(
|
|
25583
|
+
const pkg = JSON.parse(readFileSync31(join43(d, "package.json"), "utf8"));
|
|
25218
25584
|
if (pkg.name === "@intentius/sql-yodeler") return d;
|
|
25219
25585
|
} catch {
|
|
25220
25586
|
}
|
|
@@ -25226,11 +25592,11 @@ function yodelValidator(dirs = []) {
|
|
|
25226
25592
|
const root = packageRoot(from);
|
|
25227
25593
|
if (!root) continue;
|
|
25228
25594
|
try {
|
|
25229
|
-
const version = JSON.parse(
|
|
25595
|
+
const version = JSON.parse(readFileSync31(join43(root, "package.json"), "utf8")).version ?? "";
|
|
25230
25596
|
const key = `${root}\0${version}`;
|
|
25231
25597
|
let v = installed.get(key);
|
|
25232
25598
|
if (!v) {
|
|
25233
|
-
const load2 = (n) => JSON.parse(
|
|
25599
|
+
const load2 = (n) => JSON.parse(readFileSync31(join43(root, "schemas", `${n}.schema.json`), "utf8"));
|
|
25234
25600
|
v = compile({ common: load2("common"), report: load2("report"), drift: load2("drift") }, { by: "schema", from: "package", sqlYodeler: version });
|
|
25235
25601
|
installed.set(key, v);
|
|
25236
25602
|
}
|
|
@@ -25241,9 +25607,9 @@ function yodelValidator(dirs = []) {
|
|
|
25241
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) });
|
|
25242
25608
|
return pinned;
|
|
25243
25609
|
}
|
|
25244
|
-
var
|
|
25245
|
-
var looksLikeReport = (d) =>
|
|
25246
|
-
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);
|
|
25247
25613
|
function filesOf(source2) {
|
|
25248
25614
|
let st;
|
|
25249
25615
|
try {
|
|
@@ -25252,7 +25618,7 @@ function filesOf(source2) {
|
|
|
25252
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.");
|
|
25253
25619
|
}
|
|
25254
25620
|
const dir = st.isDirectory() ? source2 : dirname15(source2);
|
|
25255
|
-
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));
|
|
25256
25622
|
return st.isDirectory() ? { dir, files } : { dir, files, named: resolve27(source2) };
|
|
25257
25623
|
}
|
|
25258
25624
|
function readYodelReport(source2, validator2) {
|
|
@@ -25264,7 +25630,7 @@ function readYodelReport(source2, validator2) {
|
|
|
25264
25630
|
for (const file of files) {
|
|
25265
25631
|
let doc;
|
|
25266
25632
|
try {
|
|
25267
|
-
doc = JSON.parse(
|
|
25633
|
+
doc = JSON.parse(readFileSync31(file, "utf8"));
|
|
25268
25634
|
} catch (e) {
|
|
25269
25635
|
if (file === named) refuse14(`${basename4(file)} is not JSON: ${e instanceof Error ? e.message : String(e)}`, remedy);
|
|
25270
25636
|
ignored.push(basename4(file));
|
|
@@ -25351,24 +25717,24 @@ function yodelReportRoute(app, source2, dirs) {
|
|
|
25351
25717
|
|
|
25352
25718
|
// src/demo-pins.ts
|
|
25353
25719
|
init_terraform_member();
|
|
25354
|
-
import { existsSync as existsSync25, lstatSync as lstatSync2, mkdirSync as mkdirSync7, readFileSync as
|
|
25355
|
-
import { dirname as dirname16, join as
|
|
25356
|
-
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);
|
|
25357
25723
|
function linkDemoPins(work, probes = {}) {
|
|
25358
|
-
const file =
|
|
25724
|
+
const file = join44(work, "chant.workspace.json");
|
|
25359
25725
|
if (!existsSync25(file)) return { ok: true, linked: [] };
|
|
25360
25726
|
let decl;
|
|
25361
25727
|
try {
|
|
25362
|
-
decl = JSON.parse(
|
|
25728
|
+
decl = JSON.parse(readFileSync32(file, "utf8"));
|
|
25363
25729
|
} catch {
|
|
25364
25730
|
return { ok: true, linked: [] };
|
|
25365
25731
|
}
|
|
25366
|
-
if (!
|
|
25732
|
+
if (!isRecord20(decl) || !Array.isArray(decl.pins)) return { ok: true, linked: [] };
|
|
25367
25733
|
const find = probes.resolve ?? ((pkg) => resolvedPackage(pkg));
|
|
25368
25734
|
const linked = [];
|
|
25369
25735
|
let rewrite = false;
|
|
25370
25736
|
for (const pin of decl.pins) {
|
|
25371
|
-
if (!
|
|
25737
|
+
if (!isRecord20(pin) || typeof pin.package !== "string") continue;
|
|
25372
25738
|
const pkg = pin.package;
|
|
25373
25739
|
if (pkg === TERRAFORM_LEXICON_PKG) {
|
|
25374
25740
|
const refusal2 = (probes.reader ?? terraformReaderState)().refusal;
|
|
@@ -25382,7 +25748,7 @@ function linkDemoPins(work, probes = {}) {
|
|
|
25382
25748
|
remedy: `Install ${pkg} beside behold, then load the demo again.`
|
|
25383
25749
|
};
|
|
25384
25750
|
}
|
|
25385
|
-
const link =
|
|
25751
|
+
const link = join44(work, "node_modules", ...pkg.split("/"));
|
|
25386
25752
|
if (!present(link)) {
|
|
25387
25753
|
mkdirSync7(dirname16(link), { recursive: true });
|
|
25388
25754
|
symlinkSync2(found.dir, link, "junction");
|
|
@@ -25417,13 +25783,13 @@ function linksTo(link, target) {
|
|
|
25417
25783
|
}
|
|
25418
25784
|
|
|
25419
25785
|
// src/demos.ts
|
|
25420
|
-
import { readFileSync as
|
|
25421
|
-
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";
|
|
25422
25788
|
import { spawn as spawn8, spawnSync as spawnSync3 } from "node:child_process";
|
|
25423
25789
|
function readCatalog(file, catalog) {
|
|
25424
25790
|
let raw;
|
|
25425
25791
|
try {
|
|
25426
|
-
raw = JSON.parse(
|
|
25792
|
+
raw = JSON.parse(readFileSync33(file, "utf8"));
|
|
25427
25793
|
} catch {
|
|
25428
25794
|
return [];
|
|
25429
25795
|
}
|
|
@@ -25463,10 +25829,10 @@ function validEntry(e) {
|
|
|
25463
25829
|
return true;
|
|
25464
25830
|
}
|
|
25465
25831
|
function loadDemoRegistry(pkgRoot3) {
|
|
25466
|
-
const merged = readCatalog(
|
|
25832
|
+
const merged = readCatalog(join45(pkgRoot3, "demos.json"), "demos");
|
|
25467
25833
|
const seen = new Set(merged.map((e) => e.name));
|
|
25468
25834
|
const named = process.env.BEHOLD_WORKBENCH;
|
|
25469
|
-
for (const e of [...readCatalog(
|
|
25835
|
+
for (const e of [...readCatalog(join45(pkgRoot3, "workbench.json"), "workbench"), ...named ? readCatalog(resolve29(named), "workbench") : []]) {
|
|
25470
25836
|
if (seen.has(e.name)) {
|
|
25471
25837
|
process.stderr.write(`behold: workbench entry "${e.name}" collides with a demo already in the catalog \u2014 dropped
|
|
25472
25838
|
`);
|
|
@@ -25513,7 +25879,7 @@ async function loadDemo(entry, opts) {
|
|
|
25513
25879
|
say(`serving ${work} where it is \u2014 nothing copied`);
|
|
25514
25880
|
} else if (!existsSync26(target)) {
|
|
25515
25881
|
if (entry.source === "bundled" || entry.source === "local" && local) {
|
|
25516
|
-
const from = entry.source === "bundled" ?
|
|
25882
|
+
const from = entry.source === "bundled" ? join45(pkgRoot3, entry.dir) : local;
|
|
25517
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})` };
|
|
25518
25884
|
say(`copying to ${target} (it's yours \u2014 edit it)`);
|
|
25519
25885
|
try {
|
|
@@ -25545,7 +25911,7 @@ async function loadDemo(entry, opts) {
|
|
|
25545
25911
|
if (!pins.ok) return { ok: false, error: `${pins.error} ${pins.remedy}` };
|
|
25546
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})` : ""}`);
|
|
25547
25913
|
}
|
|
25548
|
-
if (!inPlace && existsSync26(
|
|
25914
|
+
if (!inPlace && existsSync26(join45(work, "package.json")) && !existsSync26(join45(work, "node_modules"))) {
|
|
25549
25915
|
say("npm install\u2026");
|
|
25550
25916
|
if (await runStep("npm", ["install"], { cwd: work, shell: process.platform === "win32" }) !== 0) {
|
|
25551
25917
|
return { ok: false, error: `npm install failed in ${work}` };
|
|
@@ -25562,7 +25928,7 @@ async function loadDemo(entry, opts) {
|
|
|
25562
25928
|
return { ok: false, error: `setup failed (${entry.setup})` };
|
|
25563
25929
|
}
|
|
25564
25930
|
}
|
|
25565
|
-
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] };
|
|
25566
25932
|
}
|
|
25567
25933
|
function runStep(cmd, args, opts = {}) {
|
|
25568
25934
|
return new Promise((res) => {
|
|
@@ -25573,8 +25939,8 @@ function runStep(cmd, args, opts = {}) {
|
|
|
25573
25939
|
}
|
|
25574
25940
|
|
|
25575
25941
|
// src/server.ts
|
|
25576
|
-
var pkgRoot =
|
|
25577
|
-
var webRoot =
|
|
25942
|
+
var pkgRoot = join46(dirname18(fileURLToPath10(import.meta.url)), "..");
|
|
25943
|
+
var webRoot = join46(pkgRoot, "web");
|
|
25578
25944
|
var execFileP = async (cmd, args) => (await promisify3(execFile5)(cmd, args, { encoding: "utf8", timeout: 1e4 })).stdout;
|
|
25579
25945
|
function optsFromQuery(url, tierEnvVar, projectDir) {
|
|
25580
25946
|
const q = url.searchParams;
|
|
@@ -25625,7 +25991,7 @@ function tierFailure(tier, message2) {
|
|
|
25625
25991
|
function beholdVersion() {
|
|
25626
25992
|
try {
|
|
25627
25993
|
const here = dirname18(fileURLToPath10(import.meta.url));
|
|
25628
|
-
return JSON.parse(
|
|
25994
|
+
return JSON.parse(readFileSync34(join46(here, "..", "package.json"), "utf8")).version ?? "unknown";
|
|
25629
25995
|
} catch {
|
|
25630
25996
|
return "unknown";
|
|
25631
25997
|
}
|
|
@@ -25672,7 +26038,7 @@ async function captureFrame(projectDir, env, frames, broadcaster) {
|
|
|
25672
26038
|
}
|
|
25673
26039
|
}
|
|
25674
26040
|
function carveRoutes(app, reportPath, demo) {
|
|
25675
|
-
const load2 = () => readCarveReport(reportPath, (p) =>
|
|
26041
|
+
const load2 = () => readCarveReport(reportPath, (p) => readFileSync34(p, "utf8"));
|
|
25676
26042
|
const carveDirs = [dirname18(reportPath), ...demo ? [demo.out] : []];
|
|
25677
26043
|
const carveStates = () => readCarveStates(carveDirs, nodeCarveIo, 0);
|
|
25678
26044
|
const emittedNodes = async (states) => {
|
|
@@ -25908,13 +26274,18 @@ function cardPrefixesOf(ir) {
|
|
|
25908
26274
|
const byStack = ir.groups.byStack;
|
|
25909
26275
|
return byStack && Object.keys(byStack).length ? byStack : void 0;
|
|
25910
26276
|
}
|
|
25911
|
-
function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffer(),
|
|
25912
|
-
|
|
25913
|
-
|
|
25914
|
-
|
|
25915
|
-
|
|
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
|
+
});
|
|
25916
26283
|
const app = new Hono();
|
|
25917
|
-
const guard = {
|
|
26284
|
+
const guard = {
|
|
26285
|
+
allowedHosts: allowedHostsFrom(cfg.allowedHosts ?? []),
|
|
26286
|
+
...cfg.host ? { boundHost: cfg.host } : {},
|
|
26287
|
+
...cfg.public ? { public: true } : {}
|
|
26288
|
+
};
|
|
25918
26289
|
app.use("*", async (c, next) => {
|
|
25919
26290
|
const verdict3 = guardRequest(
|
|
25920
26291
|
{
|
|
@@ -25925,9 +26296,13 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
|
|
|
25925
26296
|
},
|
|
25926
26297
|
guard
|
|
25927
26298
|
);
|
|
25928
|
-
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
|
+
}
|
|
25929
26303
|
await next();
|
|
25930
26304
|
});
|
|
26305
|
+
if (cfg.public) publicRoutes(app, webRoot);
|
|
25931
26306
|
setChoudoufuRunner(cfg.choudoufu?.run);
|
|
25932
26307
|
setEstateLexiconRead(cfg.choudoufu ? cfg.choudoufu.lexicon ?? null : void 0);
|
|
25933
26308
|
setServedWorkspace(cfg.workspace);
|
|
@@ -25985,6 +26360,7 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
|
|
|
25985
26360
|
};
|
|
25986
26361
|
const estateSourceRoots = async (opts) => (await Promise.all((cfg.projectDirs ?? []).map(async (dir) => [await graphPath(dir, opts), dir]))).flat();
|
|
25987
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);
|
|
25988
26364
|
app.get("/api/frames", (c) => c.json({ frames: frames.summaries() }));
|
|
25989
26365
|
app.get("/lanes", (c) => {
|
|
25990
26366
|
const all = frames.all();
|
|
@@ -26068,8 +26444,8 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
|
|
|
26068
26444
|
return c.json({ started: true, name, ran: label });
|
|
26069
26445
|
});
|
|
26070
26446
|
app.post("/api/local/reset", (c) => {
|
|
26071
|
-
const down =
|
|
26072
|
-
const up =
|
|
26447
|
+
const down = join46(cfg.projectDir, "scripts/local/local-down.sh");
|
|
26448
|
+
const up = join46(cfg.projectDir, "scripts/local/local-up.sh");
|
|
26073
26449
|
if (!existsSync27(down) || !existsSync27(up)) {
|
|
26074
26450
|
return c.json({ error: "no local-down.sh / local-up.sh in scripts/local \u2014 reset is only for local emulator projects" }, 400);
|
|
26075
26451
|
}
|
|
@@ -26091,7 +26467,7 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
|
|
|
26091
26467
|
const dirs = [];
|
|
26092
26468
|
const declared = [];
|
|
26093
26469
|
for (const f of discoverOpIrs(estateDirs)) {
|
|
26094
|
-
const parsed = readOpIr(f.path, (p) =>
|
|
26470
|
+
const parsed = readOpIr(f.path, (p) => readFileSync34(p, "utf8"));
|
|
26095
26471
|
if (!parsed.ok) continue;
|
|
26096
26472
|
const mine = declaredConvergeOps([parsed.ir]);
|
|
26097
26473
|
if (!mine.length) continue;
|
|
@@ -26271,7 +26647,7 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
|
|
|
26271
26647
|
});
|
|
26272
26648
|
app.get("/api/project", async (c) => {
|
|
26273
26649
|
const { environments: declaredEnvs, lexicons, stacks, k8sProfiles } = await detectProject(cfg.projectDir);
|
|
26274
|
-
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;
|
|
26275
26651
|
const k8sTarget = lexicons.includes("k8s") ? resolveK8sTarget(k8sProfiles, cfg.env, await loadKubeconfig()) : void 0;
|
|
26276
26652
|
const axes = deployAxes(tierEnvVar, lexicons, k8sTarget);
|
|
26277
26653
|
const emittedOps = discoverOpIrs(estateDirs).length;
|
|
@@ -26305,6 +26681,8 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
|
|
|
26305
26681
|
currentEnv: cfg.env ?? null,
|
|
26306
26682
|
// v0.1.0 preview: the SPA hides git/PR ops + arbitrary-project affordances.
|
|
26307
26683
|
...cfg.previewMode ? { previewMode: true } : {},
|
|
26684
|
+
// #569: a public view; the SPA hides every control that writes.
|
|
26685
|
+
...cfg.public ? { publicMode: true } : {},
|
|
26308
26686
|
// #489: a terragucci repo is applied by its pipeline; the SPA offers no
|
|
26309
26687
|
// write at all, and the write routes refuse with the same words.
|
|
26310
26688
|
...tgConfig() ? { terragucci: { config: tgConfig(), ...terragucciRefusal(tgConfig(), "deploying from behold") } } : {},
|
|
@@ -26369,7 +26747,8 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
|
|
|
26369
26747
|
// true is a flag the SPA can read as "absent means no".
|
|
26370
26748
|
memberKinds,
|
|
26371
26749
|
...runtimeCapable ? { runtimeCapable: true } : {},
|
|
26372
|
-
|
|
26750
|
+
// #569: a public view offers no target: `?target=` is refused there.
|
|
26751
|
+
targets: cfg.public ? [] : deployTargets(lexicons, k8sTarget),
|
|
26373
26752
|
// Where the k8s binding came from, so the SPA never implies behold chose
|
|
26374
26753
|
// it (#106). Absent for a project with no k8s lexicon or no resolvable
|
|
26375
26754
|
// context.
|
|
@@ -26382,7 +26761,7 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
|
|
|
26382
26761
|
const body = await c.req.json().catch(() => ({}));
|
|
26383
26762
|
const dir = typeof body.dir === "string" && body.dir.trim() ? resolve30(body.dir.trim()) : "";
|
|
26384
26763
|
if (!dir || !existsSync27(dir)) return c.json({ error: `no such directory: ${dir || "(no dir given)"}` }, 400);
|
|
26385
|
-
if (!existsSync27(
|
|
26764
|
+
if (!existsSync27(join46(dir, "chant.config.ts"))) {
|
|
26386
26765
|
return c.json({ error: `${dir} doesn't look like a chant project \u2014 no chant.config.ts` }, 400);
|
|
26387
26766
|
}
|
|
26388
26767
|
switchServedProject([dir]);
|
|
@@ -26495,6 +26874,7 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
|
|
|
26495
26874
|
const layoutDir = () => cfg.workspace?.root ?? cfg.projectDir;
|
|
26496
26875
|
const layoutWriteBlock = () => {
|
|
26497
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";
|
|
26498
26878
|
if (cfg.carveReport) return "a carve report isn't a project \u2014 there's nowhere to keep a hand layout";
|
|
26499
26879
|
if (cfg.layoutWrites === false) return "a static export captures a snapshot \u2014 it doesn't write to the project";
|
|
26500
26880
|
return unwritableReason(layoutDir());
|
|
@@ -26685,7 +27065,7 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
|
|
|
26685
27065
|
}
|
|
26686
27066
|
const parsedOps = [];
|
|
26687
27067
|
for (const f of files) {
|
|
26688
|
-
const parsed = readOpIr(f.path, (p) =>
|
|
27068
|
+
const parsed = readOpIr(f.path, (p) => readFileSync34(p, "utf8"));
|
|
26689
27069
|
if (!parsed.ok) return c.json(parsed.refusal, 422);
|
|
26690
27070
|
parsedOps.push(parsed.ir);
|
|
26691
27071
|
}
|
|
@@ -26698,8 +27078,8 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
|
|
|
26698
27078
|
}
|
|
26699
27079
|
const runState = runner.runState;
|
|
26700
27080
|
const ranOp = runState.op ? parsedOps.find((o) => o.name === runState.op) : void 0;
|
|
26701
|
-
const
|
|
26702
|
-
const opsIr =
|
|
27081
|
+
const join51 = ranOp ? joinRun(ranOp, runState.records, runState.gate) : null;
|
|
27082
|
+
const opsIr = join51 ? paintPlayhead(declaredIr, join51, runState.gate) : declaredIr;
|
|
26703
27083
|
const { svg: svg2 } = renderGraph(opsIr, { boxes: "byStack" });
|
|
26704
27084
|
const operator = runner.noteDeclaredConvergeOps(declaredConvergeOps(parsedOps));
|
|
26705
27085
|
return c.json({
|
|
@@ -26712,7 +27092,7 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
|
|
|
26712
27092
|
target: null,
|
|
26713
27093
|
mode: "ops",
|
|
26714
27094
|
ops: parsedOps.length,
|
|
26715
|
-
note: `${opsNote(parsedOps, opsIr)} ${playheadNote(runState,
|
|
27095
|
+
note: `${opsNote(parsedOps, opsIr)} ${playheadNote(runState, join51)} ${operatorNote(operator)}`,
|
|
26716
27096
|
// The operator strip's own model: the ConvergeOps this project
|
|
26717
27097
|
// declares, plus whatever the last status read said. `declared`
|
|
26718
27098
|
// without a `strip` is the honest starting state — a loop exists and
|
|
@@ -26823,7 +27203,7 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
|
|
|
26823
27203
|
srcCompositeEdgesAttached = 0;
|
|
26824
27204
|
}
|
|
26825
27205
|
}
|
|
26826
|
-
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"))) {
|
|
26827
27207
|
return c.json(noProjectError(cfg.projectDir), 404);
|
|
26828
27208
|
}
|
|
26829
27209
|
const radial = new URL(c.req.url).searchParams.get("radial") === "1";
|
|
@@ -27460,7 +27840,7 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
|
|
|
27460
27840
|
if (!c.req.path.startsWith("/api/") && c.res.ok && !c.res.headers.has("cache-control")) c.res.headers.set("cache-control", "no-cache");
|
|
27461
27841
|
});
|
|
27462
27842
|
app.use("/*", serveStatic({ root: rel }));
|
|
27463
|
-
app.get("/", serveStatic({ path:
|
|
27843
|
+
app.get("/", serveStatic({ path: join46(rel, "index.html") }));
|
|
27464
27844
|
return app;
|
|
27465
27845
|
}
|
|
27466
27846
|
async function bootLocalEmulators(dir, who) {
|
|
@@ -27489,11 +27869,11 @@ async function bootLocalEmulators(dir, who) {
|
|
|
27489
27869
|
async function startServer(cfg) {
|
|
27490
27870
|
if (cfg.local) cfg.emulators = await bootLocalEmulators(cfg.projectDir, "behold serve --local");
|
|
27491
27871
|
for (const dir of cfg.projectDirs ?? [cfg.projectDir]) {
|
|
27492
|
-
if (existsSync27(
|
|
27872
|
+
if (existsSync27(join46(dir, "chant.config.ts"))) addRecent(dir);
|
|
27493
27873
|
}
|
|
27494
27874
|
const broadcaster = new Broadcaster();
|
|
27495
27875
|
const frames = new FrameBuffer();
|
|
27496
|
-
const runner = new OpRunner({
|
|
27876
|
+
const runner = cfg.public ? new ReadOnlyRunner() : new OpRunner({
|
|
27497
27877
|
projectDir: cfg.projectDir,
|
|
27498
27878
|
broadcaster,
|
|
27499
27879
|
onDone: (opEnv) => captureFrame(cfg.projectDir, opEnv ?? cfg.env, frames, broadcaster)
|
|
@@ -27501,7 +27881,7 @@ async function startServer(cfg) {
|
|
|
27501
27881
|
if (!cfg.host && process.env.BEHOLD_HOST) cfg.host = process.env.BEHOLD_HOST;
|
|
27502
27882
|
const app = createApp(cfg, broadcaster, frames, runner);
|
|
27503
27883
|
const autoSync = cfg.autoSync ?? "off";
|
|
27504
|
-
if (!cfg.carveReport && !servedProductRepo(cfg.projectDirs ?? [cfg.projectDir])) {
|
|
27884
|
+
if (!cfg.public && !cfg.carveReport && !servedProductRepo(cfg.projectDirs ?? [cfg.projectDir])) {
|
|
27505
27885
|
void readoptDispatchedRun(cfg, runner).then((result) => {
|
|
27506
27886
|
if (result.outcome === "readopted") {
|
|
27507
27887
|
process.stdout.write(` re-adopted dispatched run ${result.run.runId} (${result.run.workflow}) \u2014 following via gh
|
|
@@ -27525,7 +27905,7 @@ async function startServer(cfg) {
|
|
|
27525
27905
|
const memberTag = (dir) => (cfg.projectDirs?.length ?? 0) > 1 ? ` [${basename5(dir)}]` : "";
|
|
27526
27906
|
const onPollDrift = (dir, movedLexicons) => {
|
|
27527
27907
|
broadcaster.emit("changed", dir);
|
|
27528
|
-
if (autoSync === "off") return;
|
|
27908
|
+
if (autoSync === "off" || cfg.public) return;
|
|
27529
27909
|
const guarded = servedProductRepo(cfg.projectDirs ?? [cfg.projectDir]);
|
|
27530
27910
|
if (guarded) {
|
|
27531
27911
|
broadcaster.emit("op", `\u27F3 auto-sync (${autoSync})${memberTag(dir)} declined: ${productRefusal(guarded, "auto-sync").error}`);
|
|
@@ -27634,7 +28014,8 @@ async function startServer(cfg) {
|
|
|
27634
28014
|
`behold \u2192 http://localhost:${info.port}
|
|
27635
28015
|
project: ${cfg.projectDir}${cfg.env ? ` env: ${cfg.env}` : ""}${auto}${localTag}
|
|
27636
28016
|
read-only, watching for edits${poll}. lanes: /lanes. Ctrl-C to stop.
|
|
27637
|
-
`
|
|
28017
|
+
` + (cfg.public ? ` public: GET, HEAD and OPTIONS only; no op runner; answers ${["localhost", ...allowedHostsFrom(cfg.allowedHosts ?? [])].join(", ")}.
|
|
28018
|
+
` : "")
|
|
27638
28019
|
);
|
|
27639
28020
|
void detectProject(cfg.projectDir).then(({ environments, lexicons }) => {
|
|
27640
28021
|
const envs = environments.length ? environments.join(", ") : "(none declared \u2014 env picker shows only source)";
|
|
@@ -27662,8 +28043,8 @@ init_chant();
|
|
|
27662
28043
|
|
|
27663
28044
|
// src/workspace-convert.ts
|
|
27664
28045
|
init_chant();
|
|
27665
|
-
import { existsSync as existsSync28, mkdirSync as mkdirSync9, readFileSync as
|
|
27666
|
-
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";
|
|
27667
28048
|
import { shortStackNames as shortStackNames2 } from "@intentius/pinhole";
|
|
27668
28049
|
init_project();
|
|
27669
28050
|
init_terraform_member();
|
|
@@ -27681,10 +28062,10 @@ function unique(name, taken) {
|
|
|
27681
28062
|
var posix4 = (p) => p.split(sep10).join("/");
|
|
27682
28063
|
function installedVersion(pkg, dir) {
|
|
27683
28064
|
for (let at2 = resolve31(dir); ; at2 = dirname19(at2)) {
|
|
27684
|
-
const manifest =
|
|
28065
|
+
const manifest = join47(at2, "node_modules", ...pkg.split("/"), "package.json");
|
|
27685
28066
|
if (existsSync28(manifest)) {
|
|
27686
28067
|
try {
|
|
27687
|
-
const v = JSON.parse(
|
|
28068
|
+
const v = JSON.parse(readFileSync35(manifest, "utf8")).version;
|
|
27688
28069
|
return typeof v === "string" ? v : void 0;
|
|
27689
28070
|
} catch {
|
|
27690
28071
|
return void 0;
|
|
@@ -27695,7 +28076,7 @@ function installedVersion(pkg, dir) {
|
|
|
27695
28076
|
}
|
|
27696
28077
|
function planConversion(rootArg) {
|
|
27697
28078
|
const root = resolve31(rootArg);
|
|
27698
|
-
const existing = DECLARATION_FILES.find((f) => existsSync28(
|
|
28079
|
+
const existing = DECLARATION_FILES.find((f) => existsSync28(join47(root, f)));
|
|
27699
28080
|
if (existing) return { ok: false, error: `${root} already has ${existing}; there is nothing to convert.` };
|
|
27700
28081
|
const shape = detectProjectShape(root);
|
|
27701
28082
|
if (shape.kind !== "estate" || !shape.members?.length || shape.membersFrom === "itself") {
|
|
@@ -27716,10 +28097,10 @@ function planConversion(rootArg) {
|
|
|
27716
28097
|
const scan = discoverTerraformRoots(abs[i]);
|
|
27717
28098
|
for (const r of scan.roots) {
|
|
27718
28099
|
const name2 = unique(declarationName(r.name), taken);
|
|
27719
|
-
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" });
|
|
27720
28101
|
moves.push({ from: `${short}/${r.name}/`, to: `${name2}/${name2}/` });
|
|
27721
28102
|
}
|
|
27722
|
-
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}.`);
|
|
27723
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.`);
|
|
27724
28105
|
return;
|
|
27725
28106
|
}
|
|
@@ -27762,7 +28143,7 @@ function moveLayoutIds(file, moves) {
|
|
|
27762
28143
|
return { version: 1, lenses };
|
|
27763
28144
|
}
|
|
27764
28145
|
function applyConversion(plan) {
|
|
27765
|
-
const declarationFile =
|
|
28146
|
+
const declarationFile = join47(plan.root, DECLARATION_FILES[0]);
|
|
27766
28147
|
if (existsSync28(declarationFile)) throw new Error(`${declarationFile} appeared while converting; nothing was written`);
|
|
27767
28148
|
writeFileSync7(declarationFile, `${JSON.stringify(plan.declaration, null, 2)}
|
|
27768
28149
|
`);
|
|
@@ -27773,7 +28154,7 @@ function applyConversion(plan) {
|
|
|
27773
28154
|
else if (existsSync28(target)) layout = { skipped: `${target} already exists; the layout in ${plan.layoutFrom} was left alone` };
|
|
27774
28155
|
else {
|
|
27775
28156
|
const moved = moveLayoutIds(readLayoutFile(dirname19(dirname19(plan.layoutFrom))), plan.moves);
|
|
27776
|
-
mkdirSync9(
|
|
28157
|
+
mkdirSync9(join47(plan.root, LAYOUT_DIR), { recursive: true });
|
|
27777
28158
|
const tmp = `${target}.tmp`;
|
|
27778
28159
|
writeFileSync7(tmp, `${JSON.stringify(moved, null, 2)}
|
|
27779
28160
|
`);
|
|
@@ -27784,9 +28165,9 @@ function applyConversion(plan) {
|
|
|
27784
28165
|
}
|
|
27785
28166
|
|
|
27786
28167
|
// src/export.ts
|
|
27787
|
-
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";
|
|
27788
28169
|
import { homedir as homedir3 } from "node:os";
|
|
27789
|
-
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";
|
|
27790
28171
|
import { createHash as createHash11 } from "node:crypto";
|
|
27791
28172
|
import { fileURLToPath as fileURLToPath11 } from "node:url";
|
|
27792
28173
|
var LENS_PARAMS = ["collapse", "components", "detail", "env", "logical", "ops", "radial", "stacks", "tier"];
|
|
@@ -27843,24 +28224,24 @@ function captureKeys(axes) {
|
|
|
27843
28224
|
return [...keys];
|
|
27844
28225
|
}
|
|
27845
28226
|
function webDir() {
|
|
27846
|
-
return
|
|
28227
|
+
return join48(dirname20(fileURLToPath11(import.meta.url)), "..", "web");
|
|
27847
28228
|
}
|
|
27848
28229
|
function pkgRoot2() {
|
|
27849
|
-
return
|
|
28230
|
+
return join48(dirname20(fileURLToPath11(import.meta.url)), "..");
|
|
27850
28231
|
}
|
|
27851
28232
|
var NOTICE_FILES = ["LICENSE", "THIRD_PARTY.md"];
|
|
27852
28233
|
function copyNotices(outDir, root = pkgRoot2()) {
|
|
27853
28234
|
const copied = [];
|
|
27854
28235
|
for (const f of NOTICE_FILES) {
|
|
27855
|
-
if (!existsSync29(
|
|
27856
|
-
copyFileSync(
|
|
28236
|
+
if (!existsSync29(join48(root, f))) continue;
|
|
28237
|
+
copyFileSync(join48(root, f), join48(outDir, f));
|
|
27857
28238
|
copied.push(f);
|
|
27858
28239
|
}
|
|
27859
|
-
const lic =
|
|
28240
|
+
const lic = join48(root, "licenses");
|
|
27860
28241
|
if (existsSync29(lic)) {
|
|
27861
|
-
mkdirSync10(
|
|
28242
|
+
mkdirSync10(join48(outDir, "licenses"), { recursive: true });
|
|
27862
28243
|
for (const f of readdirSync14(lic)) {
|
|
27863
|
-
copyFileSync(
|
|
28244
|
+
copyFileSync(join48(lic, f), join48(outDir, "licenses", f));
|
|
27864
28245
|
copied.push(`licenses/${f}`);
|
|
27865
28246
|
}
|
|
27866
28247
|
}
|
|
@@ -27885,7 +28266,7 @@ async function runExport(cfg, outDir, opts = {}) {
|
|
|
27885
28266
|
...proj.sqlYodeler ? { sqlYodeler: true } : {}
|
|
27886
28267
|
};
|
|
27887
28268
|
const scrub = exportScrubber(cfg);
|
|
27888
|
-
const snapDir =
|
|
28269
|
+
const snapDir = join48(outDir, "snapshots");
|
|
27889
28270
|
mkdirSync10(snapDir, { recursive: true });
|
|
27890
28271
|
const keyToFile = {};
|
|
27891
28272
|
let ok = 0;
|
|
@@ -27894,7 +28275,7 @@ async function runExport(cfg, outDir, opts = {}) {
|
|
|
27894
28275
|
const res = await app.request(`${key}${key.includes("?") ? "&" : "?"}layout=1`);
|
|
27895
28276
|
const body = scrub(shapeSnapshot(key, await res.text(), opts));
|
|
27896
28277
|
const file = slug3(key, body);
|
|
27897
|
-
writeFileSync8(
|
|
28278
|
+
writeFileSync8(join48(snapDir, file), body);
|
|
27898
28279
|
keyToFile[key] = `snapshots/${file}`;
|
|
27899
28280
|
if (res.ok) ok++;
|
|
27900
28281
|
else failed++;
|
|
@@ -27916,29 +28297,29 @@ async function runExport(cfg, outDir, opts = {}) {
|
|
|
27916
28297
|
axes,
|
|
27917
28298
|
keyToFile
|
|
27918
28299
|
};
|
|
27919
|
-
writeFileSync8(
|
|
27920
|
-
const html =
|
|
28300
|
+
writeFileSync8(join48(outDir, "manifest.json"), JSON.stringify(manifest, null, 2));
|
|
28301
|
+
const html = readFileSync36(join48(webDir(), "index.html"), "utf8").replace(
|
|
27921
28302
|
/<\/head>/i,
|
|
27922
28303
|
` <script>window.__BEHOLD_STATIC__ = true;</script>
|
|
27923
28304
|
</head>`
|
|
27924
28305
|
);
|
|
27925
|
-
writeFileSync8(
|
|
28306
|
+
writeFileSync8(join48(outDir, "index.html"), html);
|
|
27926
28307
|
for (const f of readdirSync14(webDir())) {
|
|
27927
28308
|
if (f === "index.html" || f.endsWith(".test.js")) continue;
|
|
27928
|
-
cpSync2(
|
|
28309
|
+
cpSync2(join48(webDir(), f), join48(outDir, f), { recursive: true });
|
|
27929
28310
|
}
|
|
27930
|
-
writeFileSync8(
|
|
28311
|
+
writeFileSync8(join48(outDir, "README.md"), BUNDLE_README);
|
|
27931
28312
|
copyNotices(outDir);
|
|
27932
28313
|
const name = workerName(cfg.projectDir, opts.name);
|
|
27933
28314
|
writeFileSync8(
|
|
27934
|
-
|
|
28315
|
+
join48(outDir, "wrangler.jsonc"),
|
|
27935
28316
|
JSON.stringify(
|
|
27936
28317
|
{ $schema: "node_modules/wrangler/config-schema.json", name, compatibility_date: "2025-06-01", assets: { directory: "." } },
|
|
27937
28318
|
null,
|
|
27938
28319
|
2
|
|
27939
28320
|
) + "\n"
|
|
27940
28321
|
);
|
|
27941
|
-
writeFileSync8(
|
|
28322
|
+
writeFileSync8(join48(outDir, "manifest.json"), JSON.stringify({ ...manifest, files: bundleFiles(outDir).filter((f) => f !== "manifest.json") }, null, 2));
|
|
27942
28323
|
process.stdout.write(
|
|
27943
28324
|
`behold export \u2192 ${outDir}
|
|
27944
28325
|
${ok} snapshots${failed ? ` (${failed} endpoint error(s) captured as-is)` : ""}
|
|
@@ -27947,11 +28328,11 @@ async function runExport(cfg, outDir, opts = {}) {
|
|
|
27947
28328
|
`
|
|
27948
28329
|
);
|
|
27949
28330
|
}
|
|
27950
|
-
var
|
|
28331
|
+
var isRecord21 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
27951
28332
|
function dropSource(v) {
|
|
27952
28333
|
if (Array.isArray(v)) return v.forEach(dropSource);
|
|
27953
|
-
if (!
|
|
27954
|
-
if (
|
|
28334
|
+
if (!isRecord21(v)) return;
|
|
28335
|
+
if (isRecord21(v.attrs)) delete v.attrs.source;
|
|
27955
28336
|
for (const x of Object.values(v)) dropSource(x);
|
|
27956
28337
|
}
|
|
27957
28338
|
function shapeSnapshot(key, body, opts = {}) {
|
|
@@ -27961,7 +28342,7 @@ function shapeSnapshot(key, body, opts = {}) {
|
|
|
27961
28342
|
} catch {
|
|
27962
28343
|
return body;
|
|
27963
28344
|
}
|
|
27964
|
-
if (!
|
|
28345
|
+
if (!isRecord21(doc)) return body;
|
|
27965
28346
|
if (key === "/api/project") {
|
|
27966
28347
|
delete doc.recents;
|
|
27967
28348
|
delete doc.approver;
|
|
@@ -28031,11 +28412,11 @@ function manifestFiles(text) {
|
|
|
28031
28412
|
} catch {
|
|
28032
28413
|
return [];
|
|
28033
28414
|
}
|
|
28034
|
-
const map =
|
|
28415
|
+
const map = isRecord21(doc) && isRecord21(doc.keyToFile) ? doc.keyToFile : {};
|
|
28035
28416
|
return Object.values(map).filter((f) => typeof f === "string" && /^snapshots\/[A-Za-z0-9=_.-]+$/.test(f) && !f.includes(".."));
|
|
28036
28417
|
}
|
|
28037
28418
|
function bundleFiles(outDir) {
|
|
28038
|
-
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();
|
|
28039
28420
|
}
|
|
28040
28421
|
async function publishBundle(outDir, target, client) {
|
|
28041
28422
|
const files = bundleFiles(outDir);
|
|
@@ -28051,7 +28432,7 @@ async function publishBundle(outDir, target, client) {
|
|
|
28051
28432
|
}
|
|
28052
28433
|
const last = ["manifest.json", "index.html"];
|
|
28053
28434
|
const order = [...files.filter((f) => !last.includes(f)), ...last.filter((f) => files.includes(f))];
|
|
28054
|
-
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);
|
|
28055
28436
|
const wrote = new Set(files);
|
|
28056
28437
|
const removed = [];
|
|
28057
28438
|
for (const f of previous) {
|
|
@@ -28103,8 +28484,8 @@ It's just files \u2014 GitHub Pages, S3, nginx, or Cloudflare Pages
|
|
|
28103
28484
|
|
|
28104
28485
|
// src/publish-snapshots.ts
|
|
28105
28486
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
28106
|
-
import { readFileSync as
|
|
28107
|
-
import { join as
|
|
28487
|
+
import { readFileSync as readFileSync37 } from "node:fs";
|
|
28488
|
+
import { join as join49 } from "node:path";
|
|
28108
28489
|
var DEFAULT_KEEP = 30;
|
|
28109
28490
|
var SNAPSHOT_REPORTS_BASE = "../../../";
|
|
28110
28491
|
var COMMIT = /^[0-9a-f]{7,64}$/;
|
|
@@ -28129,12 +28510,12 @@ function resolveCommit2(dir, given) {
|
|
|
28129
28510
|
return { sha };
|
|
28130
28511
|
}
|
|
28131
28512
|
}
|
|
28132
|
-
var
|
|
28513
|
+
var isRecord22 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
28133
28514
|
function parseHistory(text) {
|
|
28134
28515
|
const doc = JSON.parse(text);
|
|
28135
28516
|
if (!Array.isArray(doc)) throw new Error("history.json is not an array");
|
|
28136
|
-
const
|
|
28137
|
-
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) }));
|
|
28138
28519
|
}
|
|
28139
28520
|
function nextHistory(old, entry, keep) {
|
|
28140
28521
|
const all = [entry, ...old.filter((e) => e.commit !== entry.commit)];
|
|
@@ -28146,9 +28527,9 @@ var BUNDLE_PATH = /^[A-Za-z0-9=_.@+-]+(\/[A-Za-z0-9=_.@+-]+)*$/;
|
|
|
28146
28527
|
var insideBundle = (f) => typeof f === "string" && BUNDLE_PATH.test(f) && !f.split("/").some((p) => p === "." || p === "..");
|
|
28147
28528
|
function commitFiles(manifestText) {
|
|
28148
28529
|
const doc = JSON.parse(manifestText);
|
|
28149
|
-
if (!
|
|
28530
|
+
if (!isRecord22(doc)) return ["manifest.json"];
|
|
28150
28531
|
const listed = Array.isArray(doc.files) ? doc.files.filter(insideBundle) : [];
|
|
28151
|
-
const snaps =
|
|
28532
|
+
const snaps = isRecord22(doc.keyToFile) ? Object.values(doc.keyToFile).filter(insideBundle) : [];
|
|
28152
28533
|
const rest = [.../* @__PURE__ */ new Set([...listed, ...snaps])].filter((f) => f !== "index.html" && f !== "manifest.json").sort();
|
|
28153
28534
|
return ["index.html", ...rest, "manifest.json"];
|
|
28154
28535
|
}
|
|
@@ -28192,7 +28573,7 @@ async function publishSnapshot(outDir, target, commit, client, opts = {}) {
|
|
|
28192
28573
|
const keep = opts.keep ?? DEFAULT_KEEP;
|
|
28193
28574
|
if (!Number.isInteger(keep) || keep < 1) throw new Error(`--keep takes a whole number of at least 1, not ${keep}`);
|
|
28194
28575
|
const at2 = (f) => `${target.prefix}/${f}`;
|
|
28195
|
-
const local = JSON.parse(
|
|
28576
|
+
const local = JSON.parse(readFileSync37(join49(outDir, "manifest.json"), "utf8"));
|
|
28196
28577
|
const generated = local.capturedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
28197
28578
|
const entry = { commit: sha, at: commit.at ?? generated, project: opts.project ?? local.projectDir ?? "", generated };
|
|
28198
28579
|
const bundle = await publishBundle(outDir, { bucket: target.bucket, prefix: at2(sha) }, client);
|
|
@@ -28260,6 +28641,7 @@ Usage:
|
|
|
28260
28641
|
[--yodel-report <file|dir>]
|
|
28261
28642
|
[--publish s3://<bucket>/<prefix>/views/<name> [--commit <sha>] [--keep <n>]]
|
|
28262
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>]]
|
|
28263
28645
|
[--yodel-report <file|dir>]
|
|
28264
28646
|
behold carve <report.json> [--port <n>]
|
|
28265
28647
|
|
|
@@ -28325,6 +28707,19 @@ Options:
|
|
|
28325
28707
|
--allow-host <n,\u2026> serve only: names besides loopback behold answers to, and
|
|
28326
28708
|
whose pages may write (BEHOLD_ALLOWED_HOSTS too). For a
|
|
28327
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.
|
|
28328
28723
|
--hud <url> serve only: where hud reviews this workspace's records
|
|
28329
28724
|
(BEHOLD_HUD_URL too). A proposed decision in a member's
|
|
28330
28725
|
or a card's "why" links to its review there (#471).
|
|
@@ -28343,7 +28738,8 @@ Options:
|
|
|
28343
28738
|
serve with --terragucci only: while a page is open, ask
|
|
28344
28739
|
whether index.json changed (If-None-Match, or its mtime)
|
|
28345
28740
|
and fetch chant/lifecycle (the gates and locks) every
|
|
28346
|
-
<secs> (default 30), and push what
|
|
28741
|
+
<secs> (default 30, or 10 with --public), and push what
|
|
28742
|
+
moved to the page.
|
|
28347
28743
|
0 turns it off. The fetch goes into behold's own cache
|
|
28348
28744
|
(<tmpdir>/behold-lifecycle-*), never into the checkout.
|
|
28349
28745
|
--yodel-report <file|dir>
|
|
@@ -28464,6 +28860,7 @@ ${USAGE}`);
|
|
|
28464
28860
|
let terragucciProject;
|
|
28465
28861
|
let terragucciPoll;
|
|
28466
28862
|
let yodelReport;
|
|
28863
|
+
let isPublic = false;
|
|
28467
28864
|
const allowHosts = [];
|
|
28468
28865
|
for (let i = 0; i < rest.length; i++) {
|
|
28469
28866
|
const a = rest[i];
|
|
@@ -28478,6 +28875,7 @@ ${USAGE}`);
|
|
|
28478
28875
|
else if (a === "--env") env = rest[++i];
|
|
28479
28876
|
else if (a === "--poll") pollSecs = Number(rest[++i]);
|
|
28480
28877
|
else if (a === "--local") local = true;
|
|
28878
|
+
else if (a === "--public") isPublic = true;
|
|
28481
28879
|
else if (a === "--auto-sync") {
|
|
28482
28880
|
const m = rest[++i];
|
|
28483
28881
|
if (!m || !isAutoSyncMode(m)) {
|
|
@@ -28537,6 +28935,23 @@ ${USAGE}`);
|
|
|
28537
28935
|
process.stderr.write("behold serve: --terragucci-poll needs --terragucci\n");
|
|
28538
28936
|
process.exit(2);
|
|
28539
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;
|
|
28540
28955
|
if (yodelReport !== void 0 && !yodelReport) {
|
|
28541
28956
|
process.stderr.write("behold serve: --yodel-report needs the file `yodel report --json` wrote, or the directory holding it\n");
|
|
28542
28957
|
process.exit(2);
|
|
@@ -28552,11 +28967,13 @@ ${USAGE}`);
|
|
|
28552
28967
|
...local ? { local: true } : {},
|
|
28553
28968
|
...host ? { host } : {},
|
|
28554
28969
|
...allowHosts.length ? { allowedHosts: allowHosts } : {},
|
|
28970
|
+
...isPublic ? { public: true, layoutWrites: false } : {},
|
|
28555
28971
|
...target.workspace && hud ? { hud } : {},
|
|
28556
28972
|
...yodelReport ? { yodelReport: resolve33(yodelReport) } : {},
|
|
28557
28973
|
...terragucci ? { terragucci: { source: terragucci, ...terragucciProject ? { project: terragucciProject } : {}, ...terragucciPoll !== void 0 ? { pollSecs: terragucciPoll } : {} } } : {}
|
|
28558
28974
|
});
|
|
28559
28975
|
}
|
|
28976
|
+
var PUBLIC_TERRAGUCCI_POLL_SECS = 10;
|
|
28560
28977
|
async function serveTarget(dirs, verb) {
|
|
28561
28978
|
const workspace = dirs.length === 1 && hasDeclaration(dirs[0]) ? await declaredWorkspace(dirs[0], verb) : void 0;
|
|
28562
28979
|
if (workspace && workspace.members.length > 0) {
|
|
@@ -28608,7 +29025,7 @@ function warnIfNotChantProject(dir) {
|
|
|
28608
29025
|
if (shape.kind === "estate") {
|
|
28609
29026
|
process.stderr.write(
|
|
28610
29027
|
`behold: warning \u2014 ${dir} is an estate root, not a chant project itself.
|
|
28611
|
-
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(" ")}
|
|
28612
29029
|
`
|
|
28613
29030
|
);
|
|
28614
29031
|
return;
|
|
@@ -28644,7 +29061,7 @@ async function runCarve(rest) {
|
|
|
28644
29061
|
process.exit(2);
|
|
28645
29062
|
}
|
|
28646
29063
|
const reportPath = resolve33(fileArg);
|
|
28647
|
-
const parsed = readCarveReport(reportPath, (p) =>
|
|
29064
|
+
const parsed = readCarveReport(reportPath, (p) => readFileSync38(p, "utf8"));
|
|
28648
29065
|
if (!parsed.ok) {
|
|
28649
29066
|
process.stderr.write(`behold carve: ${parsed.refusal.error}
|
|
28650
29067
|
${parsed.refusal.remedy}
|
|
@@ -28710,7 +29127,7 @@ ${check.stdout || check.stderr}
|
|
|
28710
29127
|
if (check.code !== 0) process.exitCode = 1;
|
|
28711
29128
|
}
|
|
28712
29129
|
async function runDemo(rest) {
|
|
28713
|
-
const pkgRoot3 =
|
|
29130
|
+
const pkgRoot3 = join50(dirname21(fileURLToPath12(import.meta.url)), "..");
|
|
28714
29131
|
const registry2 = loadDemoRegistry(pkgRoot3);
|
|
28715
29132
|
let port = 4600;
|
|
28716
29133
|
let live2 = false;
|
|
@@ -28845,7 +29262,7 @@ async function serveCarveDemo(target, carve, port, live2 = false) {
|
|
|
28845
29262
|
};
|
|
28846
29263
|
process.stdout.write("behold demo carve --live \u2192 the tfstate is real now; the advisor reads what terraform wrote.\n");
|
|
28847
29264
|
}
|
|
28848
|
-
if (existsSync30(
|
|
29265
|
+
if (existsSync30(join50(project, "package.json")) && !existsSync30(join50(project, "node_modules"))) {
|
|
28849
29266
|
process.stdout.write(`behold demo carve \u2192 npm install in ${carve.project}/ (the chant this walkthrough shells)\u2026
|
|
28850
29267
|
`);
|
|
28851
29268
|
const code = await spawnStep("npm", ["install"], project);
|
|
@@ -28856,7 +29273,7 @@ async function serveCarveDemo(target, carve, port, live2 = false) {
|
|
|
28856
29273
|
}
|
|
28857
29274
|
}
|
|
28858
29275
|
let degraded;
|
|
28859
|
-
if (!existsSync30(
|
|
29276
|
+
if (!existsSync30(join50(target, "node_modules", ...HCL_PARSER_PKG.split("/")))) {
|
|
28860
29277
|
process.stdout.write(`behold demo carve \u2192 npm install ${HCL_PARSER_PKG} (chant's HCL parser, ~2MB, once)\u2026
|
|
28861
29278
|
`);
|
|
28862
29279
|
const code = await spawnStep("npm", ["install", "--no-save", "--no-package-lock", HCL_PARSER_PKG], target);
|