@intentius/behold 0.29.1 → 0.29.2
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 +17 -4
- package/dist/cli.js +281 -125
- package/package.json +1 -1
- package/web/app.js +348 -51
- package/web/embed.js +6 -3
- package/web/embed.test.js +15 -1
- package/web/index.html +7 -0
- package/web/kiosk-runs.js +485 -0
- package/web/kiosk-runs.test.js +98 -0
- package/web/kiosk.html +98 -29
- package/web/kiosk.js +177 -83
- package/web/kiosk.test.js +65 -0
- package/web/terragucci-change.js +148 -3
- package/web/terragucci-change.test.js +99 -0
- package/web/view-url.js +24 -2
- package/web/view-url.test.js +23 -0
package/dist/cli.js
CHANGED
|
@@ -299,7 +299,7 @@ var K8sApiError, K8sTransportError, K8sClientUnavailableError, ExecCredentialNot
|
|
|
299
299
|
var init_errors = __esm({
|
|
300
300
|
"node_modules/@intentius/chant-k8s-client/src/errors.ts"() {
|
|
301
301
|
K8sApiError = class _K8sApiError extends Error {
|
|
302
|
-
constructor(statusCode, reason, apiMessage, target,
|
|
302
|
+
constructor(statusCode, reason, apiMessage, target, status2) {
|
|
303
303
|
super(
|
|
304
304
|
`${target ? `${target}: ` : ""}${apiMessage || "request failed"} (HTTP ${statusCode}${reason ? `, ${reason}` : ""})`
|
|
305
305
|
);
|
|
@@ -307,7 +307,7 @@ var init_errors = __esm({
|
|
|
307
307
|
this.reason = reason;
|
|
308
308
|
this.apiMessage = apiMessage;
|
|
309
309
|
this.target = target;
|
|
310
|
-
this.status =
|
|
310
|
+
this.status = status2;
|
|
311
311
|
this.name = "K8sApiError";
|
|
312
312
|
}
|
|
313
313
|
statusCode;
|
|
@@ -348,16 +348,16 @@ var init_errors = __esm({
|
|
|
348
348
|
* Kubernetes error and occasionally plain text from a proxy in front of one.
|
|
349
349
|
*/
|
|
350
350
|
static fromResponse(statusCode, body, target) {
|
|
351
|
-
let
|
|
351
|
+
let status2;
|
|
352
352
|
try {
|
|
353
353
|
const parsed = JSON.parse(body);
|
|
354
354
|
if (parsed && typeof parsed === "object" && parsed.kind === "Status") {
|
|
355
|
-
|
|
355
|
+
status2 = parsed;
|
|
356
356
|
}
|
|
357
357
|
} catch {
|
|
358
358
|
}
|
|
359
|
-
const message2 =
|
|
360
|
-
return new _K8sApiError(statusCode,
|
|
359
|
+
const message2 = status2?.message ?? firstLine(body) ?? "";
|
|
360
|
+
return new _K8sApiError(statusCode, status2?.reason, message2, target, status2);
|
|
361
361
|
}
|
|
362
362
|
};
|
|
363
363
|
K8sTransportError = class extends Error {
|
|
@@ -496,13 +496,13 @@ function renderConflictReport(report) {
|
|
|
496
496
|
);
|
|
497
497
|
return lines2.join("\n");
|
|
498
498
|
}
|
|
499
|
-
function parseFieldConflicts(
|
|
500
|
-
const fromCauses = causesOf(
|
|
499
|
+
function parseFieldConflicts(status2, message2) {
|
|
500
|
+
const fromCauses = causesOf(status2);
|
|
501
501
|
if (fromCauses.length > 0) return fromCauses;
|
|
502
|
-
return parseConflictMessage(message2 ??
|
|
502
|
+
return parseConflictMessage(message2 ?? status2?.message ?? "");
|
|
503
503
|
}
|
|
504
|
-
function causesOf(
|
|
505
|
-
const details =
|
|
504
|
+
function causesOf(status2) {
|
|
505
|
+
const details = status2?.details;
|
|
506
506
|
if (!details || typeof details !== "object") return [];
|
|
507
507
|
const causes = details.causes;
|
|
508
508
|
if (!Array.isArray(causes)) return [];
|
|
@@ -570,8 +570,8 @@ var init_conflict = __esm({
|
|
|
570
570
|
conflicts;
|
|
571
571
|
/** The field manager chant applied as, and which was refused. */
|
|
572
572
|
fieldManager;
|
|
573
|
-
constructor(statusCode, apiMessage, conflicts, fieldManager, target,
|
|
574
|
-
super(statusCode,
|
|
573
|
+
constructor(statusCode, apiMessage, conflicts, fieldManager, target, status2) {
|
|
574
|
+
super(statusCode, status2?.reason ?? "Conflict", apiMessage, target, status2);
|
|
575
575
|
this.name = "FieldManagerConflictError";
|
|
576
576
|
this.conflicts = conflicts;
|
|
577
577
|
this.fieldManager = fieldManager;
|
|
@@ -763,9 +763,9 @@ function parseWatchFrames(chunk, carry = "") {
|
|
|
763
763
|
}
|
|
764
764
|
function isExpiredFrame(frame) {
|
|
765
765
|
if (frame.type !== "ERROR") return false;
|
|
766
|
-
const
|
|
767
|
-
if (
|
|
768
|
-
return
|
|
766
|
+
const status2 = frame.object;
|
|
767
|
+
if (status2.code === 410) return true;
|
|
768
|
+
return status2.reason === "Expired" || status2.reason === "Gone";
|
|
769
769
|
}
|
|
770
770
|
function resourceVersionOf(frame) {
|
|
771
771
|
const rv = frame.object?.metadata?.resourceVersion;
|
|
@@ -943,16 +943,16 @@ async function createK8sClient(options2 = {}) {
|
|
|
943
943
|
return { status: response.httpStatusCode, body: text };
|
|
944
944
|
}
|
|
945
945
|
async function sendJson(path, method, opts = {}) {
|
|
946
|
-
const { status, body } = await send(path, method, opts);
|
|
947
|
-
if (
|
|
948
|
-
throw noted(K8sApiError.fromResponse(
|
|
946
|
+
const { status: status2, body } = await send(path, method, opts);
|
|
947
|
+
if (status2 < 200 || status2 > 299) {
|
|
948
|
+
throw noted(K8sApiError.fromResponse(status2, body, opts.target));
|
|
949
949
|
}
|
|
950
950
|
try {
|
|
951
951
|
return JSON.parse(body);
|
|
952
952
|
} catch (err) {
|
|
953
953
|
throw noted(
|
|
954
954
|
new K8sTransportError(
|
|
955
|
-
`the API server returned HTTP ${
|
|
955
|
+
`the API server returned HTTP ${status2} with a body that is not JSON`,
|
|
956
956
|
opts.target ?? `${method} ${path}`,
|
|
957
957
|
{ cause: err }
|
|
958
958
|
)
|
|
@@ -1292,13 +1292,13 @@ async function createK8sClient(options2 = {}) {
|
|
|
1292
1292
|
if (opts.sinceSeconds !== void 0) query.sinceSeconds = String(opts.sinceSeconds);
|
|
1293
1293
|
if (opts.timestamps) query.timestamps = "true";
|
|
1294
1294
|
const target = `${refText(ref2)} logs`;
|
|
1295
|
-
const { status, body } = await send(`${objectPath(info, ref2.name, ref2.namespace)}/log`, "GET", {
|
|
1295
|
+
const { status: status2, body } = await send(`${objectPath(info, ref2.name, ref2.namespace)}/log`, "GET", {
|
|
1296
1296
|
query: Object.keys(query).length > 0 ? query : void 0,
|
|
1297
1297
|
signal: opts.signal,
|
|
1298
1298
|
target
|
|
1299
1299
|
});
|
|
1300
|
-
if (
|
|
1301
|
-
throw noted(K8sApiError.fromResponse(
|
|
1300
|
+
if (status2 < 200 || status2 > 299) {
|
|
1301
|
+
throw noted(K8sApiError.fromResponse(status2, body, target));
|
|
1302
1302
|
}
|
|
1303
1303
|
return body;
|
|
1304
1304
|
}
|
|
@@ -1887,8 +1887,8 @@ function statusForBand(band, score) {
|
|
|
1887
1887
|
return score >= 80 ? "good" : score >= 50 ? "warn" : "neutral";
|
|
1888
1888
|
}
|
|
1889
1889
|
}
|
|
1890
|
-
function verdictForStatus(
|
|
1891
|
-
return
|
|
1890
|
+
function verdictForStatus(status2) {
|
|
1891
|
+
return status2 === "good" ? "carve now" : status2 === "warn" ? "boundary work" : "leave in Terraform";
|
|
1892
1892
|
}
|
|
1893
1893
|
function tfTypeOf(address, kind) {
|
|
1894
1894
|
if (kind === "module" || address.startsWith("module.")) return "module";
|
|
@@ -1942,7 +1942,7 @@ function carveReportToIr(report) {
|
|
|
1942
1942
|
const resources = report.resources ?? [];
|
|
1943
1943
|
const known = new Set(resources.map((r) => r.address));
|
|
1944
1944
|
const nodes = resources.map((r) => {
|
|
1945
|
-
const
|
|
1945
|
+
const status2 = statusForBand(r.band, r.score);
|
|
1946
1946
|
const b = r.breakdown ?? {};
|
|
1947
1947
|
const edges2 = boundaryEdgesOf(r);
|
|
1948
1948
|
const patches = edges2.filter((e) => e.carved === r.address && directionOf(e) === "inbound").map((e) => e.survivor);
|
|
@@ -1954,9 +1954,9 @@ function carveReportToIr(report) {
|
|
|
1954
1954
|
attrs: {
|
|
1955
1955
|
// The drift palette's channel — `_`-prefixed, so it paints the card and
|
|
1956
1956
|
// stays out of both the card's fields and the inspect pane's list.
|
|
1957
|
-
_status:
|
|
1957
|
+
_status: status2,
|
|
1958
1958
|
score: r.score,
|
|
1959
|
-
carve: verdictForStatus(
|
|
1959
|
+
carve: verdictForStatus(status2),
|
|
1960
1960
|
band: r.band,
|
|
1961
1961
|
arithmetic: scoreArithmetic(r),
|
|
1962
1962
|
boundaryWork: boundaryWorkOf(r),
|
|
@@ -4899,8 +4899,8 @@ function carveStatusArgs(from) {
|
|
|
4899
4899
|
async function discoverCarveStates(dirs, read, io = nodeCarveIo, depth = MANIFEST_SCAN_DEPTH) {
|
|
4900
4900
|
const byTarget = /* @__PURE__ */ new Map();
|
|
4901
4901
|
for (const dir of dirs) {
|
|
4902
|
-
const
|
|
4903
|
-
const paths =
|
|
4902
|
+
const status2 = await read(dir).catch(() => void 0);
|
|
4903
|
+
const paths = status2?.carves ? status2.carves.map((row) => resolve3(status2.from ?? dir, row.path)) : listCarveManifests(dir, io, depth);
|
|
4904
4904
|
mergeCarveStates(byTarget, paths, io);
|
|
4905
4905
|
}
|
|
4906
4906
|
return byTarget;
|
|
@@ -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 = (str17) => REGEX_SPECIAL_CHARS.test(str17);
|
|
5832
|
+
exports.isRegexChar = (str17) => str17.length === 1 && exports.hasRegexChars(str17);
|
|
5833
|
+
exports.escapeRegex = (str17) => str17.replace(REGEX_SPECIAL_CHARS_GLOBAL, "\\$1");
|
|
5834
|
+
exports.toPosixSlashes = (str17) => str17.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 = (str17) => {
|
|
5846
|
+
return str17.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 str17 = 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 = () => str17.charCodeAt(index + 1);
|
|
5956
5956
|
const advance = () => {
|
|
5957
5957
|
prev = code;
|
|
5958
|
-
return
|
|
5958
|
+
return str17.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 = str17;
|
|
6127
6127
|
let prefix = "";
|
|
6128
6128
|
let glob = "";
|
|
6129
6129
|
if (start > 0) {
|
|
6130
|
-
prefix =
|
|
6131
|
-
|
|
6130
|
+
prefix = str17.slice(0, start);
|
|
6131
|
+
str17 = str17.slice(start);
|
|
6132
6132
|
lastIndex -= start;
|
|
6133
6133
|
}
|
|
6134
6134
|
if (base && isGlob === true && lastIndex > 0) {
|
|
6135
|
-
base =
|
|
6136
|
-
glob =
|
|
6135
|
+
base = str17.slice(0, lastIndex);
|
|
6136
|
+
glob = str17.slice(lastIndex);
|
|
6137
6137
|
} else if (isGlob === true) {
|
|
6138
6138
|
base = "";
|
|
6139
|
-
glob =
|
|
6139
|
+
glob = str17;
|
|
6140
6140
|
} else {
|
|
6141
|
-
base =
|
|
6141
|
+
base = str17;
|
|
6142
6142
|
}
|
|
6143
|
-
if (base && base !== "" && base !== "/" && base !==
|
|
6143
|
+
if (base && base !== "" && base !== "/" && base !== str17) {
|
|
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 = (str17) => {
|
|
7191
|
+
switch (str17) {
|
|
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(str17);
|
|
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 = (str17) => {
|
|
7241
7241
|
for (const isMatch of fns) {
|
|
7242
|
-
const state2 = isMatch(
|
|
7242
|
+
const state2 = isMatch(str17);
|
|
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 = (str17, patterns, options2) => picomatch3(patterns, options2)(str17);
|
|
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 });
|
|
@@ -11077,16 +11077,16 @@ function pipelineProgress(pipeline) {
|
|
|
11077
11077
|
}));
|
|
11078
11078
|
return { kind: "pipeline", status: "running", waves, components };
|
|
11079
11079
|
}
|
|
11080
|
-
function withJobStatus(state, job,
|
|
11080
|
+
function withJobStatus(state, job, status2) {
|
|
11081
11081
|
const components = state.components.map((c) => {
|
|
11082
11082
|
if (c.component !== job) return c;
|
|
11083
|
-
if ((c.status === "ok" || c.status === "failed") &&
|
|
11084
|
-
return { ...c, status };
|
|
11083
|
+
if ((c.status === "ok" || c.status === "failed") && status2 === "running") return c;
|
|
11084
|
+
return { ...c, status: status2 };
|
|
11085
11085
|
});
|
|
11086
11086
|
const waves = state.waves.map((w) => {
|
|
11087
11087
|
const members = components.filter((c) => w.components.includes(c.component));
|
|
11088
|
-
const
|
|
11089
|
-
return { ...w, status:
|
|
11088
|
+
const status3 = members.some((c) => c.status === "failed") ? "failed" : members.every((c) => c.status === "ok") ? "ok" : members.some((c) => c.status !== "pending") ? "running" : "pending";
|
|
11089
|
+
return { ...w, status: status3 };
|
|
11090
11090
|
});
|
|
11091
11091
|
return { ...state, components, waves };
|
|
11092
11092
|
}
|
|
@@ -11234,8 +11234,8 @@ function runViewToProgress(pipeline, view) {
|
|
|
11234
11234
|
...state,
|
|
11235
11235
|
waves: state.waves.map((w) => {
|
|
11236
11236
|
const members = state.components.filter((c) => w.components.includes(c.component));
|
|
11237
|
-
const
|
|
11238
|
-
return { ...w, status };
|
|
11237
|
+
const status2 = members.some((c) => c.status === "failed") ? "failed" : members.every((c) => c.status === "ok") ? "ok" : members.some((c) => c.status !== "pending") ? "running" : "pending";
|
|
11238
|
+
return { ...w, status: status2 };
|
|
11239
11239
|
})
|
|
11240
11240
|
};
|
|
11241
11241
|
if (view.url) state = { ...state, url: view.url };
|
|
@@ -12010,8 +12010,8 @@ var FORGE_WORD = {
|
|
|
12010
12010
|
local: "laptop",
|
|
12011
12011
|
unknown: "?"
|
|
12012
12012
|
};
|
|
12013
|
-
function releaseChip(
|
|
12014
|
-
return `${
|
|
12013
|
+
function releaseChip(status2) {
|
|
12014
|
+
return `${status2.gitSha.slice(0, 7)} via ${FORGE_WORD[status2.forge]}`;
|
|
12015
12015
|
}
|
|
12016
12016
|
function releaseStatus(rec7) {
|
|
12017
12017
|
if (!rec7) return void 0;
|
|
@@ -12060,7 +12060,7 @@ function joinTickVerdicts(ir, tick, now = Date.now()) {
|
|
|
12060
12060
|
const v = byComponent.get(n.id);
|
|
12061
12061
|
if (!v) return n;
|
|
12062
12062
|
joined = true;
|
|
12063
|
-
const
|
|
12063
|
+
const status2 = {
|
|
12064
12064
|
reconciliation: v.reconciliation,
|
|
12065
12065
|
detail: v.detail,
|
|
12066
12066
|
...v.live !== void 0 ? { live: v.live } : {},
|
|
@@ -12080,7 +12080,7 @@ function joinTickVerdicts(ir, tick, now = Date.now()) {
|
|
|
12080
12080
|
// are deliberately not passed, so this can only ever reach
|
|
12081
12081
|
// `componentStatusColor`'s last branch.
|
|
12082
12082
|
...paint ? { _status: componentStatusColor({ reconciliation: v.reconciliation, detail: v.detail }) } : {},
|
|
12083
|
-
_tickStatus:
|
|
12083
|
+
_tickStatus: status2
|
|
12084
12084
|
}
|
|
12085
12085
|
};
|
|
12086
12086
|
});
|
|
@@ -12756,11 +12756,11 @@ function tierMismatchNote(ir, tiers, currentTier) {
|
|
|
12756
12756
|
if (values.length < 2) return void 0;
|
|
12757
12757
|
const byLexicon = /* @__PURE__ */ new Map();
|
|
12758
12758
|
for (const n of ir.nodes) {
|
|
12759
|
-
const
|
|
12760
|
-
if (
|
|
12759
|
+
const status2 = n.attrs?._status;
|
|
12760
|
+
if (status2 !== "good" && status2 !== "accent") continue;
|
|
12761
12761
|
const lexicon = n.lexicon ?? "?";
|
|
12762
12762
|
const c = byLexicon.get(lexicon) ?? { good: 0, accent: 0 };
|
|
12763
|
-
c[
|
|
12763
|
+
c[status2 === "good" ? "good" : "accent"]++;
|
|
12764
12764
|
byLexicon.set(lexicon, c);
|
|
12765
12765
|
}
|
|
12766
12766
|
for (const [lexicon, { good, accent }] of [...byLexicon].sort()) {
|
|
@@ -15655,7 +15655,7 @@ function collapseBoxes(ir, opts = {}) {
|
|
|
15655
15655
|
const nodes2 = ids.flatMap((id2) => byId.get(id2) ?? []);
|
|
15656
15656
|
const id = `${SUMMARY_PREFIX}${key}`;
|
|
15657
15657
|
for (const n of nodes2) standsFor.set(n.id, id);
|
|
15658
|
-
const
|
|
15658
|
+
const status2 = summaryStatus(nodes2);
|
|
15659
15659
|
summaries.push({
|
|
15660
15660
|
id,
|
|
15661
15661
|
kind: "member",
|
|
@@ -15665,7 +15665,7 @@ function collapseBoxes(ir, opts = {}) {
|
|
|
15665
15665
|
summary: boxBadgeText(nodes2),
|
|
15666
15666
|
cards: nodes2.length,
|
|
15667
15667
|
_collapsed: true,
|
|
15668
|
-
...
|
|
15668
|
+
...status2 ? { _status: status2 } : {}
|
|
15669
15669
|
}
|
|
15670
15670
|
});
|
|
15671
15671
|
delete groups[key];
|
|
@@ -16023,8 +16023,8 @@ function bandedPlan(ir, bands) {
|
|
|
16023
16023
|
});
|
|
16024
16024
|
});
|
|
16025
16025
|
const statuses = new Set(members.map((id) => statusOf.get(id)));
|
|
16026
|
-
const
|
|
16027
|
-
boxes.push({ title, x: panelW / 2, y: top + panelH / 2, w: panelW, h: panelH, ...
|
|
16026
|
+
const status2 = statuses.size === 1 ? [...statuses][0] : void 0;
|
|
16027
|
+
boxes.push({ title, x: panelW / 2, y: top + panelH / 2, w: panelW, h: panelH, ...status2 ? { status: status2 } : {} });
|
|
16028
16028
|
top += panelH + BAND_GAP;
|
|
16029
16029
|
}
|
|
16030
16030
|
return { placed, boxes, width: panelW, height: Math.max(1, top - BAND_GAP) };
|
|
@@ -17635,9 +17635,9 @@ function neutralizeNegations(lower) {
|
|
|
17635
17635
|
const text = lower.replace(/\bnot[_\-\s]?[a-z]+\b/g, mark).replace(/\b[a-z]+\s*[=:]\s*false\b/g, mark);
|
|
17636
17636
|
return { text, negated };
|
|
17637
17637
|
}
|
|
17638
|
-
function classifyHealth(
|
|
17639
|
-
if (!
|
|
17640
|
-
const { text, negated } = neutralizeNegations(
|
|
17638
|
+
function classifyHealth(status2) {
|
|
17639
|
+
if (!status2) return "unknown";
|
|
17640
|
+
const { text, negated } = neutralizeNegations(status2.toLowerCase());
|
|
17641
17641
|
if (DEGRADED.test(text)) return "degraded";
|
|
17642
17642
|
if (PROGRESSING.test(text)) return "progressing";
|
|
17643
17643
|
if (negated) return "degraded";
|
|
@@ -17725,9 +17725,9 @@ function readyFromConditions(tree) {
|
|
|
17725
17725
|
for (const c of conditions) {
|
|
17726
17726
|
const cond = rec4(c);
|
|
17727
17727
|
if (!cond || cond.type !== "Ready") continue;
|
|
17728
|
-
const
|
|
17729
|
-
if (
|
|
17730
|
-
return { status, reason: str5(cond.reason) };
|
|
17728
|
+
const status2 = str5(cond.status);
|
|
17729
|
+
if (status2 !== "True" && status2 !== "False" && status2 !== "Unknown") continue;
|
|
17730
|
+
return { status: status2, reason: str5(cond.reason) };
|
|
17731
17731
|
}
|
|
17732
17732
|
return void 0;
|
|
17733
17733
|
}
|
|
@@ -17796,8 +17796,8 @@ function unhappyCondition(o, type) {
|
|
|
17796
17796
|
for (const c of raw) {
|
|
17797
17797
|
const cond = rec4(c);
|
|
17798
17798
|
if (!cond || cond.type !== type) continue;
|
|
17799
|
-
const
|
|
17800
|
-
const unhappy = type === "ReplicaFailure" ?
|
|
17799
|
+
const status2 = str5(cond.status);
|
|
17800
|
+
const unhappy = type === "ReplicaFailure" ? status2 === "True" : status2 === "False";
|
|
17801
17801
|
if (!unhappy) continue;
|
|
17802
17802
|
const reason = str5(cond.reason);
|
|
17803
17803
|
const message2 = str5(cond.message);
|
|
@@ -23544,11 +23544,30 @@ var TerragucciProgress = class {
|
|
|
23544
23544
|
p.catch(() => this.cached = void 0);
|
|
23545
23545
|
return p;
|
|
23546
23546
|
}
|
|
23547
|
+
/**
|
|
23548
|
+
* #569: one commit's run view, not the newest: the kiosk keeps the graph on
|
|
23549
|
+
* the cycle its narration is about while the loop's next runs (the reset's
|
|
23550
|
+
* push back to the base) already went. Cached like `answer`, per commit; the
|
|
23551
|
+
* poll's watch stays on the newest.
|
|
23552
|
+
*/
|
|
23553
|
+
answerFor(commit, fresh = false) {
|
|
23554
|
+
const key = `${this.o.checkoutProject?.() ?? ""}\0${commit}`;
|
|
23555
|
+
const at2 = Date.now();
|
|
23556
|
+
const hit = this.pinned.get(key);
|
|
23557
|
+
if (!fresh && hit && at2 - hit.at < PROGRESS_TTL_MS) return hit.answer;
|
|
23558
|
+
for (const [k, v] of this.pinned) if (at2 - v.at >= PROGRESS_TTL_MS) this.pinned.delete(k);
|
|
23559
|
+
const p = this.read(commit);
|
|
23560
|
+
this.pinned.set(key, { at: at2, answer: p });
|
|
23561
|
+
p.catch(() => this.pinned.delete(key));
|
|
23562
|
+
return p;
|
|
23563
|
+
}
|
|
23564
|
+
pinned = /* @__PURE__ */ new Map();
|
|
23547
23565
|
/** Drop the cached answer: the index changed, so the newest commit may have. */
|
|
23548
23566
|
forget() {
|
|
23549
23567
|
this.cached = void 0;
|
|
23568
|
+
this.pinned.clear();
|
|
23550
23569
|
}
|
|
23551
|
-
async read() {
|
|
23570
|
+
async read(commit) {
|
|
23552
23571
|
const v = this.o.validators?.();
|
|
23553
23572
|
const src = this.o.source;
|
|
23554
23573
|
let text;
|
|
@@ -23569,13 +23588,13 @@ var TerragucciProgress = class {
|
|
|
23569
23588
|
const rows = doc.reports;
|
|
23570
23589
|
const checkout = this.o.checkoutProject?.();
|
|
23571
23590
|
const project = pickProject(rows, { ...this.o.project ? { project: this.o.project } : {}, ...checkout ? { checkoutProject: checkout } : {} }, src);
|
|
23572
|
-
const row = newestApply(rows, project);
|
|
23591
|
+
const row = commit ? newestApply(rows.filter((r) => r.commit === commit || r.commit.startsWith(commit)), project) : newestApply(rows, project);
|
|
23573
23592
|
if (!row) {
|
|
23574
|
-
this.watch = void 0;
|
|
23593
|
+
if (!commit) this.watch = void 0;
|
|
23575
23594
|
return {
|
|
23576
23595
|
...this.base(project, v?.run.validation),
|
|
23577
23596
|
run: null,
|
|
23578
|
-
absent: `index.json lists no tf-apply run of ${project} yet, so there is no run view to read.`,
|
|
23597
|
+
absent: commit ? `index.json lists no tf-apply run of ${project} on ${commit.slice(0, 12)} yet, so there is no run view of it to read.` : `index.json lists no tf-apply run of ${project} yet, so there is no run view to read.`,
|
|
23579
23598
|
states: [],
|
|
23580
23599
|
reads: [],
|
|
23581
23600
|
waves: [],
|
|
@@ -23586,7 +23605,7 @@ var TerragucciProgress = class {
|
|
|
23586
23605
|
}
|
|
23587
23606
|
const key = runKey(row);
|
|
23588
23607
|
const a = await this.readRun(project, row.commit, key, v?.run);
|
|
23589
|
-
this.watch = { key, commit: row.commit, project, last: a };
|
|
23608
|
+
if (!commit) this.watch = { key, commit: row.commit, project, last: a };
|
|
23590
23609
|
return a;
|
|
23591
23610
|
}
|
|
23592
23611
|
base(project, validation) {
|
|
@@ -23677,7 +23696,14 @@ var TerragucciProgress = class {
|
|
|
23677
23696
|
function terragucciProgressRoute(app, progress) {
|
|
23678
23697
|
app.get("/api/terragucci/progress", async (c) => {
|
|
23679
23698
|
try {
|
|
23680
|
-
|
|
23699
|
+
const q = new URL(c.req.url).searchParams;
|
|
23700
|
+
const fresh = q.get("fresh") === "1";
|
|
23701
|
+
const commit = q.get("commit");
|
|
23702
|
+
if (commit !== null) {
|
|
23703
|
+
if (!/^[0-9a-f]{7,40}$/.test(commit)) return c.json({ error: "commit is a commit sha, 7 to 40 hex digits", code: "terragucci-progress" }, 400);
|
|
23704
|
+
return c.json(await progress.answerFor(commit, fresh));
|
|
23705
|
+
}
|
|
23706
|
+
return c.json(await progress.answer(fresh));
|
|
23681
23707
|
} catch (e) {
|
|
23682
23708
|
if (e instanceof TerragucciReadError) return c.json(e.refusal, 422);
|
|
23683
23709
|
if (e instanceof ProgressGraphError) return c.json({ error: e.message, code: "terragucci-graph", remedy: "`behold doctor` names what the Terraform read needs." }, 422);
|
|
@@ -23770,6 +23796,123 @@ function kioskRoute(app, webRoot2, enabled) {
|
|
|
23770
23796
|
});
|
|
23771
23797
|
}
|
|
23772
23798
|
|
|
23799
|
+
// src/terragucci-runs.ts
|
|
23800
|
+
var RUNS_INDEX_KEY = "runs/index.json";
|
|
23801
|
+
var runKey2 = (id) => `runs/${id}.json`;
|
|
23802
|
+
var RUNS_READ_TTL_MS = 2e3;
|
|
23803
|
+
var RUN_STATUSES = ["queued", "running", "waiting", "success", "failure", "cancelled", "skipped"];
|
|
23804
|
+
var isRecord18 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
23805
|
+
var str14 = (v) => typeof v === "string";
|
|
23806
|
+
var int2 = (v) => typeof v === "number" && Number.isInteger(v);
|
|
23807
|
+
var timeOrNull = (v) => v === null || v === void 0 || str14(v);
|
|
23808
|
+
var status = (v) => str14(v) && RUN_STATUSES.includes(v);
|
|
23809
|
+
function jobProblem(j, withSteps, at2) {
|
|
23810
|
+
if (!isRecord18(j)) return `${at2} is not an object`;
|
|
23811
|
+
if (!int2(j.id)) return `${at2}.id is not an integer`;
|
|
23812
|
+
if (!str14(j.name)) return `${at2}.name is not a string`;
|
|
23813
|
+
if (!Array.isArray(j.needs) || !j.needs.every(str14)) return `${at2}.needs is not a list of job names`;
|
|
23814
|
+
if (!status(j.status)) return `${at2}.status is not one of ${RUN_STATUSES.join(", ")}`;
|
|
23815
|
+
if (!timeOrNull(j.started_at) || !timeOrNull(j.ended_at)) return `${at2}'s times are neither times nor null`;
|
|
23816
|
+
if (withSteps) {
|
|
23817
|
+
if (!Array.isArray(j.steps)) return `${at2}.steps is not a list`;
|
|
23818
|
+
for (const [i, s] of j.steps.entries()) {
|
|
23819
|
+
const sat = `${at2}.steps[${i}]`;
|
|
23820
|
+
if (!isRecord18(s)) return `${sat} is not an object`;
|
|
23821
|
+
if (!str14(s.name)) return `${sat}.name is not a string`;
|
|
23822
|
+
if (!str14(s.status)) return `${sat}.status is not a string`;
|
|
23823
|
+
if (!Array.isArray(s.log) || !s.log.every(str14)) return `${sat}.log is not a list of lines`;
|
|
23824
|
+
if (s.truncated !== void 0 && !int2(s.truncated)) return `${sat}.truncated is not an integer`;
|
|
23825
|
+
}
|
|
23826
|
+
}
|
|
23827
|
+
return void 0;
|
|
23828
|
+
}
|
|
23829
|
+
function runProblem(r, withSteps, at2) {
|
|
23830
|
+
if (!isRecord18(r)) return `${at2} is not an object`;
|
|
23831
|
+
if (!int2(r.id)) return `${at2}.id is not an integer`;
|
|
23832
|
+
if (!str14(r.title)) return `${at2}.title is not a string`;
|
|
23833
|
+
if (!status(r.status)) return `${at2}.status is not one of ${RUN_STATUSES.join(", ")}`;
|
|
23834
|
+
if (r.trigger !== null && r.trigger !== void 0 && !str14(r.trigger)) return `${at2}.trigger is not a string`;
|
|
23835
|
+
if (!timeOrNull(r.created_at) || !timeOrNull(r.started_at) || !timeOrNull(r.ended_at)) return `${at2}'s times are neither times nor null`;
|
|
23836
|
+
if (!Array.isArray(r.jobs)) return `${at2}.jobs is not a list`;
|
|
23837
|
+
for (const [i, j] of r.jobs.entries()) {
|
|
23838
|
+
const p = jobProblem(j, withSteps, `${at2}.jobs[${i}]`);
|
|
23839
|
+
if (p) return p;
|
|
23840
|
+
}
|
|
23841
|
+
return void 0;
|
|
23842
|
+
}
|
|
23843
|
+
function runsIndexProblem(doc) {
|
|
23844
|
+
if (!isRecord18(doc)) return "it is not an object";
|
|
23845
|
+
if (doc.schema !== "demo.runs/v1") return `schema is ${JSON.stringify(doc.schema)}, not demo.runs/v1`;
|
|
23846
|
+
if (!Array.isArray(doc.runs)) return "runs is not a list";
|
|
23847
|
+
for (const [i, r] of doc.runs.entries()) {
|
|
23848
|
+
const p = runProblem(r, false, `runs[${i}]`);
|
|
23849
|
+
if (p) return p;
|
|
23850
|
+
}
|
|
23851
|
+
return void 0;
|
|
23852
|
+
}
|
|
23853
|
+
function runProblemOf(doc, id) {
|
|
23854
|
+
if (!isRecord18(doc)) return "it is not an object";
|
|
23855
|
+
if (doc.schema !== "demo.run/v1") return `schema is ${JSON.stringify(doc.schema)}, not demo.run/v1`;
|
|
23856
|
+
if (doc.id !== id) return `its id is ${JSON.stringify(doc.id)}, not ${id}`;
|
|
23857
|
+
return runProblem(doc, true, "the run");
|
|
23858
|
+
}
|
|
23859
|
+
async function readDoc2(source2, key, what, problem, now) {
|
|
23860
|
+
const base = { source: source2.location, read: now.toISOString(), key };
|
|
23861
|
+
let body;
|
|
23862
|
+
try {
|
|
23863
|
+
body = await source2.read(key);
|
|
23864
|
+
} catch (e) {
|
|
23865
|
+
if (e instanceof TerragucciDenied) return { ...base, doc: null, error: deniedWords(e, what), code: "terragucci-runs" };
|
|
23866
|
+
return { ...base, doc: null, error: `behold could not read ${key} from ${source2.location}: ${e instanceof Error ? e.message : String(e)}`, code: "terragucci-runs" };
|
|
23867
|
+
}
|
|
23868
|
+
if (body === void 0) return { ...base, doc: null, absent: `${source2.location} has no ${key}: the demo's loop driver writes the pipeline runs there (terragucci's stack/demo.sh).` };
|
|
23869
|
+
let doc;
|
|
23870
|
+
try {
|
|
23871
|
+
doc = JSON.parse(body);
|
|
23872
|
+
} catch {
|
|
23873
|
+
return { ...base, doc: null, error: `${key} in ${source2.location} is not JSON.`, code: "terragucci-runs" };
|
|
23874
|
+
}
|
|
23875
|
+
const p = problem(doc);
|
|
23876
|
+
if (p) return { ...base, doc: null, error: `${key} in ${source2.location} is not the driver's format: ${p}.`, code: "terragucci-runs" };
|
|
23877
|
+
return { ...base, doc };
|
|
23878
|
+
}
|
|
23879
|
+
function readRunsIndex(source2, now = /* @__PURE__ */ new Date()) {
|
|
23880
|
+
return readDoc2(source2, RUNS_INDEX_KEY, "the demo's pipeline runs", runsIndexProblem, now);
|
|
23881
|
+
}
|
|
23882
|
+
function readRun(source2, id, now = /* @__PURE__ */ new Date()) {
|
|
23883
|
+
return readDoc2(source2, runKey2(id), `pipeline run ${id}`, (d) => runProblemOf(d, id), now);
|
|
23884
|
+
}
|
|
23885
|
+
function runIdOf(raw) {
|
|
23886
|
+
return /^[1-9][0-9]{0,15}$/.test(raw) ? Number(raw) : null;
|
|
23887
|
+
}
|
|
23888
|
+
function terragucciRunsRoutes(app, source2, opts = {}) {
|
|
23889
|
+
const cache6 = /* @__PURE__ */ new Map();
|
|
23890
|
+
const shared2 = (key, read) => {
|
|
23891
|
+
const at2 = Date.now();
|
|
23892
|
+
const hit = cache6.get(key);
|
|
23893
|
+
if (hit && at2 - hit.at < RUNS_READ_TTL_MS) return hit.read;
|
|
23894
|
+
for (const [k, v] of cache6) if (at2 - v.at >= RUNS_READ_TTL_MS) cache6.delete(k);
|
|
23895
|
+
const p = read();
|
|
23896
|
+
cache6.set(key, { at: at2, read: p });
|
|
23897
|
+
return p;
|
|
23898
|
+
};
|
|
23899
|
+
const answer = (a) => {
|
|
23900
|
+
const status2 = "error" in a ? 502 : "absent" in a ? 404 : 200;
|
|
23901
|
+
return { body: a, status: status2 };
|
|
23902
|
+
};
|
|
23903
|
+
const now = () => opts.now ? opts.now() : /* @__PURE__ */ new Date();
|
|
23904
|
+
app.get("/api/terragucci/runs", async (c) => {
|
|
23905
|
+
const { body, status: status2 } = answer(await shared2(RUNS_INDEX_KEY, () => readRunsIndex(source2, now())));
|
|
23906
|
+
return c.json(body, status2, { "cache-control": "no-store" });
|
|
23907
|
+
});
|
|
23908
|
+
app.get("/api/terragucci/runs/:id", async (c) => {
|
|
23909
|
+
const id = runIdOf(c.req.param("id"));
|
|
23910
|
+
if (id === null) return c.json({ error: "a run id is a positive integer", code: "terragucci-runs" }, 400);
|
|
23911
|
+
const { body, status: status2 } = answer(await shared2(runKey2(id), () => readRun(source2, id, now())));
|
|
23912
|
+
return c.json(body, status2, { "cache-control": "no-store" });
|
|
23913
|
+
});
|
|
23914
|
+
}
|
|
23915
|
+
|
|
23773
23916
|
// src/terragucci-view.ts
|
|
23774
23917
|
var TERRAGUCCI_READ_TTL_MS = 3e4;
|
|
23775
23918
|
function repoBase(dirs) {
|
|
@@ -23890,6 +24033,8 @@ function terragucciRoutes(app, opts, dirs) {
|
|
|
23890
24033
|
});
|
|
23891
24034
|
});
|
|
23892
24035
|
terragucciDemoRoute(app, source2, opts.now ? { now: opts.now } : {});
|
|
24036
|
+
const runsSource = opts.runsSource ? terragucciSource(opts.runsSource, { ...opts.aws ? { aws: opts.aws } : {}, ...opts.fetch ? { fetch: opts.fetch } : {} }) : source2;
|
|
24037
|
+
terragucciRunsRoutes(app, runsSource, opts.now ? { now: opts.now } : {});
|
|
23893
24038
|
terragucciTimelineRoute(app, source2, {
|
|
23894
24039
|
...opts.project ? { project: opts.project } : {},
|
|
23895
24040
|
checkoutProject: () => checkoutProject(repoBase(dirs())),
|
|
@@ -23954,7 +24099,7 @@ var heldByOf = (v) => {
|
|
|
23954
24099
|
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;
|
|
23955
24100
|
};
|
|
23956
24101
|
var modeOf = (v) => v === "ledger" || v === "pr-review" || v === "sealed" ? v : void 0;
|
|
23957
|
-
var
|
|
24102
|
+
var str15 = (v) => typeof v === "string" && v !== "" ? v : void 0;
|
|
23958
24103
|
var strings3 = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : void 0;
|
|
23959
24104
|
var time3 = (iso) => Date.parse(iso) || 0;
|
|
23960
24105
|
function rowOf(line) {
|
|
@@ -23970,14 +24115,14 @@ function rowOf(line) {
|
|
|
23970
24115
|
}
|
|
23971
24116
|
function sealOf(r) {
|
|
23972
24117
|
const s = r.seal;
|
|
23973
|
-
const signed = s && s.signature ? { sealed: true, ...
|
|
24118
|
+
const signed = s && s.signature ? { sealed: true, ...str15(s.signer) ? { signer: str15(s.signer) } : {} } : void 0;
|
|
23974
24119
|
if (r.review || r.via === "pr-review") return { seal: "review", ...signed };
|
|
23975
24120
|
return signed ? { seal: "sealed", ...signed } : { seal: "unsigned" };
|
|
23976
24121
|
}
|
|
23977
24122
|
function reviewOf(r) {
|
|
23978
24123
|
const rv = r.review ?? void 0;
|
|
23979
24124
|
const pr = typeof rv?.pr === "number" ? rv.pr : typeof r.pr === "number" ? r.pr : void 0;
|
|
23980
|
-
const head =
|
|
24125
|
+
const head = str15(rv?.head) ?? str15(r.head);
|
|
23981
24126
|
const approvers = strings3(rv?.approvers) ?? strings3(r.reviewers);
|
|
23982
24127
|
if (pr === void 0 && head === void 0 && approvers === void 0) return void 0;
|
|
23983
24128
|
return { ...pr !== void 0 ? { pr } : {}, ...head ? { head } : {}, ...approvers ? { approvers } : {} };
|
|
@@ -24003,7 +24148,7 @@ function decisionOf(d) {
|
|
|
24003
24148
|
const r = v;
|
|
24004
24149
|
if (!r || typeof r !== "object" || typeof r.wave !== "number" || typeof r.name !== "string" || typeof r.op !== "string" || typeof r.gate !== "string") return void 0;
|
|
24005
24150
|
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;
|
|
24006
|
-
return { wave: r.wave, name: r.name, op: r.op, gate: r.gate, ...
|
|
24151
|
+
return { wave: r.wave, name: r.name, op: r.op, gate: r.gate, ...str15(r.digest) ? { digest: str15(r.digest) } : {}, ...strings3(r.heldBy) ? { heldBy: strings3(r.heldBy) } : {}, ...r.heldOn !== void 0 ? { heldOn: r.heldOn } : {}, ...outcomes2 ? { outcomes: outcomes2 } : {} };
|
|
24007
24152
|
}
|
|
24008
24153
|
function deriveGateAudit(input) {
|
|
24009
24154
|
const isOverride = input.isOverride ?? isOverrideGate2;
|
|
@@ -24028,17 +24173,17 @@ function deriveGateAudit(input) {
|
|
|
24028
24173
|
const requests = [];
|
|
24029
24174
|
for (const p of parsed) {
|
|
24030
24175
|
if (!p.pending || p.line.removed) continue;
|
|
24031
|
-
const digest =
|
|
24176
|
+
const digest = str15(p.r.planDigest);
|
|
24032
24177
|
denials.set(`${String(p.r.gate)}
|
|
24033
24178
|
${digest ?? ""}`, p.r);
|
|
24034
|
-
requests.push({ op: ledger.op, gate: String(p.r.gate), at: time3(String(p.r.timestamp)), ...digest ? { digest } : {}, ...
|
|
24179
|
+
requests.push({ op: ledger.op, gate: String(p.r.gate), at: time3(String(p.r.timestamp)), ...digest ? { digest } : {}, ...str15(p.r.environment) ? { environment: str15(p.r.environment) } : {} });
|
|
24035
24180
|
}
|
|
24036
24181
|
for (const { line, n, r, pending } of parsed) {
|
|
24037
24182
|
const gate = String(r.gate);
|
|
24038
24183
|
const override = isOverride(ledger.op, gate);
|
|
24039
24184
|
const removed = line.removed;
|
|
24040
24185
|
if (pending && removed) continue;
|
|
24041
|
-
const digest =
|
|
24186
|
+
const digest = str15(r.planDigest);
|
|
24042
24187
|
const author = removed ?? line.added;
|
|
24043
24188
|
const source2 = {
|
|
24044
24189
|
path,
|
|
@@ -24057,7 +24202,7 @@ ${digest ?? ""}`, p.r);
|
|
|
24057
24202
|
...base,
|
|
24058
24203
|
kind: override ? "override-requested" : "approval-requested",
|
|
24059
24204
|
at: String(r.timestamp),
|
|
24060
|
-
...drop({ runId:
|
|
24205
|
+
...drop({ runId: str15(r.runId), expiresAt: str15(r.expiresAt), description: str15(r.description), url: str15(r.url), rules: strings3(r.rules), wave: waveOf(r.wave), heldBy: heldByOf(r.heldBy), approvalMode: modeOf(r.approvalMode) }),
|
|
24061
24206
|
detail: extra2
|
|
24062
24207
|
}));
|
|
24063
24208
|
continue;
|
|
@@ -24085,13 +24230,13 @@ ${digest ?? ""}`) : void 0;
|
|
|
24085
24230
|
at: String(r.timestamp),
|
|
24086
24231
|
by: String(r.resolvedBy),
|
|
24087
24232
|
seal,
|
|
24088
|
-
...drop({ sealed, signer, committedBy: line.added?.by, relayedBy:
|
|
24233
|
+
...drop({ sealed, signer, committedBy: line.added?.by, relayedBy: str15(r.relayedBy), approver: approverOf(r), note: str15(r.note), url: str15(r.url), review: reviewOf(r), rules }),
|
|
24089
24234
|
detail: extra2
|
|
24090
24235
|
});
|
|
24091
24236
|
entries.push(entry);
|
|
24092
24237
|
if (!override) {
|
|
24093
24238
|
const at2 = time3(entry.at);
|
|
24094
|
-
const env =
|
|
24239
|
+
const env = str15(r.environment);
|
|
24095
24240
|
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));
|
|
24096
24241
|
if (!asked) {
|
|
24097
24242
|
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` });
|
|
@@ -24838,21 +24983,21 @@ function controlRepo(dirs, roots = () => checkoutRoots(dirs).length) {
|
|
|
24838
24983
|
}
|
|
24839
24984
|
return n === 0 ? { config, projects } : void 0;
|
|
24840
24985
|
}
|
|
24841
|
-
var
|
|
24842
|
-
var
|
|
24843
|
-
var
|
|
24986
|
+
var isRecord19 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
24987
|
+
var str16 = (v) => typeof v === "string";
|
|
24988
|
+
var int3 = (v) => typeof v === "number" && Number.isInteger(v);
|
|
24844
24989
|
function structuralRun2(doc) {
|
|
24845
|
-
if (!
|
|
24990
|
+
if (!isRecord19(doc)) return "it is not a JSON object";
|
|
24846
24991
|
if (doc.schema !== RUN_SCHEMA2) return `its schema is ${JSON.stringify(doc.schema)}, not ${RUN_SCHEMA2}`;
|
|
24847
|
-
if (!
|
|
24992
|
+
if (!str16(doc.project) || !str16(doc.commit) || !str16(doc.updated)) return "it lacks project, commit or updated";
|
|
24848
24993
|
if (!Array.isArray(doc.roots) || !Array.isArray(doc.waves)) return "it has no roots or waves array";
|
|
24849
24994
|
for (const [i, r] of doc.roots.entries()) {
|
|
24850
|
-
if (!
|
|
24851
|
-
if (r.state !== void 0 && !(
|
|
24852
|
-
if (r.external !== void 0 && !(Array.isArray(r.external) && r.external.every((x) =>
|
|
24995
|
+
if (!isRecord19(r) || !str16(r.root) || !int3(r.wave) || !Array.isArray(r.reads) || !r.reads.every(str16)) return `root ${i} lacks root, wave or reads`;
|
|
24996
|
+
if (r.state !== void 0 && !(isRecord19(r.state) && str16(r.state.key))) return `root ${String(r.root)}'s state has no key`;
|
|
24997
|
+
if (r.external !== void 0 && !(Array.isArray(r.external) && r.external.every((x) => isRecord19(x) && str16(x.data) && str16(x.key)))) return `root ${String(r.root)}'s external reads lack data or key`;
|
|
24853
24998
|
}
|
|
24854
24999
|
for (const [i, w] of doc.waves.entries()) {
|
|
24855
|
-
if (!
|
|
25000
|
+
if (!isRecord19(w) || !int3(w.number) || !Array.isArray(w.roots) || !Array.isArray(w.reads) || !str16(w.state) || !str16(w.gate)) return `wave ${i} lacks number, roots, reads, state or gate`;
|
|
24856
25001
|
}
|
|
24857
25002
|
return void 0;
|
|
24858
25003
|
}
|
|
@@ -24884,7 +25029,7 @@ function runViewValidator(dirs = []) {
|
|
|
24884
25029
|
v = {
|
|
24885
25030
|
validation: { by: "schema", terragucci: version },
|
|
24886
25031
|
check(doc) {
|
|
24887
|
-
const id =
|
|
25032
|
+
const id = isRecord19(doc) ? doc.schema : void 0;
|
|
24888
25033
|
if (id !== RUN_SCHEMA2) return `its schema is ${JSON.stringify(id)}, not ${RUN_SCHEMA2}`;
|
|
24889
25034
|
if (validate(doc)) return void 0;
|
|
24890
25035
|
const e = validate.errors?.[0];
|
|
@@ -24905,7 +25050,7 @@ var refuse13 = (error, remedy) => {
|
|
|
24905
25050
|
throw new TerragucciReadError(refusal(error, remedy));
|
|
24906
25051
|
};
|
|
24907
25052
|
var trimSlashes = (s) => s.replace(/^\/+|\/+$/g, "");
|
|
24908
|
-
function
|
|
25053
|
+
function runKey3(p, commit) {
|
|
24909
25054
|
const page = p.run_view?.commit === commit ? p.run_view.page : void 0;
|
|
24910
25055
|
if (page && !/^[a-z]+:\/\//i.test(page) && !page.startsWith("/") && !page.split("/").includes("..") && page.endsWith("/run.html")) {
|
|
24911
25056
|
return `${trimSlashes(page.slice(0, -"run.html".length))}/run.json`;
|
|
@@ -24986,7 +25131,7 @@ async function readControlEstate(source2, config, opts = {}) {
|
|
|
24986
25131
|
if (!commit) {
|
|
24987
25132
|
missing = p.status === "ok" ? "no apply has run, so there is no run view" : p.status === "no-index" ? "no runs in the bucket yet" : `its index could not be read${p.error ? `: ${p.error}` : ""}`;
|
|
24988
25133
|
} else {
|
|
24989
|
-
const key =
|
|
25134
|
+
const key = runKey3(p, commit);
|
|
24990
25135
|
let body;
|
|
24991
25136
|
try {
|
|
24992
25137
|
body = await get(key);
|
|
@@ -25607,9 +25752,9 @@ function yodelValidator(dirs = []) {
|
|
|
25607
25752
|
pinned ??= compile({ common: common_schema_default, report: report_schema_default, drift: drift_schema_default }, { by: "schema", from: "pinned", commit: PINNED_COMMIT.slice(0, 7) });
|
|
25608
25753
|
return pinned;
|
|
25609
25754
|
}
|
|
25610
|
-
var
|
|
25611
|
-
var looksLikeReport = (d) =>
|
|
25612
|
-
var looksLikeDrift = (d) =>
|
|
25755
|
+
var isRecord20 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
25756
|
+
var looksLikeReport = (d) => isRecord20(d) && "generatedAt" in d && "environments" in d;
|
|
25757
|
+
var looksLikeDrift = (d) => isRecord20(d) && "environment" in d && ("drifted" in d || "changed" in d);
|
|
25613
25758
|
function filesOf(source2) {
|
|
25614
25759
|
let st;
|
|
25615
25760
|
try {
|
|
@@ -25719,7 +25864,7 @@ function yodelReportRoute(app, source2, dirs) {
|
|
|
25719
25864
|
init_terraform_member();
|
|
25720
25865
|
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
25866
|
import { dirname as dirname16, join as join44, resolve as resolve28 } from "node:path";
|
|
25722
|
-
var
|
|
25867
|
+
var isRecord21 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
25723
25868
|
function linkDemoPins(work, probes = {}) {
|
|
25724
25869
|
const file = join44(work, "chant.workspace.json");
|
|
25725
25870
|
if (!existsSync25(file)) return { ok: true, linked: [] };
|
|
@@ -25729,12 +25874,12 @@ function linkDemoPins(work, probes = {}) {
|
|
|
25729
25874
|
} catch {
|
|
25730
25875
|
return { ok: true, linked: [] };
|
|
25731
25876
|
}
|
|
25732
|
-
if (!
|
|
25877
|
+
if (!isRecord21(decl) || !Array.isArray(decl.pins)) return { ok: true, linked: [] };
|
|
25733
25878
|
const find = probes.resolve ?? ((pkg) => resolvedPackage(pkg));
|
|
25734
25879
|
const linked = [];
|
|
25735
25880
|
let rewrite = false;
|
|
25736
25881
|
for (const pin of decl.pins) {
|
|
25737
|
-
if (!
|
|
25882
|
+
if (!isRecord21(pin) || typeof pin.package !== "string") continue;
|
|
25738
25883
|
const pkg = pin.package;
|
|
25739
25884
|
if (pkg === TERRAFORM_LEXICON_PKG) {
|
|
25740
25885
|
const refusal2 = (probes.reader ?? terraformReaderState)().refusal;
|
|
@@ -28328,11 +28473,11 @@ async function runExport(cfg, outDir, opts = {}) {
|
|
|
28328
28473
|
`
|
|
28329
28474
|
);
|
|
28330
28475
|
}
|
|
28331
|
-
var
|
|
28476
|
+
var isRecord22 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
28332
28477
|
function dropSource(v) {
|
|
28333
28478
|
if (Array.isArray(v)) return v.forEach(dropSource);
|
|
28334
|
-
if (!
|
|
28335
|
-
if (
|
|
28479
|
+
if (!isRecord22(v)) return;
|
|
28480
|
+
if (isRecord22(v.attrs)) delete v.attrs.source;
|
|
28336
28481
|
for (const x of Object.values(v)) dropSource(x);
|
|
28337
28482
|
}
|
|
28338
28483
|
function shapeSnapshot(key, body, opts = {}) {
|
|
@@ -28342,7 +28487,7 @@ function shapeSnapshot(key, body, opts = {}) {
|
|
|
28342
28487
|
} catch {
|
|
28343
28488
|
return body;
|
|
28344
28489
|
}
|
|
28345
|
-
if (!
|
|
28490
|
+
if (!isRecord22(doc)) return body;
|
|
28346
28491
|
if (key === "/api/project") {
|
|
28347
28492
|
delete doc.recents;
|
|
28348
28493
|
delete doc.approver;
|
|
@@ -28412,7 +28557,7 @@ function manifestFiles(text) {
|
|
|
28412
28557
|
} catch {
|
|
28413
28558
|
return [];
|
|
28414
28559
|
}
|
|
28415
|
-
const map =
|
|
28560
|
+
const map = isRecord22(doc) && isRecord22(doc.keyToFile) ? doc.keyToFile : {};
|
|
28416
28561
|
return Object.values(map).filter((f) => typeof f === "string" && /^snapshots\/[A-Za-z0-9=_.-]+$/.test(f) && !f.includes(".."));
|
|
28417
28562
|
}
|
|
28418
28563
|
function bundleFiles(outDir) {
|
|
@@ -28510,12 +28655,12 @@ function resolveCommit2(dir, given) {
|
|
|
28510
28655
|
return { sha };
|
|
28511
28656
|
}
|
|
28512
28657
|
}
|
|
28513
|
-
var
|
|
28658
|
+
var isRecord23 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
28514
28659
|
function parseHistory(text) {
|
|
28515
28660
|
const doc = JSON.parse(text);
|
|
28516
28661
|
if (!Array.isArray(doc)) throw new Error("history.json is not an array");
|
|
28517
|
-
const
|
|
28518
|
-
return doc.filter((e) =>
|
|
28662
|
+
const str17 = (v) => typeof v === "string" ? v : "";
|
|
28663
|
+
return doc.filter((e) => isRecord23(e) && typeof e.commit === "string" && COMMIT.test(e.commit)).map((e) => ({ commit: e.commit, at: str17(e.at), project: str17(e.project), generated: str17(e.generated) }));
|
|
28519
28664
|
}
|
|
28520
28665
|
function nextHistory(old, entry, keep) {
|
|
28521
28666
|
const all = [entry, ...old.filter((e) => e.commit !== entry.commit)];
|
|
@@ -28527,9 +28672,9 @@ var BUNDLE_PATH = /^[A-Za-z0-9=_.@+-]+(\/[A-Za-z0-9=_.@+-]+)*$/;
|
|
|
28527
28672
|
var insideBundle = (f) => typeof f === "string" && BUNDLE_PATH.test(f) && !f.split("/").some((p) => p === "." || p === "..");
|
|
28528
28673
|
function commitFiles(manifestText) {
|
|
28529
28674
|
const doc = JSON.parse(manifestText);
|
|
28530
|
-
if (!
|
|
28675
|
+
if (!isRecord23(doc)) return ["manifest.json"];
|
|
28531
28676
|
const listed = Array.isArray(doc.files) ? doc.files.filter(insideBundle) : [];
|
|
28532
|
-
const snaps =
|
|
28677
|
+
const snaps = isRecord23(doc.keyToFile) ? Object.values(doc.keyToFile).filter(insideBundle) : [];
|
|
28533
28678
|
const rest = [.../* @__PURE__ */ new Set([...listed, ...snaps])].filter((f) => f !== "index.html" && f !== "manifest.json").sort();
|
|
28534
28679
|
return ["index.html", ...rest, "manifest.json"];
|
|
28535
28680
|
}
|
|
@@ -28641,7 +28786,7 @@ Usage:
|
|
|
28641
28786
|
[--yodel-report <file|dir>]
|
|
28642
28787
|
[--publish s3://<bucket>/<prefix>/views/<name> [--commit <sha>] [--keep <n>]]
|
|
28643
28788
|
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>]]
|
|
28789
|
+
[--public] [--terragucci <src> [--terragucci-project <p>] [--terragucci-poll <secs>] [--terragucci-runs <src>]]
|
|
28645
28790
|
[--yodel-report <file|dir>]
|
|
28646
28791
|
behold carve <report.json> [--port <n>]
|
|
28647
28792
|
|
|
@@ -28742,6 +28887,11 @@ Options:
|
|
|
28742
28887
|
moved to the page.
|
|
28743
28888
|
0 turns it off. The fetch goes into behold's own cache
|
|
28744
28889
|
(<tmpdir>/behold-lifecycle-*), never into the checkout.
|
|
28890
|
+
--terragucci-runs <src>
|
|
28891
|
+
serve with --terragucci only: where /kiosk reads the
|
|
28892
|
+
demo's pipeline runs (runs/index.json, runs/<id>.json),
|
|
28893
|
+
when not beside the reports: a directory, s3:// or
|
|
28894
|
+
http(s), as --terragucci takes.
|
|
28745
28895
|
--yodel-report <file|dir>
|
|
28746
28896
|
serve/export: mark SQL Yodeler's \`yodel report --json\`
|
|
28747
28897
|
output and any \`yodel drift <env> --json\` beside it
|
|
@@ -28859,6 +29009,7 @@ ${USAGE}`);
|
|
|
28859
29009
|
let terragucci;
|
|
28860
29010
|
let terragucciProject;
|
|
28861
29011
|
let terragucciPoll;
|
|
29012
|
+
let terragucciRuns;
|
|
28862
29013
|
let yodelReport;
|
|
28863
29014
|
let isPublic = false;
|
|
28864
29015
|
const allowHosts = [];
|
|
@@ -28871,6 +29022,7 @@ ${USAGE}`);
|
|
|
28871
29022
|
else if (a === "--terragucci") terragucci = rest[++i];
|
|
28872
29023
|
else if (a === "--terragucci-project") terragucciProject = rest[++i];
|
|
28873
29024
|
else if (a === "--terragucci-poll") terragucciPoll = Number(rest[++i]);
|
|
29025
|
+
else if (a === "--terragucci-runs") terragucciRuns = rest[++i];
|
|
28874
29026
|
else if (a === "--yodel-report") yodelReport = rest[++i];
|
|
28875
29027
|
else if (a === "--env") env = rest[++i];
|
|
28876
29028
|
else if (a === "--poll") pollSecs = Number(rest[++i]);
|
|
@@ -28935,6 +29087,10 @@ ${USAGE}`);
|
|
|
28935
29087
|
process.stderr.write("behold serve: --terragucci-poll needs --terragucci\n");
|
|
28936
29088
|
process.exit(2);
|
|
28937
29089
|
}
|
|
29090
|
+
if (terragucciRuns !== void 0 && terragucci === void 0) {
|
|
29091
|
+
process.stderr.write("behold serve: --terragucci-runs needs --terragucci\n");
|
|
29092
|
+
process.exit(2);
|
|
29093
|
+
}
|
|
28938
29094
|
if (isPublic && projectDirs.length > 1) {
|
|
28939
29095
|
process.stderr.write("behold serve: --public serves one project directory\n");
|
|
28940
29096
|
process.exit(2);
|
|
@@ -28970,7 +29126,7 @@ ${USAGE}`);
|
|
|
28970
29126
|
...isPublic ? { public: true, layoutWrites: false } : {},
|
|
28971
29127
|
...target.workspace && hud ? { hud } : {},
|
|
28972
29128
|
...yodelReport ? { yodelReport: resolve33(yodelReport) } : {},
|
|
28973
|
-
...terragucci ? { terragucci: { source: terragucci, ...terragucciProject ? { project: terragucciProject } : {}, ...terragucciPoll !== void 0 ? { pollSecs: terragucciPoll } : {} } } : {}
|
|
29129
|
+
...terragucci ? { terragucci: { source: terragucci, ...terragucciProject ? { project: terragucciProject } : {}, ...terragucciPoll !== void 0 ? { pollSecs: terragucciPoll } : {}, ...terragucciRuns ? { runsSource: terragucciRuns } : {} } } : {}
|
|
28974
29130
|
});
|
|
28975
29131
|
}
|
|
28976
29132
|
var PUBLIC_TERRAGUCCI_POLL_SECS = 10;
|