@datarails-ai/dr-appspace-cli 0.12.22 → 0.12.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +876 -475
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -13,7 +13,7 @@ import { fileURLToPath as fileURLToPath6 } from "url";
|
|
|
13
13
|
// package.json
|
|
14
14
|
var package_default = {
|
|
15
15
|
name: "@datarails-ai/dr-appspace-cli",
|
|
16
|
-
version: "0.12.
|
|
16
|
+
version: "0.12.23",
|
|
17
17
|
description: "Datarails App Space CLI \u2014 build, try, push and publish App Space apps",
|
|
18
18
|
type: "module",
|
|
19
19
|
engines: {
|
|
@@ -57,7 +57,7 @@ var package_default = {
|
|
|
57
57
|
};
|
|
58
58
|
|
|
59
59
|
// src/buildInfo.ts
|
|
60
|
-
var BUILD_TIME = "2026-10-24T20:
|
|
60
|
+
var BUILD_TIME = "2026-10-24T20:55:46.894Z";
|
|
61
61
|
function formatBuildTime(iso) {
|
|
62
62
|
if (iso === "") return "";
|
|
63
63
|
const d = new Date(iso);
|
|
@@ -4393,9 +4393,9 @@ function transformRowProblems(data) {
|
|
|
4393
4393
|
const widgets = isPlainObject(data?.widgets) ? data.widgets : {};
|
|
4394
4394
|
const defaults = isPlainObject(data?.defaults) ? data.defaults : {};
|
|
4395
4395
|
for (const key of Object.keys(widgets).sort(natural2)) {
|
|
4396
|
-
const
|
|
4397
|
-
if (!isPlainObject(
|
|
4398
|
-
const row = { ...defaults, ...
|
|
4396
|
+
const own2 = widgets[key];
|
|
4397
|
+
if (!isPlainObject(own2) || own2.transform === void 0 || own2.transform === null) continue;
|
|
4398
|
+
const row = { ...defaults, ...own2 };
|
|
4399
4399
|
const where2 = `/data/widgets/${key}`;
|
|
4400
4400
|
if (typeof row.transform !== "string" || !TRANSFORM_PATH.test(row.transform)) {
|
|
4401
4401
|
problems.push({
|
|
@@ -4433,22 +4433,22 @@ function rowShape(row, data) {
|
|
|
4433
4433
|
if (declared[key] !== void 0 && declared[key] !== null) reasons.push(`its data names a ${key} \u2014 drop "data.${key}": the transform is handed every month and works out its own window`);
|
|
4434
4434
|
}
|
|
4435
4435
|
}
|
|
4436
|
-
const
|
|
4437
|
-
if (typeof
|
|
4436
|
+
const named13 = typeof declared === "string" ? declared.split(".")[0] : isPlainObject(declared) ? declared.dataset : void 0;
|
|
4437
|
+
if (typeof named13 !== "string" || named13 === "") {
|
|
4438
4438
|
reasons.push('it names no dataset \u2014 set "data" to the dataset whose measures it reads');
|
|
4439
4439
|
return reasons;
|
|
4440
4440
|
}
|
|
4441
4441
|
const datasets = isPlainObject(data?.datasets) ? data.datasets : {};
|
|
4442
|
-
const dataset = datasets[
|
|
4442
|
+
const dataset = datasets[named13];
|
|
4443
4443
|
if (!isPlainObject(dataset)) {
|
|
4444
|
-
reasons.push(`it reads dataset ${JSON.stringify(
|
|
4444
|
+
reasons.push(`it reads dataset ${JSON.stringify(named13)}, which data.datasets does not declare`);
|
|
4445
4445
|
return reasons;
|
|
4446
4446
|
}
|
|
4447
4447
|
const reads = isPlainObject(data?.reads) ? data.reads : {};
|
|
4448
4448
|
const read = typeof dataset.read === "string" ? reads[dataset.read] : void 0;
|
|
4449
4449
|
if (isPlainObject(read) && Object.hasOwn(read, "period") && read.period !== "all") {
|
|
4450
4450
|
reasons.push(
|
|
4451
|
-
`dataset ${JSON.stringify(
|
|
4451
|
+
`dataset ${JSON.stringify(named13)} reads ${JSON.stringify(dataset.read)} with the period pushed down (period ${JSON.stringify(read.period)}) \u2014 set that read's period to "all", so the transform is handed the whole history`
|
|
4452
4452
|
);
|
|
4453
4453
|
}
|
|
4454
4454
|
return reasons;
|
|
@@ -5108,9 +5108,9 @@ function assemble(input) {
|
|
|
5108
5108
|
}
|
|
5109
5109
|
return { contract, problems, transforms, did, widgetIds, fresh };
|
|
5110
5110
|
}
|
|
5111
|
-
function readSkeleton(
|
|
5112
|
-
const at = (...segments) => where(
|
|
5113
|
-
const doc =
|
|
5111
|
+
function readSkeleton(named13, problems) {
|
|
5112
|
+
const at = (...segments) => where(named13.name, named13.nested === true ? ["skeleton", ...segments] : segments);
|
|
5113
|
+
const doc = named13.doc;
|
|
5114
5114
|
const start = problems.length;
|
|
5115
5115
|
if (!isObj(doc)) {
|
|
5116
5116
|
problems.push(problem("assemble-skeleton", at(), "the skeleton is not a JSON object.", "Write the skeleton block the front of the flow handed over, as it was written."));
|
|
@@ -5293,15 +5293,15 @@ function namespacedIds(pages) {
|
|
|
5293
5293
|
}
|
|
5294
5294
|
return out;
|
|
5295
5295
|
}
|
|
5296
|
-
function readBase(
|
|
5297
|
-
const doc =
|
|
5296
|
+
function readBase(named13, problems) {
|
|
5297
|
+
const doc = named13.doc;
|
|
5298
5298
|
const app = isObj(doc) ? doc["app"] : void 0;
|
|
5299
5299
|
if (!isObj(doc) || !isObj(app) || !Array.isArray(doc["pages"]) || !isObj(doc["elements"])) {
|
|
5300
|
-
problems.push(problem("assemble-base", where(
|
|
5300
|
+
problems.push(problem("assemble-base", where(named13.name, []), "the contract.json here has no app, pages or elements to build on.", "Remove it to start from the skeleton, or restore it with app-space get <app-id> --dir."));
|
|
5301
5301
|
return null;
|
|
5302
5302
|
}
|
|
5303
5303
|
if (app["kind"] !== "dashboard") {
|
|
5304
|
-
problems.push(problem("assemble-base", where(
|
|
5304
|
+
problems.push(problem("assemble-base", where(named13.name, ["app", "kind"]), "the contract.json here is not a dashboard.", "A SPEC draft builds a dashboard; compile an app contract without --draft."));
|
|
5305
5305
|
return null;
|
|
5306
5306
|
}
|
|
5307
5307
|
const copy = clone2(doc);
|
|
@@ -5332,10 +5332,10 @@ function resolveAppId(input, base, problems) {
|
|
|
5332
5332
|
}
|
|
5333
5333
|
return id;
|
|
5334
5334
|
}
|
|
5335
|
-
function readDraft(
|
|
5336
|
-
const doc =
|
|
5337
|
-
const draft = { name:
|
|
5338
|
-
const at = (...segments) => where(
|
|
5335
|
+
function readDraft(named13, problems) {
|
|
5336
|
+
const doc = named13.doc;
|
|
5337
|
+
const draft = { name: named13.name, cards: [] };
|
|
5338
|
+
const at = (...segments) => where(named13.name, segments);
|
|
5339
5339
|
if (!isObj(doc)) {
|
|
5340
5340
|
problems.push(problem("assemble-draft", at(), "the draft is not a JSON object.", 'Write the SPEC draft as {"widgets": {"<card id>": {"props": \u2026, "row": \u2026}}, \u2026}.'));
|
|
5341
5341
|
return draft;
|
|
@@ -7199,10 +7199,10 @@ function bind(elements, spec, backend, model) {
|
|
|
7199
7199
|
widgets[key] = { component: null, components: [], spec: key, bound: false };
|
|
7200
7200
|
}
|
|
7201
7201
|
const runtimeCards = uids.filter((uid) => !isDashboardCard(elements[uid]) && cardSpecOf(elements[uid]) !== null);
|
|
7202
|
-
const
|
|
7202
|
+
const own2 = (table, key) => Object.hasOwn(table, key) ? table[key] : void 0;
|
|
7203
7203
|
for (const uid of [...cards, ...runtimeCards].sort(natural3)) {
|
|
7204
7204
|
const id = isDashboardCard(elements[uid]) ? elements[uid].id : cardSpecOf(elements[uid]);
|
|
7205
|
-
const entry =
|
|
7205
|
+
const entry = own2(widgets, id) ?? (widgets[id] = { component: null, components: [], spec: null, bound: false });
|
|
7206
7206
|
entry.components.push(uid);
|
|
7207
7207
|
if (entry.component === null) entry.component = uid;
|
|
7208
7208
|
entry.bound = entry.spec !== null;
|
|
@@ -7213,19 +7213,19 @@ function bind(elements, spec, backend, model) {
|
|
|
7213
7213
|
if (element.type !== "control") continue;
|
|
7214
7214
|
const name = element.binding;
|
|
7215
7215
|
if (!name) continue;
|
|
7216
|
-
const entry =
|
|
7216
|
+
const entry = own2(controls, name) ?? (controls[name] = { components: [], widgets: [], bound: false });
|
|
7217
7217
|
entry.components.push(uid);
|
|
7218
7218
|
}
|
|
7219
7219
|
for (const uid of cards) {
|
|
7220
7220
|
for (const param of elements[uid].props.params ?? []) {
|
|
7221
|
-
const entry =
|
|
7221
|
+
const entry = own2(controls, param) ?? (controls[param] = { components: [], widgets: [], bound: false });
|
|
7222
7222
|
if (!entry.widgets.includes(elements[uid].id)) entry.widgets.push(elements[uid].id);
|
|
7223
7223
|
}
|
|
7224
7224
|
}
|
|
7225
7225
|
for (const [key, row] of Object.entries(spec.table)) {
|
|
7226
7226
|
if (!isWidgetKey(key) || !isPlainObject(row) || !isPlainObject(row.params)) continue;
|
|
7227
7227
|
for (const param of Object.keys(row.params)) {
|
|
7228
|
-
const entry =
|
|
7228
|
+
const entry = own2(controls, param) ?? (controls[param] = { components: [], widgets: [], bound: false });
|
|
7229
7229
|
if (!entry.widgets.includes(key)) entry.widgets.push(key);
|
|
7230
7230
|
}
|
|
7231
7231
|
}
|
|
@@ -9343,11 +9343,11 @@ function unproducedPayloadKeys(contract, { handlerSource = () => void 0, moduleS
|
|
|
9343
9343
|
for (const name of Object.keys(handlers).sort()) {
|
|
9344
9344
|
const reads = handlerPayloadReads(handlers[name], handlerSource(name));
|
|
9345
9345
|
if (reads === null || reads.required.length === 0) continue;
|
|
9346
|
-
const
|
|
9347
|
-
if (
|
|
9346
|
+
const own2 = producers.get(name) ?? [];
|
|
9347
|
+
if (own2.length === 0 || anywhere.length > 0 || own2.some((one) => one.keys === null)) continue;
|
|
9348
9348
|
const produced = [];
|
|
9349
|
-
for (const one of
|
|
9350
|
-
const pages = [...new Set(
|
|
9349
|
+
for (const one of own2) for (const key of one.keys) if (!produced.includes(key)) produced.push(key);
|
|
9350
|
+
const pages = [...new Set(own2.map((one) => one.page).filter(named2))];
|
|
9351
9351
|
for (const key of reads.required) {
|
|
9352
9352
|
if (produced.includes(key)) continue;
|
|
9353
9353
|
const there = produced.length === 0 ? "the controls there produce no payload keys" : `the controls there produce ${produced.join(", ")}`;
|
|
@@ -9655,7 +9655,7 @@ function shapeMisses(found, at) {
|
|
|
9655
9655
|
return misses;
|
|
9656
9656
|
}
|
|
9657
9657
|
function shapes(branches, root) {
|
|
9658
|
-
const
|
|
9658
|
+
const named13 = branches.map((branch) => {
|
|
9659
9659
|
let node = branch;
|
|
9660
9660
|
for (let hops = 0; isObject(node) && typeof node.$ref === "string" && hops < 8; hops += 1) {
|
|
9661
9661
|
const target = resolve4(node.$ref, root);
|
|
@@ -9671,8 +9671,8 @@ function shapes(branches, root) {
|
|
|
9671
9671
|
if ("type" in node) return (Array.isArray(node.type) ? node.type : [node.type]).map(String).join(" or ");
|
|
9672
9672
|
return "a shape";
|
|
9673
9673
|
});
|
|
9674
|
-
const shown2 =
|
|
9675
|
-
return `${shown2.join(" | ")}${
|
|
9674
|
+
const shown2 = named13.slice(0, 6);
|
|
9675
|
+
return `${shown2.join(" | ")}${named13.length > shown2.length ? ` | ${named13.length - shown2.length} more` : ""}`;
|
|
9676
9676
|
}
|
|
9677
9677
|
function checkObject(instance, node, at, root, problems, ctx) {
|
|
9678
9678
|
const properties = isObject(node.properties) ? node.properties : {};
|
|
@@ -10154,9 +10154,9 @@ function unreadReasons(contract, { handlerSource = () => void 0, moduleSource, p
|
|
|
10154
10154
|
if (typeof source !== "string") continue;
|
|
10155
10155
|
const reads = pyPayloadReads(source);
|
|
10156
10156
|
const read = /* @__PURE__ */ new Set([...reads.required, ...reads.optional]);
|
|
10157
|
-
const
|
|
10157
|
+
const own2 = (producers.get(name) ?? []).filter((one) => Array.isArray(one.keys));
|
|
10158
10158
|
const sent = /* @__PURE__ */ new Map();
|
|
10159
|
-
for (const one of
|
|
10159
|
+
for (const one of own2) {
|
|
10160
10160
|
for (const key of one.keys) {
|
|
10161
10161
|
if (!REASON_KEYS.includes(key)) continue;
|
|
10162
10162
|
if (!sent.has(key)) sent.set(key, /* @__PURE__ */ new Set());
|
|
@@ -11776,7 +11776,7 @@ function restateDashboard(composed, pages, handlerName, handler, { refuse, note,
|
|
|
11776
11776
|
const outPages = [];
|
|
11777
11777
|
for (const page of pages) {
|
|
11778
11778
|
const path3 = page.path;
|
|
11779
|
-
const
|
|
11779
|
+
const own2 = Object.keys(typed).filter((uid) => typed[uid]?.page === path3).sort(natural3);
|
|
11780
11780
|
const rank = (uid) => {
|
|
11781
11781
|
const at = Array.isArray(page.tree) ? page.tree.indexOf(uid) : -1;
|
|
11782
11782
|
return at === -1 ? Number.MAX_SAFE_INTEGER : at;
|
|
@@ -11787,7 +11787,7 @@ function restateDashboard(composed, pages, handlerName, handler, { refuse, note,
|
|
|
11787
11787
|
elements[uid] = null;
|
|
11788
11788
|
return uid;
|
|
11789
11789
|
};
|
|
11790
|
-
const regions = (role) =>
|
|
11790
|
+
const regions = (role) => own2.filter((uid) => typed[uid].type === "region" && (typed[uid].role ?? "header") === role);
|
|
11791
11791
|
const region = (uid, role, tag, children, parent = null) => ({
|
|
11792
11792
|
uid,
|
|
11793
11793
|
page: path3,
|
|
@@ -11809,8 +11809,8 @@ function restateDashboard(composed, pages, handlerName, handler, { refuse, note,
|
|
|
11809
11809
|
chrome.forEach((uid, index) => {
|
|
11810
11810
|
elements[uid] = region(uid, HEADER_CHROME[index], "section", [], header);
|
|
11811
11811
|
});
|
|
11812
|
-
let nav =
|
|
11813
|
-
const declaredTabs =
|
|
11812
|
+
let nav = own2.find((uid) => typed[uid].type === "nav");
|
|
11813
|
+
const declaredTabs = own2.filter((uid) => typed[uid].type === "tab").sort((a, b) => orderOf(typed[a]) - orderOf(typed[b]) || natural3(a, b));
|
|
11814
11814
|
let tabs = [];
|
|
11815
11815
|
if (declaredTabs.length > 0) {
|
|
11816
11816
|
tabs = declaredTabs.map((uid) => {
|
|
@@ -11868,7 +11868,7 @@ function restateDashboard(composed, pages, handlerName, handler, { refuse, note,
|
|
|
11868
11868
|
elements[filters] = region(filters, "filters", "section", []);
|
|
11869
11869
|
const params = (Array.isArray(page.params) ? page.params : []).filter(isPlainObject8).map(canonical2);
|
|
11870
11870
|
const parts = [];
|
|
11871
|
-
for (const uid of
|
|
11871
|
+
for (const uid of own2.filter((uid2) => typed[uid2].type === "control")) {
|
|
11872
11872
|
const made = controlToPart(typed[uid], uid, path3, refuse);
|
|
11873
11873
|
if (made === null) continue;
|
|
11874
11874
|
const param = canonical2(made.param);
|
|
@@ -11886,10 +11886,10 @@ function restateDashboard(composed, pages, handlerName, handler, { refuse, note,
|
|
|
11886
11886
|
parts.push(uid);
|
|
11887
11887
|
}
|
|
11888
11888
|
const sections = sectionsOf(page);
|
|
11889
|
-
const cards =
|
|
11890
|
-
const dropped = new Set(
|
|
11889
|
+
const cards = own2.filter((uid) => typed[uid].type === "widget");
|
|
11890
|
+
const dropped = new Set(own2.filter((uid) => DROPPED.has(typed[uid].type) || typed[uid].type === "region" && DROPPED_REGIONS.has(typed[uid].role ?? "header")));
|
|
11891
11891
|
const handled = /* @__PURE__ */ new Set([header, ...chrome, filters, ...nav === void 0 ? [] : [nav, ...elements[nav].children], ...parts, ...cards]);
|
|
11892
|
-
const kept =
|
|
11892
|
+
const kept = own2.filter((uid) => !handled.has(uid) && !dropped.has(uid) && !["region", "nav", "tab", "control", "widget"].includes(typed[uid].type));
|
|
11893
11893
|
for (const uid of kept) {
|
|
11894
11894
|
if (conflicts.has(uid)) refuse(conflicts.get(uid).code, conflicts.get(uid).pointer, conflicts.get(uid).message);
|
|
11895
11895
|
elements[uid] = keptOf(typed[uid], uid, path3, typed, declared, dropped, kept, cards, sections);
|
|
@@ -11900,7 +11900,7 @@ function restateDashboard(composed, pages, handlerName, handler, { refuse, note,
|
|
|
11900
11900
|
if (conflicts.has(uid) && (parent !== null || kept.includes(typed[uid].parent))) refuse(conflicts.get(uid).code, conflicts.get(uid).pointer, conflicts.get(uid).message);
|
|
11901
11901
|
elements[uid] = cardOf(typed[uid], uid, path3, handlerName, sections, refuse, parent);
|
|
11902
11902
|
}
|
|
11903
|
-
const otherRegions =
|
|
11903
|
+
const otherRegions = own2.filter((uid) => typed[uid].type === "region" && !handled.has(uid) && !dropped.has(uid));
|
|
11904
11904
|
for (const uid of otherRegions) elements[uid] = region(uid, typed[uid].role ?? "header", "section", []);
|
|
11905
11905
|
const indexOfSection = (uid) => sections.findIndex((entry) => entry.id === elements[uid]?.section);
|
|
11906
11906
|
const ownRoots = [...parts, ...kept.filter((uid) => elements[uid].parent === null), ...otherRegions].sort((a, b) => rank(a) - rank(b) || natural3(a, b));
|
|
@@ -15105,8 +15105,8 @@ function partProblems(elements, tree, handlers, sources, pages = [], model = nul
|
|
|
15105
15105
|
}
|
|
15106
15106
|
}
|
|
15107
15107
|
const typedProps = isPlainObject9(props.props) ? props.props : {};
|
|
15108
|
-
const
|
|
15109
|
-
if ("field" in
|
|
15108
|
+
const own2 = PROPS_SCHEMAS[type]?.properties?.props?.properties ?? {};
|
|
15109
|
+
if ("field" in own2 && "param" in own2) {
|
|
15110
15110
|
const field = stated(typedProps.field);
|
|
15111
15111
|
const param = stated(typedProps.param);
|
|
15112
15112
|
if (field && param) {
|
|
@@ -15255,12 +15255,12 @@ function partProblems(elements, tree, handlers, sources, pages = [], model = nul
|
|
|
15255
15255
|
writers.push({ handler: action.call, key, pointer: `${at}/props/props/key`, via: `row action "${action.label ?? index}"` });
|
|
15256
15256
|
} else if (stated(action.dialog)) {
|
|
15257
15257
|
const dialog = dialogsById.get(`${element.page}|${action.dialog}`);
|
|
15258
|
-
const
|
|
15259
|
-
if (!
|
|
15260
|
-
const ownKey = stated(
|
|
15258
|
+
const own3 = isPlainObject9(dialog?.props?.props) ? dialog.props.props : null;
|
|
15259
|
+
if (!own3 || !stated(own3.on_confirm)) return;
|
|
15260
|
+
const ownKey = stated(own3.key);
|
|
15261
15261
|
writers.push({
|
|
15262
|
-
handler:
|
|
15263
|
-
key: ownKey ?
|
|
15262
|
+
handler: own3.on_confirm,
|
|
15263
|
+
key: ownKey ? own3.key : key,
|
|
15264
15264
|
pointer: ownKey ? `/elements/${escapePointer3(dialog.uid)}/props/props/key` : `${at}/props/props/key`,
|
|
15265
15265
|
via: `row action "${action.label ?? index}" (dialog "${action.dialog}"${ownKey ? ", keyed by its own props.key" : ""})`
|
|
15266
15266
|
});
|
|
@@ -17146,8 +17146,8 @@ function bandLook2(section) {
|
|
|
17146
17146
|
return BAND_LOOK3.filter((key) => named5(section[key])).map((key) => ` data-dr-${key}="${attr2(section[key])}"`).join("");
|
|
17147
17147
|
}
|
|
17148
17148
|
function documentTitle(page, appName) {
|
|
17149
|
-
const
|
|
17150
|
-
return appName === "" ||
|
|
17149
|
+
const own2 = shortTitle(page, appName);
|
|
17150
|
+
return appName === "" || own2 === appName ? own2 : `${own2} \xB7 ${appName}`;
|
|
17151
17151
|
}
|
|
17152
17152
|
function shortTitle(page, appName) {
|
|
17153
17153
|
const raw = named5(page.title) ? page.title : named5(page.header?.title) ? page.header.title : page.path;
|
|
@@ -18236,8 +18236,8 @@ function checksMetrics(catalog) {
|
|
|
18236
18236
|
function checksTables(catalog) {
|
|
18237
18237
|
return catalog.source !== "none" && catalog.tables.length > 0;
|
|
18238
18238
|
}
|
|
18239
|
-
function findTable(catalog,
|
|
18240
|
-
const want =
|
|
18239
|
+
function findTable(catalog, named13) {
|
|
18240
|
+
const want = named13.toLowerCase();
|
|
18241
18241
|
return catalog.tables.find(
|
|
18242
18242
|
(table) => table.id.toLowerCase() === want || (table.alias ?? "").toLowerCase() === want || table.name.toLowerCase() === want
|
|
18243
18243
|
);
|
|
@@ -18459,7 +18459,7 @@ function shapeMisses2(found, at) {
|
|
|
18459
18459
|
return misses;
|
|
18460
18460
|
}
|
|
18461
18461
|
function shapes2(branches, root) {
|
|
18462
|
-
const
|
|
18462
|
+
const named13 = branches.map((branch) => {
|
|
18463
18463
|
let node = branch;
|
|
18464
18464
|
for (let hops = 0; isObject3(node) && typeof node["$ref"] === "string" && hops < 8; hops += 1) {
|
|
18465
18465
|
const target = resolveRef(node["$ref"], root, []);
|
|
@@ -18481,8 +18481,8 @@ function shapes2(branches, root) {
|
|
|
18481
18481
|
}
|
|
18482
18482
|
return "a shape";
|
|
18483
18483
|
});
|
|
18484
|
-
const shown2 =
|
|
18485
|
-
const more =
|
|
18484
|
+
const shown2 = named13.slice(0, 6);
|
|
18485
|
+
const more = named13.length - shown2.length;
|
|
18486
18486
|
return `${shown2.join(" | ")}${more > 0 ? ` | ${String(more)} more` : ""}`;
|
|
18487
18487
|
}
|
|
18488
18488
|
function describe3(node, ctx) {
|
|
@@ -19931,12 +19931,12 @@ function checkSources(doc, catalog, problems, skipped) {
|
|
|
19931
19931
|
const spec = obj(row);
|
|
19932
19932
|
const source = obj(spec["source"]);
|
|
19933
19933
|
if (canMetrics && source["metric_id"] !== void 0) {
|
|
19934
|
-
const
|
|
19935
|
-
if (!metricIds.has(
|
|
19934
|
+
const named13 = String(source["metric_id"]);
|
|
19935
|
+
if (!metricIds.has(named13)) {
|
|
19936
19936
|
problems.push({
|
|
19937
19937
|
code: "unknown-metric",
|
|
19938
19938
|
pointer: pointer("data", "widgets", id, "source", "metric_id"),
|
|
19939
|
-
message: `${
|
|
19939
|
+
message: `${named13} is not in the tenant inventory.`,
|
|
19940
19940
|
fix: "Use an id from the brief, or drop this widget and its card."
|
|
19941
19941
|
});
|
|
19942
19942
|
}
|
|
@@ -20469,8 +20469,8 @@ function menuless(row) {
|
|
|
20469
20469
|
if (!addressesTable(source)) return true;
|
|
20470
20470
|
const measures = list2(source["measures"]).length > 0 ? list2(source["measures"]) : source["measure"] ? [source["measure"]] : [];
|
|
20471
20471
|
const fields = measures.map((one) => obj(one)["field"]);
|
|
20472
|
-
const
|
|
20473
|
-
return !(
|
|
20472
|
+
const named13 = typeof row["value_field"] === "string" && fields.includes(row["value_field"]);
|
|
20473
|
+
return !(named13 || fields.length === 1);
|
|
20474
20474
|
}
|
|
20475
20475
|
function addressesTable(source) {
|
|
20476
20476
|
return source["address"] === "id" || source["address"] === "alias" || Boolean(source["table_alias"]) || source["table_id"] !== void 0 && source["table_id"] !== null;
|
|
@@ -20893,14 +20893,14 @@ function checkAppModel(doc, problems) {
|
|
|
20893
20893
|
}
|
|
20894
20894
|
}
|
|
20895
20895
|
for (const key of ["action", "source"]) {
|
|
20896
|
-
const
|
|
20897
|
-
if (typeof
|
|
20898
|
-
if (Object.prototype.hasOwnProperty.call(handlers,
|
|
20899
|
-
if (key === "source" && Object.prototype.hasOwnProperty.call(sources,
|
|
20896
|
+
const named13 = props[key];
|
|
20897
|
+
if (typeof named13 !== "string" || named13 === "") continue;
|
|
20898
|
+
if (Object.prototype.hasOwnProperty.call(handlers, named13)) continue;
|
|
20899
|
+
if (key === "source" && Object.prototype.hasOwnProperty.call(sources, named13)) continue;
|
|
20900
20900
|
problems.push({
|
|
20901
20901
|
code: "unknown-handler",
|
|
20902
20902
|
pointer: pointer("elements", uid, "props", key),
|
|
20903
|
-
message: key === "source" ? `${JSON.stringify(
|
|
20903
|
+
message: key === "source" ? `${JSON.stringify(named13)} is neither a handler in data.app.handlers nor a source in data.sources.` : `${JSON.stringify(named13)} is not a handler in data.app.handlers.`,
|
|
20904
20904
|
fix: key === "source" ? "Declare the handler with its role, declare the source under data.sources, or point at one that is declared." : "Declare the handler with its role, or point at one that is declared."
|
|
20905
20905
|
});
|
|
20906
20906
|
}
|
|
@@ -20919,14 +20919,14 @@ function checkAppModel(doc, problems) {
|
|
|
20919
20919
|
if (element["type"] !== "field") continue;
|
|
20920
20920
|
const props = obj(element["props"]);
|
|
20921
20921
|
const field = props["field"];
|
|
20922
|
-
const
|
|
20923
|
-
const history = typeof
|
|
20922
|
+
const own2 = props["entity"];
|
|
20923
|
+
const history = typeof own2 === "string" && own2 !== "" && own2 !== "_events" ? historyListAround2(doc, uid) : null;
|
|
20924
20924
|
if (history !== null) {
|
|
20925
20925
|
const shows = typeof field === "string" && field !== "" ? ` shows ${JSON.stringify(field)} and` : "";
|
|
20926
20926
|
problems.push({
|
|
20927
20927
|
code: "app-model",
|
|
20928
20928
|
pointer: pointer("elements", uid, "props", "entity"),
|
|
20929
|
-
message: `field ${JSON.stringify(uid)}${shows} is a column of the history list ${JSON.stringify(history)} (over _events, the platform's record of who did what), but states entity ${JSON.stringify(
|
|
20929
|
+
message: `field ${JSON.stringify(uid)}${shows} is a column of the history list ${JSON.stringify(history)} (over _events, the platform's record of who did what), but states entity ${JSON.stringify(own2)}.`,
|
|
20930
20930
|
fix: `Drop its entity so it inherits _events, or set it to "_events". ${historyColumnsInWords2()}.`
|
|
20931
20931
|
});
|
|
20932
20932
|
continue;
|
|
@@ -20975,8 +20975,8 @@ function checkUnwrittenProps(doc, problems) {
|
|
|
20975
20975
|
const parent = parentOf2(uid);
|
|
20976
20976
|
const parentType = parent === null ? null : obj(elements[parent])["type"];
|
|
20977
20977
|
if (typeof parentType === "string" && LEAF_TYPES.has(parentType)) continue;
|
|
20978
|
-
const
|
|
20979
|
-
if ((type === "field" || type === "action") && typeof
|
|
20978
|
+
const own2 = props["entity"];
|
|
20979
|
+
if ((type === "field" || type === "action") && typeof own2 === "string" && own2 !== "" && own2 !== "_events") {
|
|
20980
20980
|
if (!(type === "field" && historyListAround2(doc, uid) !== null)) {
|
|
20981
20981
|
let supplied = null;
|
|
20982
20982
|
let holder = null;
|
|
@@ -20995,15 +20995,15 @@ function checkUnwrittenProps(doc, problems) {
|
|
|
20995
20995
|
problems.push({
|
|
20996
20996
|
code: "app-model",
|
|
20997
20997
|
pointer: pointer("elements", uid, "props", "entity"),
|
|
20998
|
-
message: `${type} ${JSON.stringify(uid)} states entity ${JSON.stringify(
|
|
20998
|
+
message: `${type} ${JSON.stringify(uid)} states entity ${JSON.stringify(own2)}, and no form, list, queue, detail or filter around it names one: the page carries an entity only on those, so this one never reaches the package (a part such as a dialog carries none).`,
|
|
20999
20999
|
fix: `Drop props.entity from ${JSON.stringify(uid)}: the handler it posts to already names its table.`
|
|
21000
21000
|
});
|
|
21001
|
-
} else if (supplied !==
|
|
21001
|
+
} else if (supplied !== own2) {
|
|
21002
21002
|
problems.push({
|
|
21003
21003
|
code: "app-model",
|
|
21004
21004
|
pointer: pointer("elements", uid, "props", "entity"),
|
|
21005
|
-
message: `${type} ${JSON.stringify(uid)} states entity ${JSON.stringify(
|
|
21006
|
-
fix: `Drop props.entity so it inherits ${JSON.stringify(supplied)}, or move it into a component over ${JSON.stringify(
|
|
21005
|
+
message: `${type} ${JSON.stringify(uid)} states entity ${JSON.stringify(own2)}, but sits in ${JSON.stringify(holder)} over ${JSON.stringify(supplied)}: the page carries only the entity of the component around it.`,
|
|
21006
|
+
fix: `Drop props.entity so it inherits ${JSON.stringify(supplied)}, or move it into a component over ${JSON.stringify(own2)}.`
|
|
21007
21007
|
});
|
|
21008
21008
|
}
|
|
21009
21009
|
}
|
|
@@ -21563,11 +21563,204 @@ function endOfQuote(source, start, quote) {
|
|
|
21563
21563
|
return source.length;
|
|
21564
21564
|
}
|
|
21565
21565
|
|
|
21566
|
+
// ../flows/insights-builder/kit/checks/rules/runtime.mjs
|
|
21567
|
+
var RUNTIME_RENDERER3 = "runtime";
|
|
21568
|
+
var CARD_COMPONENT = "widget";
|
|
21569
|
+
var SPEC_CARD = "data-dr-spec-card";
|
|
21570
|
+
var COMPONENT_ATTR2 = "data-dr-component";
|
|
21571
|
+
var CONTRACT_ID = "dr-contract";
|
|
21572
|
+
var JSON_TYPE2 = "application/json";
|
|
21573
|
+
var MOUNT_CALL = /\bdrApp\s*\.\s*mount\s*\(/;
|
|
21574
|
+
var RUNTIME_LIBRARIES = [
|
|
21575
|
+
{
|
|
21576
|
+
module: "dr-app-runtime",
|
|
21577
|
+
file: "runtime.js",
|
|
21578
|
+
pattern: /^vendor\/dr-app-runtime@\d+\.\d+\.\d+\/runtime\.js$/,
|
|
21579
|
+
why: "it mounts the skeleton, batches the cards into one request to the handler and hosts the filter bar"
|
|
21580
|
+
},
|
|
21581
|
+
{
|
|
21582
|
+
module: "dr-app-widgets",
|
|
21583
|
+
file: "widgets.js",
|
|
21584
|
+
pattern: /^vendor\/dr-app-widgets@\d+\.\d+\.\d+\/widgets\.js$/,
|
|
21585
|
+
why: "it draws every card kind, formats each figure through dr-fmt and hands each card to dr-insights"
|
|
21586
|
+
},
|
|
21587
|
+
{
|
|
21588
|
+
module: "dr-app-dashboard",
|
|
21589
|
+
file: "dashboard.js",
|
|
21590
|
+
pattern: /^vendor\/dr-app-dashboard@\d+\.\d+\.\d+\/dashboard\.js$/,
|
|
21591
|
+
why: "it draws the filter bar, its presets and the saved filters the cards are read under"
|
|
21592
|
+
}
|
|
21593
|
+
];
|
|
21594
|
+
var MIN_DASHBOARD_RUNTIME = "1.13.0";
|
|
21595
|
+
var RUNTIME_VERSION = /^vendor\/dr-app-runtime@(\d+\.\d+\.\d+)\/runtime\.js$/;
|
|
21596
|
+
function runtimeBelowMinimum(page) {
|
|
21597
|
+
const { elements } = parseHtml(page.text);
|
|
21598
|
+
return elements.filter((el) => el.tag === "script" && el.attrs.src !== void 0).map((el) => RUNTIME_VERSION.exec(resolveFromPlatformRoot(page.path, el.attrs.src) ?? "")?.[1]).filter((version) => version !== void 0 && compareVersions(version, MIN_DASHBOARD_RUNTIME) < 0);
|
|
21599
|
+
}
|
|
21600
|
+
function compareVersions(a, b) {
|
|
21601
|
+
const left = a.split(".").map(Number);
|
|
21602
|
+
const right = b.split(".").map(Number);
|
|
21603
|
+
for (let i = 0; i < 3; i++) if (left[i] !== right[i]) return left[i] - right[i];
|
|
21604
|
+
return 0;
|
|
21605
|
+
}
|
|
21606
|
+
function contractBlock(elements) {
|
|
21607
|
+
const first = elements.find((el) => el.tag === "script" && el.attrs.id === CONTRACT_ID);
|
|
21608
|
+
if (first === void 0 || first.attrs.src !== void 0) return null;
|
|
21609
|
+
return (first.attrs.type ?? "").trim().toLowerCase() === JSON_TYPE2 ? first : null;
|
|
21610
|
+
}
|
|
21611
|
+
function isEmbeddedContract(el, elements) {
|
|
21612
|
+
return el === contractBlock(elements);
|
|
21613
|
+
}
|
|
21614
|
+
function runtimeContractOf(app, page) {
|
|
21615
|
+
if (app.manifest?.kind !== "dashboard") return null;
|
|
21616
|
+
const { elements, scripts } = parseHtml(page.text ?? "");
|
|
21617
|
+
const block = contractBlock(elements);
|
|
21618
|
+
if (block === null) return null;
|
|
21619
|
+
const body = scripts[elements.filter((el) => el.tag === "script" && !("src" in el.attrs)).indexOf(block)]?.text ?? "";
|
|
21620
|
+
let embedded;
|
|
21621
|
+
try {
|
|
21622
|
+
embedded = JSON.parse(body.replace(/<\\\//g, "</"));
|
|
21623
|
+
} catch {
|
|
21624
|
+
return null;
|
|
21625
|
+
}
|
|
21626
|
+
return isPlainObject11(embedded) && isPlainObject11(embedded.app) && embedded.app.renderer === RUNTIME_RENDERER3 ? embedded : null;
|
|
21627
|
+
}
|
|
21628
|
+
function contractCards(embedded, path3) {
|
|
21629
|
+
return elementsOn(embedded, path3).filter((element) => element.type === CARD_COMPONENT).map((element) => {
|
|
21630
|
+
const props = isPlainObject11(element.props) ? element.props : {};
|
|
21631
|
+
const id = named6(props.id) ? props.id.trim() : "";
|
|
21632
|
+
const card = named6(props.card) ? props.card.trim() : null;
|
|
21633
|
+
return { key: id || card || "", id, card, source: named6(props.source) ? props.source.trim() : "" };
|
|
21634
|
+
});
|
|
21635
|
+
}
|
|
21636
|
+
function contractParams(embedded, path3) {
|
|
21637
|
+
return elementsOn(embedded, path3).flatMap((element) => {
|
|
21638
|
+
const param = isPlainObject11(element.props) && isPlainObject11(element.props.props) ? element.props.props.param : void 0;
|
|
21639
|
+
return named6(param) ? [{ name: param.trim(), id: named6(element.props.id) ? element.props.id.trim() : "" }] : [];
|
|
21640
|
+
});
|
|
21641
|
+
}
|
|
21642
|
+
function contractPage(embedded, path3) {
|
|
21643
|
+
const page = Array.isArray(embedded?.pages) ? embedded.pages.find((one) => isPlainObject11(one) && one.path === path3) : void 0;
|
|
21644
|
+
const params = new Set((Array.isArray(page?.params) ? page.params : []).map((one) => isPlainObject11(one) && named6(one.name) ? one.name.trim() : null).filter(Boolean));
|
|
21645
|
+
const handler = isPlainObject11(page?.mount) && named6(page.mount.handler) ? page.mount.handler.trim() : "";
|
|
21646
|
+
return { params, handler };
|
|
21647
|
+
}
|
|
21648
|
+
function elementsOn(embedded, path3) {
|
|
21649
|
+
const elements = isPlainObject11(embedded?.elements) ? Object.values(embedded.elements) : [];
|
|
21650
|
+
return elements.filter((element) => isPlainObject11(element) && element.page === path3);
|
|
21651
|
+
}
|
|
21652
|
+
function missingLibraries(page) {
|
|
21653
|
+
const { elements } = parseHtml(page.text);
|
|
21654
|
+
const loaded = elements.filter((el) => el.tag === "script" && el.attrs.src !== void 0).map((el) => resolveFromPlatformRoot(page.path, el.attrs.src)).filter((target) => target !== null);
|
|
21655
|
+
return RUNTIME_LIBRARIES.filter((library) => !loaded.some((target) => library.pattern.test(target)));
|
|
21656
|
+
}
|
|
21657
|
+
function libraryOrderProblems(page) {
|
|
21658
|
+
const { elements, scripts } = parseHtml(page.text);
|
|
21659
|
+
const indexOf = (module) => elements.findIndex((el) => el.tag === "script" && el.attrs.src !== void 0 && RUNTIME_LIBRARIES.find((one) => one.module === module).pattern.test(resolveFromPlatformRoot(page.path, el.attrs.src) ?? ""));
|
|
21660
|
+
const inline = elements.filter((el) => el.tag === "script" && !("src" in el.attrs));
|
|
21661
|
+
const block = contractBlock(elements);
|
|
21662
|
+
const mountScript = inline.find((el, index) => el !== block && MOUNT_CALL.test(executableText(scripts[index]?.text ?? "")));
|
|
21663
|
+
const mount = mountScript === void 0 ? -1 : elements.indexOf(mountScript);
|
|
21664
|
+
const runtime = indexOf("dr-app-runtime");
|
|
21665
|
+
const problems = [];
|
|
21666
|
+
for (const module of ["dr-app-runtime", "dr-app-widgets", "dr-app-dashboard"]) {
|
|
21667
|
+
const at = indexOf(module);
|
|
21668
|
+
const how = at === -1 ? null : deferralOf(elements[at]);
|
|
21669
|
+
if (how !== null) problems.push(`${module} is loaded with ${how}, so it does not run where it is written; load the runtime, dr-app-widgets and dr-app-dashboard as plain scripts, in that order, before the page's drApp.mount(...)`);
|
|
21670
|
+
}
|
|
21671
|
+
for (const module of ["dr-app-widgets", "dr-app-dashboard"]) {
|
|
21672
|
+
const at = indexOf(module);
|
|
21673
|
+
if (at !== -1 && runtime !== -1 && at < runtime) {
|
|
21674
|
+
problems.push(`${module} is loaded before dr-app-runtime; it registers with drApp when it loads, finds no drApp there yet, and registers nothing, so the cards stay blank`);
|
|
21675
|
+
}
|
|
21676
|
+
}
|
|
21677
|
+
const bar = indexOf("dr-app-dashboard");
|
|
21678
|
+
if (mount !== -1 && runtime !== -1 && runtime > mount) {
|
|
21679
|
+
problems.push(`the page's drApp.mount(...) runs before dr-app-runtime is loaded; drApp is undefined when it runs, and nothing is mounted`);
|
|
21680
|
+
}
|
|
21681
|
+
if (mount !== -1 && bar !== -1 && bar > mount) {
|
|
21682
|
+
problems.push(`dr-app-dashboard is loaded after the page's drApp.mount(...); the bar must be registered before the mount, or the cards load without waiting for the filters it settles`);
|
|
21683
|
+
}
|
|
21684
|
+
return problems;
|
|
21685
|
+
}
|
|
21686
|
+
function deferralOf(el) {
|
|
21687
|
+
if ("async" in el.attrs) return "async";
|
|
21688
|
+
if ("defer" in el.attrs) return "defer";
|
|
21689
|
+
return (el.attrs.type ?? "").trim().toLowerCase() === "module" ? 'type="module"' : null;
|
|
21690
|
+
}
|
|
21691
|
+
function isRuntimeCard2(el) {
|
|
21692
|
+
return el.attrs[COMPONENT_ATTR2] === CARD_COMPONENT;
|
|
21693
|
+
}
|
|
21694
|
+
function specSource(data, card) {
|
|
21695
|
+
const refs = [];
|
|
21696
|
+
const parts = [];
|
|
21697
|
+
const seen = /* @__PURE__ */ new Set();
|
|
21698
|
+
const cite = (source) => {
|
|
21699
|
+
if (!isPlainObject11(source)) return;
|
|
21700
|
+
if (source.metric_id !== void 0 && source.metric_id !== null) {
|
|
21701
|
+
const id2 = String(source.metric_id);
|
|
21702
|
+
if (refs.some((ref2) => ref2.kind === "metric" && ref2.id === id2)) return;
|
|
21703
|
+
refs.push({ kind: "metric", id: id2 });
|
|
21704
|
+
parts.push(`Metric: ${source.metric_name ?? "metric"} (id ${id2})`);
|
|
21705
|
+
return;
|
|
21706
|
+
}
|
|
21707
|
+
const id = source.table_id === void 0 || source.table_id === null ? void 0 : String(source.table_id);
|
|
21708
|
+
const alias = typeof source.table_alias === "string" && source.table_alias !== "" ? source.table_alias : void 0;
|
|
21709
|
+
const by = source.address === "id" || source.address === "alias" ? source.address : alias !== void 0 ? "alias" : id !== void 0 ? "id" : null;
|
|
21710
|
+
const ref = by === "id" && id !== void 0 ? { kind: "table", id } : by === "alias" && alias !== void 0 ? { kind: "table", alias } : null;
|
|
21711
|
+
if (ref === null) return;
|
|
21712
|
+
if (refs.some((one) => one.kind === "table" && one.id === ref.id && one.alias === ref.alias)) return;
|
|
21713
|
+
refs.push(ref);
|
|
21714
|
+
const label = [id === void 0 ? null : `id ${id}`, alias === void 0 ? null : `alias ${alias}`].filter(Boolean).join(", ");
|
|
21715
|
+
parts.push(`Table: ${source.table_name ?? "table"} (${label})`);
|
|
21716
|
+
};
|
|
21717
|
+
const readsOf = (name) => {
|
|
21718
|
+
const datasets = isPlainObject11(data?.datasets) ? data.datasets : {};
|
|
21719
|
+
const reads = isPlainObject11(data?.reads) ? data.reads : {};
|
|
21720
|
+
const dataset = own(datasets, name);
|
|
21721
|
+
if (!isPlainObject11(dataset)) {
|
|
21722
|
+
cite(own(reads, name));
|
|
21723
|
+
return;
|
|
21724
|
+
}
|
|
21725
|
+
for (const read of [dataset.read, ...Array.isArray(dataset.steps) ? dataset.steps.map(lookupOf) : []].flat()) {
|
|
21726
|
+
if (typeof read === "string") cite(own(reads, read));
|
|
21727
|
+
}
|
|
21728
|
+
};
|
|
21729
|
+
const visit = (key) => {
|
|
21730
|
+
if (typeof key !== "string" || seen.has(key)) return;
|
|
21731
|
+
seen.add(key);
|
|
21732
|
+
const row = own(isPlainObject11(data?.widgets) ? data.widgets : {}, key);
|
|
21733
|
+
if (!isPlainObject11(row)) return;
|
|
21734
|
+
cite(row.source);
|
|
21735
|
+
if (typeof row.data === "string") readsOf(row.data);
|
|
21736
|
+
for (const next of [...listOf(row.depends), ...listOf(row.about)]) visit(next);
|
|
21737
|
+
};
|
|
21738
|
+
visit(card);
|
|
21739
|
+
return { refs, sentence: parts.join("; ") };
|
|
21740
|
+
}
|
|
21741
|
+
function lookupOf(step) {
|
|
21742
|
+
if (!isPlainObject11(step)) return [];
|
|
21743
|
+
return Object.values(step).flatMap((value) => isPlainObject11(value) && typeof value.lookup === "string" ? [value.lookup] : []);
|
|
21744
|
+
}
|
|
21745
|
+
function listOf(value) {
|
|
21746
|
+
return Array.isArray(value) ? value : [];
|
|
21747
|
+
}
|
|
21748
|
+
function named6(value) {
|
|
21749
|
+
return typeof value === "string" && value.trim() !== "";
|
|
21750
|
+
}
|
|
21751
|
+
function own(table, key) {
|
|
21752
|
+
return isPlainObject11(table) && Object.hasOwn(table, key) ? table[key] : void 0;
|
|
21753
|
+
}
|
|
21754
|
+
function isPlainObject11(value) {
|
|
21755
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21756
|
+
}
|
|
21757
|
+
|
|
21566
21758
|
// ../flows/insights-builder/kit/checks/rules/formatting.mjs
|
|
21567
21759
|
var ACCOUNT_TYPE = /\b(revenues?|expenses?|cogs|liabilit(?:y|ies)|assets?|equity)\b/i;
|
|
21568
21760
|
var FMT_MODULE = /^vendor\/dr-fmt@\d+\.\d+\.\d+\/fmt\.js$/;
|
|
21569
21761
|
var FMT_USE = /\bdrFmt\s*\.\s*[A-Za-z_$]/;
|
|
21570
21762
|
var WIDGET_LIBRARY = /^vendor\/dr-widgets@\d+\.\d+\.\d+\/widgets\.js$/;
|
|
21763
|
+
var RUNTIME_WIDGET_LIBRARY = /^vendor\/dr-app-widgets@\d+\.\d+\.\d+\/widgets\.js$/;
|
|
21571
21764
|
function checkFormatting(frontend, kit) {
|
|
21572
21765
|
const violations = [];
|
|
21573
21766
|
const skipped = [];
|
|
@@ -21591,19 +21784,20 @@ function checkPage(page, module, violations, skipped) {
|
|
|
21591
21784
|
if (financial.length === 0) {
|
|
21592
21785
|
skipped.push({ rule: "R4.1", reason: `${page.path}: no widget names a P&L or balance-sheet category; dr-fmt was not required` });
|
|
21593
21786
|
} else {
|
|
21594
|
-
const
|
|
21787
|
+
const named13 = categoryText(financial[0]).match(ACCOUNT_TYPE)[0];
|
|
21595
21788
|
if (fmtIndex === -1) {
|
|
21596
21789
|
violations.push({
|
|
21597
21790
|
rule: "R4.1",
|
|
21598
21791
|
where: where2(financial[0].line),
|
|
21599
|
-
message: `widget "${financial[0].id}" shows ${
|
|
21792
|
+
message: `widget "${financial[0].id}" shows ${named13}, and ${page.path} loads no ${shelf2}; its signs and scale are then whatever the ledger happened to store`
|
|
21600
21793
|
});
|
|
21601
21794
|
}
|
|
21602
|
-
|
|
21795
|
+
const library = page.runtime === true ? "dr-app-widgets" : "dr-widgets";
|
|
21796
|
+
if (!FMT_USE.test(executableText(page.scriptText)) && !loadsModule(page.path, page.text, page.runtime === true ? RUNTIME_WIDGET_LIBRARY : WIDGET_LIBRARY)) {
|
|
21603
21797
|
violations.push({
|
|
21604
21798
|
rule: "R4.1",
|
|
21605
21799
|
where: where2(financial[0].line),
|
|
21606
|
-
message: `no script calls drFmt.*, and the page loads no vendor
|
|
21800
|
+
message: `no script calls drFmt.*, and the page loads no vendor/${library}@<version>/widgets.js to call it; widget "${financial[0].id}" shows ${named13}, whose sign convention dr-fmt exists to normalise`
|
|
21607
21801
|
});
|
|
21608
21802
|
}
|
|
21609
21803
|
}
|
|
@@ -21611,7 +21805,7 @@ function checkPage(page, module, violations, skipped) {
|
|
|
21611
21805
|
skipped.push({ rule: "R4.2", reason: `${page.path} loads no ${module}; script order was not checked` });
|
|
21612
21806
|
return;
|
|
21613
21807
|
}
|
|
21614
|
-
const deferred =
|
|
21808
|
+
const deferred = deferralOf2(elements[fmtIndex]);
|
|
21615
21809
|
if (deferred) {
|
|
21616
21810
|
violations.push({
|
|
21617
21811
|
rule: "R4.2",
|
|
@@ -21623,9 +21817,10 @@ function checkPage(page, module, violations, skipped) {
|
|
|
21623
21817
|
for (let i = 0; i < fmtIndex; i++) {
|
|
21624
21818
|
const el = elements[i];
|
|
21625
21819
|
if (el.tag !== "script" || runsAfterTheParser(el)) continue;
|
|
21626
|
-
|
|
21627
|
-
|
|
21628
|
-
|
|
21820
|
+
if (page.runtime === true && isEmbeddedContract(el, elements)) continue;
|
|
21821
|
+
const own2 = el.attrs.src === void 0 ? null : resolveInBundle(page.path, el.attrs.src);
|
|
21822
|
+
if (el.attrs.src !== void 0 && own2 === null) continue;
|
|
21823
|
+
const name = own2 ?? "an inline script";
|
|
21629
21824
|
violations.push({
|
|
21630
21825
|
rule: "R4.2",
|
|
21631
21826
|
where: where2(el.line),
|
|
@@ -21643,7 +21838,7 @@ function runsAfterTheParser(el) {
|
|
|
21643
21838
|
function categoryText(widget) {
|
|
21644
21839
|
return `${widget.source} ${widget.title} ${widget.description}`;
|
|
21645
21840
|
}
|
|
21646
|
-
function
|
|
21841
|
+
function deferralOf2(el) {
|
|
21647
21842
|
if ("async" in el.attrs) return "async";
|
|
21648
21843
|
if ("defer" in el.attrs) return "defer";
|
|
21649
21844
|
return (el.attrs.type ?? "").trim().toLowerCase() === "module" ? 'type="module"' : null;
|
|
@@ -21664,6 +21859,7 @@ var ASSISTANT_MODULE = /^vendor\/dr-insights@(\d+\.\d+\.\d+)\/(insights\.(?:css|
|
|
|
21664
21859
|
var UI_KITS = ["ui-kit/v3", "ui-kit/v4"];
|
|
21665
21860
|
var WIDGET_ID = /^[a-z][a-z0-9_-]*$/;
|
|
21666
21861
|
var REQUIRED_WIDGET_ATTRS = ["data-dr-widget-title", "data-dr-widget-description", "data-dr-widget-source"];
|
|
21862
|
+
var RUNTIME_WIDGET_ATTRS = ["data-dr-widget-title", "data-dr-widget-description"];
|
|
21667
21863
|
var SPAN_MARK = "data-dr-span";
|
|
21668
21864
|
var DRAWING = [
|
|
21669
21865
|
{ re: /\becharts\s*\.\s*init\s*\(/g, what: "echarts.init" },
|
|
@@ -21680,25 +21876,50 @@ function checkFrontend(app, kit, contract = null) {
|
|
|
21680
21876
|
const widgets = [];
|
|
21681
21877
|
const pages = [];
|
|
21682
21878
|
const scriptTexts = [];
|
|
21879
|
+
const cardHandlers = /* @__PURE__ */ new Set();
|
|
21683
21880
|
const handlerText = app.handlers.map((handler) => handler.source).join("\n");
|
|
21881
|
+
const declared = new Set(app.handlers.map((handler) => handler.name));
|
|
21882
|
+
const specKeys = runtimeSpecKeys(app);
|
|
21684
21883
|
if (!app.index) {
|
|
21685
21884
|
violations.push({ rule: "R2.1", where: "ui/index.html", message: "the bundle has no index.html" });
|
|
21686
21885
|
}
|
|
21687
21886
|
for (const page of app.pages) {
|
|
21688
|
-
const
|
|
21887
|
+
const embedded = runtimeContractOf(app, page);
|
|
21888
|
+
const runtime = embedded === null ? null : { embedded, data: contract?.data ?? embedded.data ?? null, specKeys, declared };
|
|
21889
|
+
const result = checkPage2(app, page, kit, runtime);
|
|
21689
21890
|
violations.push(...result.violations, ...checkScripts(result.scripts));
|
|
21690
21891
|
violations.push(...checkControls(page, result.controls, result.scriptText, handlerText));
|
|
21691
21892
|
widgets.push(...result.widgets);
|
|
21692
21893
|
scriptTexts.push(result.scriptText);
|
|
21693
|
-
pages.push({ path: page.path, text: page.text, widgets: result.widgets, scriptText: result.scriptText });
|
|
21894
|
+
pages.push({ path: page.path, text: page.text, widgets: result.widgets, scriptText: result.scriptText, ...runtime === null ? {} : { runtime: true, claimed: result.claimed } });
|
|
21895
|
+
if (runtime !== null) {
|
|
21896
|
+
violations.push(...checkMount(page, embedded, declared));
|
|
21897
|
+
for (const name of runtimeHandlers(page, result.widgets, embedded)) if (declared.has(name)) cardHandlers.add(name);
|
|
21898
|
+
}
|
|
21694
21899
|
}
|
|
21695
|
-
violations.push(...checkAgainstSpec(app, pages, contract));
|
|
21900
|
+
violations.push(...checkAgainstSpec(app, pages, contract, specKeys));
|
|
21696
21901
|
for (const file of app.ui) {
|
|
21697
21902
|
if (DATA_FILE.test(file.path)) {
|
|
21698
21903
|
violations.push({ rule: "R1.3", where: file.path, message: "a data file in the bundle; a dashboard ships no data" });
|
|
21699
21904
|
}
|
|
21700
21905
|
}
|
|
21701
|
-
return { violations: distinct(violations), widgets, scriptText: scriptTexts.join("\n"), pages };
|
|
21906
|
+
return { violations: distinct(violations), widgets, scriptText: scriptTexts.join("\n"), pages, cardHandlers };
|
|
21907
|
+
}
|
|
21908
|
+
function runtimeHandlers(page, widgets, embedded) {
|
|
21909
|
+
const names = widgets.map((widget) => widget.handler).filter((name) => typeof name === "string" && name !== "");
|
|
21910
|
+
const { handler } = contractPage(embedded, page.path);
|
|
21911
|
+
if (handler !== "") names.push(handler);
|
|
21912
|
+
return names;
|
|
21913
|
+
}
|
|
21914
|
+
function checkMount(page, embedded, declared) {
|
|
21915
|
+
const { handler } = contractPage(embedded, page.path);
|
|
21916
|
+
if (handler === "" || declared.has(handler)) return [];
|
|
21917
|
+
return [{ rule: "R2.2", where: page.path, message: `${page.path}'s mount names handler "${handler}", which this package does not declare; the filter bar's catalog and saved filters would reach no handler` }];
|
|
21918
|
+
}
|
|
21919
|
+
function runtimeSpecKeys(app) {
|
|
21920
|
+
const spec = readSpec(app.handlers);
|
|
21921
|
+
if (!spec.found || spec.unresolved.length > 0) return null;
|
|
21922
|
+
return new Set(Object.keys(spec.table).filter((key) => !key.startsWith("_") && !key.startsWith("$")));
|
|
21702
21923
|
}
|
|
21703
21924
|
function checkScripts(scripts) {
|
|
21704
21925
|
const violations = [];
|
|
@@ -21730,10 +21951,10 @@ function distinct(violations) {
|
|
|
21730
21951
|
return true;
|
|
21731
21952
|
});
|
|
21732
21953
|
}
|
|
21733
|
-
function checkPage2(app, page, kit) {
|
|
21954
|
+
function checkPage2(app, page, kit, runtime = null) {
|
|
21734
21955
|
const violations = [];
|
|
21735
21956
|
const widgets = [];
|
|
21736
|
-
const scripts = collectScripts(app, page);
|
|
21957
|
+
const scripts = collectScripts(app, page, runtime);
|
|
21737
21958
|
const scriptText = scripts.map((script) => script.text).join("\n");
|
|
21738
21959
|
const { elements } = parseHtml(page.text);
|
|
21739
21960
|
const where2 = (line2) => `${page.path}:${line2}`;
|
|
@@ -21778,7 +21999,7 @@ function checkPage2(app, page, kit) {
|
|
|
21778
21999
|
}
|
|
21779
22000
|
}
|
|
21780
22001
|
const seen = /* @__PURE__ */ new Map();
|
|
21781
|
-
for (const el of elements) {
|
|
22002
|
+
for (const el of runtime === null ? elements : []) {
|
|
21782
22003
|
if (!("data-dr-widget" in el.attrs)) continue;
|
|
21783
22004
|
const id = el.attrs["data-dr-widget"];
|
|
21784
22005
|
if (!WIDGET_ID.test(id)) {
|
|
@@ -21805,25 +22026,178 @@ function checkPage2(app, page, kit) {
|
|
|
21805
22026
|
line: el.line
|
|
21806
22027
|
});
|
|
21807
22028
|
}
|
|
21808
|
-
|
|
21809
|
-
|
|
22029
|
+
const cards = runtime === null ? null : checkRuntimeCards(app, page, elements, runtime, violations);
|
|
22030
|
+
if (cards !== null) {
|
|
22031
|
+
widgets.push(...cards.widgets);
|
|
22032
|
+
violations.push(...checkRuntimeControls(page, elements, runtime, [...cards.widgets.map((widget) => widget.id), ...cards.claimed]));
|
|
22033
|
+
}
|
|
22034
|
+
if (cards === null ? widgets.length === 0 : !cards.any) {
|
|
22035
|
+
violations.push({
|
|
22036
|
+
rule: "R2.1",
|
|
22037
|
+
where: page.path,
|
|
22038
|
+
message: runtime === null ? `${page.path} has no [data-dr-widget] element; a dashboard page is one widget or more` : `${page.path} has no [data-dr-component="widget"] card naming a ${SPEC_CARD}; a dashboard page is one widget or more`
|
|
22039
|
+
});
|
|
21810
22040
|
}
|
|
21811
|
-
const
|
|
21812
|
-
|
|
22041
|
+
const missing = runtime === null ? [] : missingLibraries(page).map((one) => one.module);
|
|
22042
|
+
const library = runtime === null ? loadsModule(page.path, page.text, WIDGET_LIBRARY2) : !missing.includes("dr-app-runtime") && !missing.includes("dr-app-widgets");
|
|
22043
|
+
violations.push(...checkLibraryDraws(page, library, scripts, runtime === null ? null : missing));
|
|
22044
|
+
if (runtime !== null) for (const problem5 of libraryOrderProblems(page)) violations.push({ rule: "R2.6", where: page.path, message: problem5 });
|
|
21813
22045
|
if (widgets.length > 0 && !library && !/drInsights\.setData\s*\(/.test(scriptText)) {
|
|
21814
|
-
violations.push({
|
|
22046
|
+
violations.push({
|
|
22047
|
+
rule: "R3.4",
|
|
22048
|
+
where: page.path,
|
|
22049
|
+
message: runtime === null ? `no script loaded by ${page.path} calls window.drInsights.setData, and none loads ${climb}vendor/dr-widgets@<version>/widgets.js, which would call it; the Ask button would open with no numbers` : `no script loaded by ${page.path} calls window.drInsights.setData, and it does not load both ${climb}vendor/dr-app-runtime@<version>/runtime.js and ${climb}vendor/dr-app-widgets@<version>/widgets.js, whose card bridge would call it; the Ask button would open with no numbers`
|
|
22050
|
+
});
|
|
21815
22051
|
}
|
|
21816
|
-
for (const widget of library ? [] : widgets) {
|
|
22052
|
+
for (const widget of library || runtime !== null ? [] : widgets) {
|
|
21817
22053
|
const quoted3 = new RegExp(`(['"\`])${widget.id}\\1`);
|
|
21818
22054
|
if (!quoted3.test(scriptText)) {
|
|
21819
22055
|
violations.push({ rule: "R3.4", where: where2(widget.line), message: `widget "${widget.id}" is never named in a script, so no setData call can carry its data` });
|
|
21820
22056
|
}
|
|
21821
22057
|
}
|
|
21822
|
-
return { violations, widgets, scriptText, scripts, controls };
|
|
22058
|
+
return { violations, widgets, scriptText, scripts, controls, claimed: cards?.claimed ?? [] };
|
|
21823
22059
|
}
|
|
21824
|
-
function
|
|
22060
|
+
function checkRuntimeCards(app, page, elements, runtime, violations) {
|
|
22061
|
+
const where2 = (line2) => `${page.path}:${line2}`;
|
|
22062
|
+
const mounted = contractCards(runtime.embedded, page.path);
|
|
22063
|
+
const pool = /* @__PURE__ */ new Map();
|
|
22064
|
+
for (const one of mounted) pool.set(one.key, [...pool.get(one.key) ?? [], one]);
|
|
22065
|
+
const skeleton = elements.filter(isRuntimeCard2);
|
|
22066
|
+
const widgets = [];
|
|
22067
|
+
const claimed = [];
|
|
22068
|
+
const claim = (...names) => claimed.push(...names.filter((name) => typeof name === "string" && name !== ""));
|
|
22069
|
+
const seenId = /* @__PURE__ */ new Map();
|
|
22070
|
+
const seenCard = /* @__PURE__ */ new Map();
|
|
22071
|
+
for (const el of skeleton) {
|
|
22072
|
+
const id = (el.attrs["data-dr-id"] ?? "").trim();
|
|
22073
|
+
const own2 = (el.attrs[SPEC_CARD] ?? "").trim();
|
|
22074
|
+
const twins = pool.get(id || own2) ?? [];
|
|
22075
|
+
const same5 = twins.findIndex((one) => one.card === own2);
|
|
22076
|
+
const twin = twins.length === 0 ? void 0 : twins.splice(same5 === -1 ? 0 : same5, 1)[0];
|
|
22077
|
+
const drawn2 = twin === void 0 ? null : twin.card;
|
|
22078
|
+
const called = id === "" ? 'a [data-dr-component="widget"] card' : `the card with data-dr-id "${id}"`;
|
|
22079
|
+
if (own2 === "" && (twin === void 0 || drawn2 === null)) {
|
|
22080
|
+
violations.push({ rule: "R3.2", where: where2(el.line), message: `${called} names no SPEC card, in its ${SPEC_CARD} or in #dr-contract, which the runtime mounts from; nothing binds it to a row of the SPEC, so nothing on the handler answers for it` });
|
|
22081
|
+
claim(id);
|
|
22082
|
+
continue;
|
|
22083
|
+
}
|
|
22084
|
+
if (own2 === "") {
|
|
22085
|
+
violations.push({ rule: "R3.2", where: where2(el.line), message: `${called} names no ${SPEC_CARD}, while #dr-contract mounts it as "${drawn2}"; the skeleton is what a round trip reads back, so it names the SPEC card the runtime draws` });
|
|
22086
|
+
claim(id, drawn2);
|
|
22087
|
+
continue;
|
|
22088
|
+
}
|
|
22089
|
+
if (twin === void 0) {
|
|
22090
|
+
violations.push({ rule: "R2.5", where: where2(el.line), message: `the card "${own2}"${id === "" ? "" : ` (data-dr-id "${id}")`} is not in #dr-contract, which the runtime mounts from; the runtime never draws it` });
|
|
22091
|
+
claim(id, own2);
|
|
22092
|
+
continue;
|
|
22093
|
+
}
|
|
22094
|
+
if (drawn2 === null) {
|
|
22095
|
+
violations.push({ rule: "R3.2", where: where2(el.line), message: `the card "${own2}"${id === "" ? "" : ` (data-dr-id "${id}")`} names no SPEC card in #dr-contract, which the runtime mounts from; the runtime draws it as a plain app widget, bound to no row of the SPEC` });
|
|
22096
|
+
claim(id, own2);
|
|
22097
|
+
continue;
|
|
22098
|
+
}
|
|
22099
|
+
if (drawn2 !== own2) {
|
|
22100
|
+
const unknown = runtime.specKeys !== null && !runtime.specKeys.has(drawn2) ? `, and "${drawn2}" is no row of the handler's SPEC` : "";
|
|
22101
|
+
violations.push({ rule: "R2.5", where: where2(el.line), message: `the card${id === "" ? "" : ` with data-dr-id "${id}"`} draws "${own2}" in the skeleton and "${drawn2}" in #dr-contract, which the runtime mounts from${unknown}; the two name one SPEC card, or the page shows one card and a round trip writes back another` });
|
|
22102
|
+
claim(id, own2, drawn2);
|
|
22103
|
+
continue;
|
|
22104
|
+
}
|
|
22105
|
+
if (!WIDGET_ID.test(own2)) {
|
|
22106
|
+
violations.push({ rule: "R3.2", where: where2(el.line), message: `${SPEC_CARD}=${JSON.stringify(own2)} is not a short lowercase id` });
|
|
22107
|
+
}
|
|
22108
|
+
if (id !== "" && seenId.has(id)) {
|
|
22109
|
+
violations.push({ rule: "R3.2", where: where2(el.line), message: `data-dr-id "${id}" is already used on line ${seenId.get(id)}; dr-insights files each card's data under its id, so two cards with one id share one Ask snapshot` });
|
|
22110
|
+
} else if (seenCard.has(own2)) {
|
|
22111
|
+
violations.push({ rule: "R3.2", where: where2(el.line), message: `widget id "${own2}" is already used on line ${seenCard.get(own2)}` });
|
|
22112
|
+
}
|
|
22113
|
+
if (id !== "" && !seenId.has(id)) seenId.set(id, el.line);
|
|
22114
|
+
if (!seenCard.has(own2)) seenCard.set(own2, el.line);
|
|
22115
|
+
for (const attr3 of RUNTIME_WIDGET_ATTRS) {
|
|
22116
|
+
if (!(el.attrs[attr3] ?? "").trim()) {
|
|
22117
|
+
violations.push({ rule: "R3.2", where: where2(el.line), message: `widget "${own2}" has no ${attr3}` });
|
|
22118
|
+
}
|
|
22119
|
+
}
|
|
22120
|
+
if (!(el.attrs[SPAN_MARK] ?? "").trim()) {
|
|
22121
|
+
violations.push({ rule: "R2.7", where: where2(el.line), message: `widget "${own2}" declares no ${SPAN_MARK}; every card on the kit's 12-column grid says how many columns it takes` });
|
|
22122
|
+
}
|
|
22123
|
+
const handler = twin.source || (el.attrs["data-dr-source"] ?? "").trim();
|
|
22124
|
+
for (const name of new Set([handler, (el.attrs["data-dr-source"] ?? "").trim()].filter((one) => one !== ""))) {
|
|
22125
|
+
if (!runtime.declared.has(name)) {
|
|
22126
|
+
violations.push({ rule: "R2.2", where: where2(el.line), message: `widget "${own2}" is posted to handler "${name}", which this package does not declare; the runtime's request for it would reach no handler` });
|
|
22127
|
+
}
|
|
22128
|
+
}
|
|
22129
|
+
const source = specSource(runtime.data, own2);
|
|
22130
|
+
widgets.push({
|
|
22131
|
+
id: own2,
|
|
22132
|
+
page: page.path,
|
|
22133
|
+
source: source.sentence,
|
|
22134
|
+
refs: source.refs,
|
|
22135
|
+
title: el.attrs["data-dr-widget-title"] ?? "",
|
|
22136
|
+
description: el.attrs["data-dr-widget-description"] ?? "",
|
|
22137
|
+
line: el.line,
|
|
22138
|
+
handler
|
|
22139
|
+
});
|
|
22140
|
+
}
|
|
22141
|
+
const contractIds = /* @__PURE__ */ new Map();
|
|
22142
|
+
for (const one of mounted) if (one.id !== "") contractIds.set(one.id, (contractIds.get(one.id) ?? 0) + 1);
|
|
22143
|
+
const said2 = /* @__PURE__ */ new Set();
|
|
22144
|
+
for (const one of [...pool.values()].flat()) {
|
|
22145
|
+
claim(one.id, one.card);
|
|
22146
|
+
if (one.id !== "" && contractIds.get(one.id) > 1) {
|
|
22147
|
+
if (said2.has(one.id)) continue;
|
|
22148
|
+
said2.add(one.id);
|
|
22149
|
+
violations.push({ rule: "R3.2", where: page.path, message: `#dr-contract mounts ${contractIds.get(one.id)} cards with id "${one.id}" on ${page.path}; dr-insights files each card's data under its id, so ids are unique within a page` });
|
|
22150
|
+
continue;
|
|
22151
|
+
}
|
|
22152
|
+
violations.push({
|
|
22153
|
+
rule: "R2.5",
|
|
22154
|
+
where: page.path,
|
|
22155
|
+
message: `#dr-contract mounts a card${one.id === "" ? "" : ` with id "${one.id}"`} ${one.card === null ? "naming no SPEC card" : `drawing "${one.card}"`} on ${page.path} that the skeleton does not carry; the runtime draws it, and a round trip of this page loses it`
|
|
22156
|
+
});
|
|
22157
|
+
}
|
|
22158
|
+
return { widgets, claimed, any: skeleton.length > 0 || mounted.length > 0 };
|
|
22159
|
+
}
|
|
22160
|
+
function checkRuntimeControls(page, elements, runtime, cards) {
|
|
22161
|
+
const lines = /* @__PURE__ */ new Map();
|
|
22162
|
+
for (const el of elements) {
|
|
22163
|
+
const id = (el.attrs["data-dr-id"] ?? "").trim();
|
|
22164
|
+
if ("data-dr-component" in el.attrs && id !== "" && !lines.has(id)) lines.set(id, el.line);
|
|
22165
|
+
}
|
|
22166
|
+
const { params } = contractPage(runtime.embedded, page.path);
|
|
22167
|
+
const rows2 = runtime.data !== null && typeof runtime.data?.widgets === "object" && runtime.data.widgets !== null ? runtime.data.widgets : {};
|
|
22168
|
+
const reads = (card, name) => {
|
|
22169
|
+
const row = Object.hasOwn(rows2, card) ? rows2[card] : void 0;
|
|
22170
|
+
return row !== null && typeof row === "object" && row.params !== null && typeof row.params === "object" && Object.hasOwn(row.params, name);
|
|
22171
|
+
};
|
|
21825
22172
|
const violations = [];
|
|
21826
|
-
|
|
22173
|
+
for (const part of contractParams(runtime.embedded, page.path)) {
|
|
22174
|
+
const where2 = lines.has(part.id) ? `${page.path}:${lines.get(part.id)}` : page.path;
|
|
22175
|
+
if (!params.has(part.name)) {
|
|
22176
|
+
violations.push({ rule: "R2.4", where: where2, message: `control "${part.name}" writes a param ${page.path} does not declare in #dr-contract (pages[].params); the runtime sends only the page's own params, so no card receives it` });
|
|
22177
|
+
} else if (!cards.some((card) => reads(card, part.name))) {
|
|
22178
|
+
violations.push({ rule: "R2.4", where: where2, message: `control "${part.name}" is read by no card on ${page.path}; a control re-reads the cards of its own page, so it would move a parameter nothing here shows` });
|
|
22179
|
+
}
|
|
22180
|
+
}
|
|
22181
|
+
return violations;
|
|
22182
|
+
}
|
|
22183
|
+
function checkLibraryDraws(page, library, scripts, missing = null) {
|
|
22184
|
+
const violations = [];
|
|
22185
|
+
if (missing !== null) {
|
|
22186
|
+
for (const one of RUNTIME_LIBRARIES.filter((candidate) => missing.includes(candidate.module))) {
|
|
22187
|
+
violations.push({
|
|
22188
|
+
rule: "R2.6",
|
|
22189
|
+
where: page.path,
|
|
22190
|
+
message: `a dashboard the app runtime renders draws through ${one.module}; this page loads no vendor/${one.module}@\u2026/${one.file}, and ${one.why}`
|
|
22191
|
+
});
|
|
22192
|
+
}
|
|
22193
|
+
for (const version of runtimeBelowMinimum(page)) {
|
|
22194
|
+
violations.push({
|
|
22195
|
+
rule: "R2.6",
|
|
22196
|
+
where: page.path,
|
|
22197
|
+
message: `this page loads dr-app-runtime ${version}, and a dashboard the app runtime renders needs ${MIN_DASHBOARD_RUNTIME} or later: card mode arrived in 1.12.0 and the filter bar's hooks in 1.13.0`
|
|
22198
|
+
});
|
|
22199
|
+
}
|
|
22200
|
+
} else if (!library) {
|
|
21827
22201
|
violations.push({
|
|
21828
22202
|
rule: "R2.6",
|
|
21829
22203
|
where: page.path,
|
|
@@ -21836,11 +22210,11 @@ function checkLibraryDraws(page, library, scripts) {
|
|
|
21836
22210
|
re.lastIndex = 0;
|
|
21837
22211
|
let match;
|
|
21838
22212
|
while ((match = re.exec(code)) !== null) {
|
|
21839
|
-
const
|
|
22213
|
+
const named13 = typeof what === "function" ? what(match) : what;
|
|
21840
22214
|
violations.push({
|
|
21841
22215
|
rule: "R2.6",
|
|
21842
22216
|
where: `${script.path}:${script.line + lineOffset(code, match.index)}`,
|
|
21843
|
-
message: `${
|
|
22217
|
+
message: `${named13} in a script this page runs; ${DRAWS_REASON}`
|
|
21844
22218
|
});
|
|
21845
22219
|
}
|
|
21846
22220
|
}
|
|
@@ -21862,10 +22236,14 @@ function checkLink(app, page, el, isKit, where2) {
|
|
|
21862
22236
|
}
|
|
21863
22237
|
return [];
|
|
21864
22238
|
}
|
|
21865
|
-
function collectScripts(app, page) {
|
|
22239
|
+
function collectScripts(app, page, runtime = null) {
|
|
21866
22240
|
const scripts = [];
|
|
21867
22241
|
const { elements, scripts: inline } = parseHtml(page.text);
|
|
21868
|
-
|
|
22242
|
+
const blocks = elements.filter((el) => el.tag === "script" && !("src" in el.attrs));
|
|
22243
|
+
inline.forEach((block, index) => {
|
|
22244
|
+
if (runtime !== null && blocks[index] !== void 0 && isEmbeddedContract(blocks[index], elements)) return;
|
|
22245
|
+
scripts.push({ path: page.path, line: block.line, text: block.text });
|
|
22246
|
+
});
|
|
21869
22247
|
for (const el of elements) {
|
|
21870
22248
|
if (el.tag !== "script" || !el.attrs.src) continue;
|
|
21871
22249
|
const path3 = resolveInBundle(page.path, el.attrs.src);
|
|
@@ -21938,9 +22316,13 @@ function checkControls(page, controls, scriptText, handlerText) {
|
|
|
21938
22316
|
}
|
|
21939
22317
|
return violations;
|
|
21940
22318
|
}
|
|
21941
|
-
function checkAgainstSpec(app, pages, contract) {
|
|
22319
|
+
function checkAgainstSpec(app, pages, contract, specKeys = null) {
|
|
22320
|
+
const runtimePages = new Set(pages.filter((page) => page.runtime === true).map((page) => page.path));
|
|
22321
|
+
const runtime = runtimePages.size > 0;
|
|
22322
|
+
const legacyPages = pages.filter((page) => !runtimePages.has(page.path));
|
|
22323
|
+
let orphans;
|
|
21942
22324
|
if (contract !== null) {
|
|
21943
|
-
|
|
22325
|
+
orphans = contract.bindings.orphan_widgets.filter((uid) => !runtimePages.has(contract.elements[uid].page)).map((uid) => {
|
|
21944
22326
|
const element = contract.elements[uid];
|
|
21945
22327
|
return {
|
|
21946
22328
|
rule: "R2.5",
|
|
@@ -21948,18 +22330,33 @@ function checkAgainstSpec(app, pages, contract) {
|
|
|
21948
22330
|
message: `widget "${element.id}" is not a key in the handler's SPEC; the page would ask for a widget the handler cannot answer for`
|
|
21949
22331
|
};
|
|
21950
22332
|
});
|
|
22333
|
+
} else {
|
|
22334
|
+
const spec = specWidgets(app.handlers);
|
|
22335
|
+
orphans = spec === null ? [] : legacyPages.flatMap(
|
|
22336
|
+
(page) => page.widgets.filter((widget) => !spec.has(widget.id)).map((widget) => ({ rule: "R2.5", where: `${page.path}:${widget.line}`, message: `widget "${widget.id}" is not a key in the handler's SPEC; the page would ask for a widget the handler cannot answer for` }))
|
|
22337
|
+
);
|
|
21951
22338
|
}
|
|
21952
|
-
|
|
21953
|
-
|
|
21954
|
-
|
|
21955
|
-
(page) => page.widgets.filter((widget) => !spec.has(widget.id)).map((widget) => ({ rule: "R2.5", where: `${page.path}:${widget.line}`, message: `widget "${widget.id}" is not a key in the handler's SPEC; the page would ask for a widget the handler cannot answer for` }))
|
|
22339
|
+
if (!runtime || specKeys === null) return orphans;
|
|
22340
|
+
const runtimeOrphans = pages.filter((page) => runtimePages.has(page.path)).flatMap(
|
|
22341
|
+
(page) => page.widgets.filter((widget) => !specKeys.has(widget.id)).map((widget) => ({ rule: "R2.5", where: `${page.path}:${widget.line}`, message: `widget "${widget.id}" is not a key in the handler's SPEC; the page would ask for a widget the handler cannot answer for` }))
|
|
21956
22342
|
);
|
|
22343
|
+
const drawn2 = new Set(pages.flatMap((page) => [...page.widgets.map((widget) => widget.id), ...page.claimed ?? []]));
|
|
22344
|
+
return [...orphans, ...runtimeOrphans, ...orphanSpecRows(app, [...specKeys].filter((key) => !drawn2.has(key)).sort())];
|
|
22345
|
+
}
|
|
22346
|
+
function orphanSpecRows(app, keys2) {
|
|
22347
|
+
const handler = app.handlers.find((one) => SPEC_TABLE.test(one.source)) ?? app.handlers[0];
|
|
22348
|
+
const file = handler?.file || app.handlersSource;
|
|
22349
|
+
return keys2.map((key) => ({
|
|
22350
|
+
rule: "R2.5",
|
|
22351
|
+
where: `${file}#SPEC.${key}`,
|
|
22352
|
+
message: `the SPEC declares "${key}", and no card on any page draws it (no [data-dr-component="widget"] names it, in ${SPEC_CARD} or in #dr-contract); the handler answers for a figure the dashboard never shows`
|
|
22353
|
+
}));
|
|
21957
22354
|
}
|
|
21958
22355
|
|
|
21959
22356
|
// ../flows/insights-builder/kit/checks/rules/handlers.mjs
|
|
21960
22357
|
var FINANCEOS_CALL = /ctx\.financeos\.[A-Za-z_][\w.]*\s*\(/;
|
|
21961
22358
|
var WIDGET_HELPER = /\bwidgets\.(?:serve|load|drill|catalog)\s*\(\s*ctx\b/;
|
|
21962
|
-
function checkHandlers(app, frontendText) {
|
|
22359
|
+
function checkHandlers(app, frontendText, cardHandlers = /* @__PURE__ */ new Set()) {
|
|
21963
22360
|
const violations = [];
|
|
21964
22361
|
if (app.handlers.length === 0) {
|
|
21965
22362
|
violations.push({ rule: "R1.2", where: app.handlersSource, message: "a dashboard has at least one handler reading FinanceOS; this app has none" });
|
|
@@ -21982,7 +22379,7 @@ function checkHandlers(app, frontendText) {
|
|
|
21982
22379
|
});
|
|
21983
22380
|
}
|
|
21984
22381
|
}
|
|
21985
|
-
if (!isReferenced2(handler.name, frontendText)) {
|
|
22382
|
+
if (!isReferenced2(handler.name, frontendText) && !cardHandlers.has(handler.name)) {
|
|
21986
22383
|
violations.push({ rule: "R2.2", where: where2, message: `handler "${handler.name}" is not called by any widget script` });
|
|
21987
22384
|
}
|
|
21988
22385
|
const signature = handler.source.match(/def\s+handle\s*\(([^)]*)\)/);
|
|
@@ -22081,7 +22478,11 @@ function checkSources2(widgets, catalog) {
|
|
|
22081
22478
|
if (!catalog && widgets.length > 0) skipped.push({ rule: "R3.3", reason: "no catalog in input; ids were not checked against the tenant" });
|
|
22082
22479
|
for (const widget of widgets) {
|
|
22083
22480
|
const where2 = `${widget.page ?? "ui/index.html"}:${widget.line}`;
|
|
22084
|
-
const refs = sourceRefs(widget.source);
|
|
22481
|
+
const refs = widget.refs ?? sourceRefs(widget.source);
|
|
22482
|
+
if (widget.refs !== void 0 && refs.length === 0) {
|
|
22483
|
+
skipped.push({ rule: "R3.3", reason: `widget "${widget.id}" on ${where2}: its SPEC row reads no metric or table this verifier can follow; its source was not checked` });
|
|
22484
|
+
continue;
|
|
22485
|
+
}
|
|
22085
22486
|
if (refs.length === 0) {
|
|
22086
22487
|
violations.push({ rule: "R3.3", where: where2, message: `widget "${widget.id}" source names no metric or table id: ${JSON.stringify(widget.source)}` });
|
|
22087
22488
|
continue;
|
|
@@ -22329,7 +22730,7 @@ function verify({ app: appRef, catalog, agent, describe: describe7, load }, { ex
|
|
|
22329
22730
|
});
|
|
22330
22731
|
const violations = [
|
|
22331
22732
|
...checkManifest(app),
|
|
22332
|
-
...checkHandlers(app, frontend.scriptText),
|
|
22733
|
+
...checkHandlers(app, frontend.scriptText, frontend.cardHandlers),
|
|
22333
22734
|
...frontend.violations,
|
|
22334
22735
|
...theme.violations,
|
|
22335
22736
|
...sources.violations,
|
|
@@ -22660,7 +23061,7 @@ var TITLED_TYPES2 = Object.freeze(["widget", "form", "list", "queue", "stat", "c
|
|
|
22660
23061
|
var CONTROL_PARTS2 = Object.freeze(["number_input", "select", "date_picker", "toggle", "control_group", "slider", "checkbox", "radio_group", "date_range", "fiscal_month"]);
|
|
22661
23062
|
|
|
22662
23063
|
// ../flows/appspace-builder/kit/contract/bundleScan.mjs
|
|
22663
|
-
var
|
|
23064
|
+
var COMPONENT_ATTR3 = "data-dr-component";
|
|
22664
23065
|
var ENTITY_ATTR2 = "data-dr-entity";
|
|
22665
23066
|
var ACTION_ATTR2 = "data-dr-action";
|
|
22666
23067
|
var SOURCE_ATTR2 = "data-dr-source";
|
|
@@ -23393,7 +23794,7 @@ function readSpec2(handlers) {
|
|
|
23393
23794
|
const where2 = handler.file || `handlers.json#${handler.name}`;
|
|
23394
23795
|
const literals = new Literals2(handler.source, "py");
|
|
23395
23796
|
const value = literals.constant("SPEC");
|
|
23396
|
-
if (!
|
|
23797
|
+
if (!isPlainObject12(value)) {
|
|
23397
23798
|
unresolved.push({ where: where2, key: "SPEC", text: literals.constantText("SPEC") ?? cut2("SPEC") });
|
|
23398
23799
|
continue;
|
|
23399
23800
|
}
|
|
@@ -23409,15 +23810,15 @@ function readSpec2(handlers) {
|
|
|
23409
23810
|
var DEFAULTS_KEY2 = "_defaults";
|
|
23410
23811
|
function effective2(table) {
|
|
23411
23812
|
const defaults = table[DEFAULTS_KEY2];
|
|
23412
|
-
if (!
|
|
23813
|
+
if (!isPlainObject12(defaults)) return table;
|
|
23413
23814
|
const out = /* @__PURE__ */ Object.create(null);
|
|
23414
23815
|
for (const [key, row] of Object.entries(table)) {
|
|
23415
23816
|
if (key === DEFAULTS_KEY2) continue;
|
|
23416
|
-
out[key] = key.startsWith("_") || !
|
|
23817
|
+
out[key] = key.startsWith("_") || !isPlainObject12(row) ? row : { ...defaults, ...row };
|
|
23417
23818
|
}
|
|
23418
23819
|
return out;
|
|
23419
23820
|
}
|
|
23420
|
-
function
|
|
23821
|
+
function isPlainObject12(value) {
|
|
23421
23822
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
23422
23823
|
}
|
|
23423
23824
|
|
|
@@ -23509,7 +23910,7 @@ function readTransforms2(source) {
|
|
|
23509
23910
|
if (!literals.has(TRANSFORMS_TABLE2)) return { found: false, texts: null, span: null, text: null };
|
|
23510
23911
|
const value = literals.constant(TRANSFORMS_TABLE2);
|
|
23511
23912
|
const span = literals.constantSpan(TRANSFORMS_TABLE2);
|
|
23512
|
-
const readable =
|
|
23913
|
+
const readable = isPlainObject12(value) && Object.entries(value).every(([path3, text3]) => TRANSFORM_PATH5.test(path3) && typeof text3 === "string");
|
|
23513
23914
|
if (!readable) return { found: true, texts: null, span, text: literals.constantText(TRANSFORMS_TABLE2) };
|
|
23514
23915
|
const texts = {};
|
|
23515
23916
|
for (const path3 of Object.keys(value).sort(natural4)) texts[path3] = value[path3];
|
|
@@ -23596,7 +23997,7 @@ function extract2(app, { describe: describe7 } = {}) {
|
|
|
23596
23997
|
for (const page of app.pages) {
|
|
23597
23998
|
const built = readPage2(app, page, spec, elements, problems);
|
|
23598
23999
|
if (shell === null && built.shell !== null && built.shell !== void 0) shell = built.shell;
|
|
23599
|
-
if (built.page.owned !== true && !page.text.includes(
|
|
24000
|
+
if (built.page.owned !== true && !page.text.includes(COMPONENT_ATTR3)) {
|
|
23600
24001
|
readBundle2(app, page.path, built.page.scripts, elements, problems);
|
|
23601
24002
|
}
|
|
23602
24003
|
pages.push(built.page);
|
|
@@ -23678,7 +24079,7 @@ function appSection2(app, manifest) {
|
|
|
23678
24079
|
for (const handler of app.handlers) {
|
|
23679
24080
|
handlers[handler.name] = { http: handler.http || null, entrypoint: handler.file || null };
|
|
23680
24081
|
}
|
|
23681
|
-
const nav =
|
|
24082
|
+
const nav = isPlainObject12(manifest.nav) ? manifest.nav : {};
|
|
23682
24083
|
return {
|
|
23683
24084
|
id: manifest.id ?? null,
|
|
23684
24085
|
name: manifest.name ?? null,
|
|
@@ -23694,7 +24095,7 @@ function appSection2(app, manifest) {
|
|
|
23694
24095
|
api_version: manifest.apiVersion ?? manifest.api_version ?? null,
|
|
23695
24096
|
routes: routesOf3(manifest),
|
|
23696
24097
|
handlers,
|
|
23697
|
-
capabilities:
|
|
24098
|
+
capabilities: isPlainObject12(manifest.capabilities) ? manifest.capabilities : {},
|
|
23698
24099
|
data_model: app.dataModel ? normaliseModel2(app.dataModel) : null,
|
|
23699
24100
|
theme: themeSection2(app)
|
|
23700
24101
|
};
|
|
@@ -23795,13 +24196,13 @@ function readPage2(app, page, spec, elements, problems) {
|
|
|
23795
24196
|
if (inherited !== null) element.props.entity = inherited;
|
|
23796
24197
|
}
|
|
23797
24198
|
for (const node of tree.nodes) {
|
|
23798
|
-
const declared = node.attrs[
|
|
24199
|
+
const declared = node.attrs[COMPONENT_ATTR3];
|
|
23799
24200
|
if (declared === void 0 || COMPONENT_TYPE_SET2.has(declared)) continue;
|
|
23800
24201
|
problems.push({
|
|
23801
24202
|
code: "unknown_component_type",
|
|
23802
24203
|
where: `${page.path}:${node.line}`,
|
|
23803
24204
|
lossy: false,
|
|
23804
|
-
message: `${
|
|
24205
|
+
message: `${COMPONENT_ATTR3}="${declared}" on <${node.tag}> is not a component type (${COMPONENT_TYPES5.join(", ")}); nothing holds it to a table or a handler`
|
|
23805
24206
|
});
|
|
23806
24207
|
}
|
|
23807
24208
|
if (!tree.well_formed) {
|
|
@@ -23878,11 +24279,11 @@ function paramsOf2(page) {
|
|
|
23878
24279
|
function mountOf2(page) {
|
|
23879
24280
|
const entry = embeddedPageOf2(page);
|
|
23880
24281
|
const value = entry?.mount;
|
|
23881
|
-
if (!
|
|
24282
|
+
if (!isPlainObject12(value)) return null;
|
|
23882
24283
|
return {
|
|
23883
24284
|
handler: typeof value.handler === "string" ? value.handler : null,
|
|
23884
24285
|
prefs: typeof value.prefs === "boolean" ? value.prefs : null,
|
|
23885
|
-
defaults:
|
|
24286
|
+
defaults: isPlainObject12(value.defaults) ? value.defaults : null
|
|
23886
24287
|
};
|
|
23887
24288
|
}
|
|
23888
24289
|
function embeddedContractOf2(page) {
|
|
@@ -23890,7 +24291,7 @@ function embeddedContractOf2(page) {
|
|
|
23890
24291
|
if (found === null) return null;
|
|
23891
24292
|
try {
|
|
23892
24293
|
const embedded = JSON.parse((found[1] ?? "").replace(/<\\\//g, "</"));
|
|
23893
|
-
return
|
|
24294
|
+
return isPlainObject12(embedded) ? embedded : null;
|
|
23894
24295
|
} catch {
|
|
23895
24296
|
return null;
|
|
23896
24297
|
}
|
|
@@ -23903,7 +24304,7 @@ function rendererOf2(pages) {
|
|
|
23903
24304
|
for (const page of pages ?? []) {
|
|
23904
24305
|
const embedded = embeddedContractOf2(page);
|
|
23905
24306
|
if (embedded === null) continue;
|
|
23906
|
-
return embedded.app?.renderer ===
|
|
24307
|
+
return embedded.app?.renderer === RUNTIME_RENDERER4 ? { renderer: RUNTIME_RENDERER4 } : {};
|
|
23907
24308
|
}
|
|
23908
24309
|
return {};
|
|
23909
24310
|
}
|
|
@@ -23911,7 +24312,7 @@ function cardSpecOf2(element) {
|
|
|
23911
24312
|
const card = element?.props?.card;
|
|
23912
24313
|
return element?.type === "widget" && typeof card === "string" && card !== "" ? card : null;
|
|
23913
24314
|
}
|
|
23914
|
-
var
|
|
24315
|
+
var RUNTIME_RENDERER4 = "runtime";
|
|
23915
24316
|
function navGates2(text3, pagePath) {
|
|
23916
24317
|
const out = /* @__PURE__ */ new Map();
|
|
23917
24318
|
if (typeof text3 !== "string") return out;
|
|
@@ -23949,7 +24350,7 @@ function isDashboardCard2(element) {
|
|
|
23949
24350
|
function classify2(node) {
|
|
23950
24351
|
if (WIDGET_ATTR2 in node.attrs) return { type: "widget", role: node.attrs[WIDGET_ATTR2 + "-kind"] ?? null };
|
|
23951
24352
|
if (CONTROL_ATTR2 in node.attrs) return { type: "control", role: node.attrs[CONTROL_ATTR2 + "-kind"] ?? null };
|
|
23952
|
-
if (COMPONENT_TYPE_SET2.has(node.attrs[
|
|
24353
|
+
if (COMPONENT_TYPE_SET2.has(node.attrs[COMPONENT_ATTR3])) return { type: node.attrs[COMPONENT_ATTR3], role: null };
|
|
23953
24354
|
if (FIELD_ATTR2 in node.attrs) return { type: "field", role: null };
|
|
23954
24355
|
if (KIT_ATTR2 in node.attrs) {
|
|
23955
24356
|
const role = node.attrs[KIT_ATTR2];
|
|
@@ -23979,25 +24380,25 @@ function partCalls2(element) {
|
|
|
23979
24380
|
const typed = element.props?.props ?? {};
|
|
23980
24381
|
const out = [];
|
|
23981
24382
|
for (const prop of Object.keys(PARTS2[element.type].events ?? {})) {
|
|
23982
|
-
if (prop !== "on_row_action" &&
|
|
24383
|
+
if (prop !== "on_row_action" && named7(typed[prop])) out.push(typed[prop]);
|
|
23983
24384
|
}
|
|
23984
24385
|
for (const action of Array.isArray(typed.row_actions) ? typed.row_actions : []) {
|
|
23985
|
-
if (action && typeof action === "object" &&
|
|
24386
|
+
if (action && typeof action === "object" && named7(action.call)) out.push(action.call);
|
|
23986
24387
|
}
|
|
23987
24388
|
const bulk = typed.selection && typeof typed.selection === "object" ? typed.selection.bulk_actions : void 0;
|
|
23988
24389
|
for (const action of Array.isArray(bulk) ? bulk : []) {
|
|
23989
|
-
if (action && typeof action === "object" &&
|
|
24390
|
+
if (action && typeof action === "object" && named7(action.call)) out.push(action.call);
|
|
23990
24391
|
}
|
|
23991
24392
|
for (const column of Array.isArray(typed.columns) ? typed.columns : []) {
|
|
23992
|
-
if (column && typeof column === "object" && column.cell === "toggle" &&
|
|
24393
|
+
if (column && typeof column === "object" && column.cell === "toggle" && named7(column.call)) out.push(column.call);
|
|
23993
24394
|
}
|
|
23994
|
-
if (element.type === "line_grid" &&
|
|
24395
|
+
if (element.type === "line_grid" && named7(typed.action)) out.push(typed.action);
|
|
23995
24396
|
return out;
|
|
23996
24397
|
}
|
|
23997
24398
|
function partReads2(element) {
|
|
23998
24399
|
const typed = element.props?.props ?? {};
|
|
23999
24400
|
if (element.type !== "document") return [];
|
|
24000
|
-
return (Array.isArray(typed.sections) ? typed.sections : []).filter((section) => section && typeof section === "object" &&
|
|
24401
|
+
return (Array.isArray(typed.sections) ? typed.sections : []).filter((section) => section && typeof section === "object" && named7(section.source)).map((section) => section.source);
|
|
24001
24402
|
}
|
|
24002
24403
|
function isPart3(element) {
|
|
24003
24404
|
return typeof element?.type === "string" && Object.prototype.hasOwnProperty.call(PARTS2, element.type);
|
|
@@ -24067,9 +24468,9 @@ function propsOf2(node, text3, tree) {
|
|
|
24067
24468
|
const label = textOf2(text3, node);
|
|
24068
24469
|
if (label) props.label = label;
|
|
24069
24470
|
}
|
|
24070
|
-
if (
|
|
24471
|
+
if (COMPONENT_ATTR3 in node.attrs || FIELD_ATTR2 in node.attrs) {
|
|
24071
24472
|
for (const [attr3, key] of COMPONENT_PROPS5) if (attr3 in node.attrs) props[key] = node.attrs[attr3];
|
|
24072
|
-
if (node.attrs[
|
|
24473
|
+
if (node.attrs[COMPONENT_ATTR3] === "widget" && named7(node.attrs[SPEC_CARD_ATTR3])) props.card = node.attrs[SPEC_CARD_ATTR3];
|
|
24073
24474
|
if (FILTER_ATTR2 in node.attrs) props.filter = parseFilter2(node.attrs[FILTER_ATTR2]);
|
|
24074
24475
|
if (ARGS_ATTR2 in node.attrs) props.args = parseArgs2(node.attrs[ARGS_ATTR2]);
|
|
24075
24476
|
if (PROPS_ATTR2 in node.attrs) props.props = parseProps2(node.attrs[PROPS_ATTR2]);
|
|
@@ -24170,7 +24571,7 @@ function firstTag2(tree, node, tag, outer = false) {
|
|
|
24170
24571
|
for (const child of node.children) {
|
|
24171
24572
|
const candidate = tree.nodes[child];
|
|
24172
24573
|
if (candidate.tag === tag) return candidate;
|
|
24173
|
-
if (outer && (
|
|
24574
|
+
if (outer && (COMPONENT_ATTR3 in candidate.attrs || FIELD_ATTR2 in candidate.attrs)) continue;
|
|
24174
24575
|
const found = firstTag2(tree, candidate, tag, outer);
|
|
24175
24576
|
if (found) return found;
|
|
24176
24577
|
}
|
|
@@ -24186,7 +24587,7 @@ function parseJsonAttr2(value) {
|
|
|
24186
24587
|
}
|
|
24187
24588
|
function shellOf2(main2) {
|
|
24188
24589
|
const shell = parseJsonAttr2(main2?.attrs[SHELL_ATTR2]);
|
|
24189
|
-
return
|
|
24590
|
+
return isPlainObject12(shell) ? shell : null;
|
|
24190
24591
|
}
|
|
24191
24592
|
function navOf2(tree, main2) {
|
|
24192
24593
|
const nav = tree.nodes.find((node) => node.tag === "nav" && node.attrs[KIT_ATTR2] === "nav" && node.attrs[NAV_COUNTS_ATTR2] !== void 0);
|
|
@@ -24246,7 +24647,7 @@ function readMount2(scripts, problems) {
|
|
|
24246
24647
|
const brace = script.text.indexOf("{", call);
|
|
24247
24648
|
if (brace === -1) continue;
|
|
24248
24649
|
const value = new Literals2(script.text, "js").valueAtChar(brace);
|
|
24249
|
-
if (!
|
|
24650
|
+
if (!isPlainObject12(value)) {
|
|
24250
24651
|
problems.push({
|
|
24251
24652
|
code: "mount_unreadable",
|
|
24252
24653
|
where: script.path,
|
|
@@ -24258,7 +24659,7 @@ function readMount2(scripts, problems) {
|
|
|
24258
24659
|
return {
|
|
24259
24660
|
handler: typeof value.handler === "string" ? value.handler : null,
|
|
24260
24661
|
prefs: typeof value.prefs === "boolean" ? value.prefs : null,
|
|
24261
|
-
defaults:
|
|
24662
|
+
defaults: isPlainObject12(value.defaults) ? value.defaults : null
|
|
24262
24663
|
};
|
|
24263
24664
|
}
|
|
24264
24665
|
return null;
|
|
@@ -24289,10 +24690,10 @@ function bind2(elements, spec, backend, model) {
|
|
|
24289
24690
|
widgets[key] = { component: null, components: [], spec: key, bound: false };
|
|
24290
24691
|
}
|
|
24291
24692
|
const runtimeCards = uids.filter((uid) => !isDashboardCard2(elements[uid]) && cardSpecOf2(elements[uid]) !== null);
|
|
24292
|
-
const
|
|
24693
|
+
const own2 = (table, key) => Object.hasOwn(table, key) ? table[key] : void 0;
|
|
24293
24694
|
for (const uid of [...cards, ...runtimeCards].sort(natural5)) {
|
|
24294
24695
|
const id = isDashboardCard2(elements[uid]) ? elements[uid].id : cardSpecOf2(elements[uid]);
|
|
24295
|
-
const entry =
|
|
24696
|
+
const entry = own2(widgets, id) ?? (widgets[id] = { component: null, components: [], spec: null, bound: false });
|
|
24296
24697
|
entry.components.push(uid);
|
|
24297
24698
|
if (entry.component === null) entry.component = uid;
|
|
24298
24699
|
entry.bound = entry.spec !== null;
|
|
@@ -24303,19 +24704,19 @@ function bind2(elements, spec, backend, model) {
|
|
|
24303
24704
|
if (element.type !== "control") continue;
|
|
24304
24705
|
const name = element.binding;
|
|
24305
24706
|
if (!name) continue;
|
|
24306
|
-
const entry =
|
|
24707
|
+
const entry = own2(controls, name) ?? (controls[name] = { components: [], widgets: [], bound: false });
|
|
24307
24708
|
entry.components.push(uid);
|
|
24308
24709
|
}
|
|
24309
24710
|
for (const uid of cards) {
|
|
24310
24711
|
for (const param of elements[uid].props.params ?? []) {
|
|
24311
|
-
const entry =
|
|
24712
|
+
const entry = own2(controls, param) ?? (controls[param] = { components: [], widgets: [], bound: false });
|
|
24312
24713
|
if (!entry.widgets.includes(elements[uid].id)) entry.widgets.push(elements[uid].id);
|
|
24313
24714
|
}
|
|
24314
24715
|
}
|
|
24315
24716
|
for (const [key, row] of Object.entries(spec.table)) {
|
|
24316
|
-
if (!isWidgetKey2(key) || !
|
|
24717
|
+
if (!isWidgetKey2(key) || !isPlainObject12(row) || !isPlainObject12(row.params)) continue;
|
|
24317
24718
|
for (const param of Object.keys(row.params)) {
|
|
24318
|
-
const entry =
|
|
24719
|
+
const entry = own2(controls, param) ?? (controls[param] = { components: [], widgets: [], bound: false });
|
|
24319
24720
|
if (!entry.widgets.includes(key)) entry.widgets.push(key);
|
|
24320
24721
|
}
|
|
24321
24722
|
}
|
|
@@ -24355,12 +24756,12 @@ function bindComponents2(elements, { backend = null, model = null } = {}) {
|
|
|
24355
24756
|
if (!isComponentType2(element.type) || isDashboardCard2(element)) continue;
|
|
24356
24757
|
const props = element.props ?? {};
|
|
24357
24758
|
const entity = typeof props.entity === "string" && props.entity !== "" ? props.entity : null;
|
|
24358
|
-
if (
|
|
24359
|
-
if (
|
|
24759
|
+
if (named7(props.action)) actionRow(props.action).components.push(uid);
|
|
24760
|
+
if (named7(props.source)) actionRow(props.source).reads.push(uid);
|
|
24360
24761
|
if (isPart3(element)) for (const name of partCalls2(element)) actionRow(name).components.push(uid);
|
|
24361
24762
|
if (isPart3(element)) for (const name of partReads2(element)) actionRow(name).reads.push(uid);
|
|
24362
24763
|
if (entity !== null && element.type !== "field") entityRow(entity).components.push(uid);
|
|
24363
|
-
if (!
|
|
24764
|
+
if (!named7(props.field)) continue;
|
|
24364
24765
|
const column = entity === null ? null : (tables[entity]?.columns ?? {})[props.field] ?? null;
|
|
24365
24766
|
const injected = entity !== null && (has(tables, entity) && INJECTED_COLUMNS3.includes(props.field) || platform(entity) && PLATFORM_TABLES3[entity].includes(props.field));
|
|
24366
24767
|
const key = `${entity ?? ""}.${props.field}`;
|
|
@@ -24387,7 +24788,7 @@ function bindComponents2(elements, { backend = null, model = null } = {}) {
|
|
|
24387
24788
|
unreached_mutations: Object.keys(facts).filter((name) => (facts[name].verbs ?? []).some((verb) => MUTATING_VERBS2.has(verb)) && !reached.has(name)).sort(natural5)
|
|
24388
24789
|
};
|
|
24389
24790
|
}
|
|
24390
|
-
function
|
|
24791
|
+
function named7(value) {
|
|
24391
24792
|
return typeof value === "string" && value !== "";
|
|
24392
24793
|
}
|
|
24393
24794
|
function isWidgetKey2(key) {
|
|
@@ -24425,7 +24826,7 @@ function componentProblems2(elements, bindings, { handlers = [], handlersSource
|
|
|
24425
24826
|
for (const uid of Object.keys(elements).sort(natural5)) {
|
|
24426
24827
|
const element = elements[uid];
|
|
24427
24828
|
if (element.type !== "form") continue;
|
|
24428
|
-
const missing = [ACTION_ATTR2, ENTITY_ATTR2].filter((attr3) => !
|
|
24829
|
+
const missing = [ACTION_ATTR2, ENTITY_ATTR2].filter((attr3) => !named7(element.props[attr3 === ACTION_ATTR2 ? "action" : "entity"]));
|
|
24429
24830
|
if (missing.length === 0) continue;
|
|
24430
24831
|
problems.push({
|
|
24431
24832
|
code: "unbound_form",
|
|
@@ -24447,8 +24848,8 @@ function componentProblems2(elements, bindings, { handlers = [], handlersSource
|
|
|
24447
24848
|
for (const uid of Object.keys(elements).sort(natural5)) {
|
|
24448
24849
|
const element = elements[uid];
|
|
24449
24850
|
const filter = element.props?.filter;
|
|
24450
|
-
if (!isComponentType2(element.type) || !
|
|
24451
|
-
const entity =
|
|
24851
|
+
if (!isComponentType2(element.type) || !isPlainObject12(filter)) continue;
|
|
24852
|
+
const entity = named7(element.props.entity) ? element.props.entity : null;
|
|
24452
24853
|
if (entity === null || !Object.prototype.hasOwnProperty.call(tables, entity)) continue;
|
|
24453
24854
|
const columns = tables[entity].columns ?? {};
|
|
24454
24855
|
for (const column of Object.keys(filter).sort()) {
|
|
@@ -24464,7 +24865,7 @@ function componentProblems2(elements, bindings, { handlers = [], handlersSource
|
|
|
24464
24865
|
return problems;
|
|
24465
24866
|
}
|
|
24466
24867
|
function readBundle2(app, pagePath, scripts, elements, problems) {
|
|
24467
|
-
const files = scripts.filter((path3) => path3.endsWith(".js")).map((path3) => app.ui.find((file) => file.path === path3)).filter((file) => file && file.text.includes(
|
|
24868
|
+
const files = scripts.filter((path3) => path3.endsWith(".js")).map((path3) => app.ui.find((file) => file.path === path3)).filter((file) => file && file.text.includes(COMPONENT_ATTR3));
|
|
24468
24869
|
let found = 0;
|
|
24469
24870
|
for (const file of files) {
|
|
24470
24871
|
for (const element of scanBundle2(file.text, { file: file.path, page: pagePath })) {
|
|
@@ -24496,7 +24897,7 @@ function dataSection2(app, manifest, spec, elements, describe7, problems) {
|
|
|
24496
24897
|
...Object.keys(sources).length > 0 ? { sources } : {}
|
|
24497
24898
|
};
|
|
24498
24899
|
if (describe7) {
|
|
24499
|
-
const widgets2 =
|
|
24900
|
+
const widgets2 = isPlainObject12(describe7.widgets) ? describe7.widgets : {};
|
|
24500
24901
|
const missing = Object.keys(spec.table).filter(isWidgetKey2).filter((key) => !(key in widgets2));
|
|
24501
24902
|
if (spec.found && spec.unresolved.length === 0 && missing.length > 0) {
|
|
24502
24903
|
problems.push({
|
|
@@ -24545,12 +24946,12 @@ function readSources2(handlers) {
|
|
|
24545
24946
|
if (!SOURCES_TABLE2.test(source) || HANDLER_LITERAL2.test(source)) continue;
|
|
24546
24947
|
const literals = new Literals2(source, "py");
|
|
24547
24948
|
const table = literals.constant("SOURCES");
|
|
24548
|
-
if (!
|
|
24949
|
+
if (!isPlainObject12(table)) continue;
|
|
24549
24950
|
for (const [name, row] of Object.entries(table)) {
|
|
24550
|
-
if (
|
|
24951
|
+
if (isPlainObject12(row) && sources[name] === void 0) sources[name] = row;
|
|
24551
24952
|
}
|
|
24552
24953
|
const carried = literals.constant("TRANSFORMS");
|
|
24553
|
-
if (!
|
|
24954
|
+
if (!isPlainObject12(carried)) continue;
|
|
24554
24955
|
for (const [name, text3] of Object.entries(carried)) {
|
|
24555
24956
|
const path3 = table[name]?.transform;
|
|
24556
24957
|
if (typeof path3 === "string" && typeof text3 === "string" && !texts.has(path3)) texts.set(path3, text3);
|
|
@@ -24585,7 +24986,7 @@ function specTransforms2(handlers, spec, problems) {
|
|
|
24585
24986
|
}
|
|
24586
24987
|
const unreadable = problems.some((problem5) => problem5.code === "transform_unreadable");
|
|
24587
24988
|
for (const key of Object.keys(spec.table).filter(isWidgetKey2).sort(natural5)) {
|
|
24588
|
-
const path3 =
|
|
24989
|
+
const path3 = isPlainObject12(spec.table[key]) ? spec.table[key].transform : void 0;
|
|
24589
24990
|
if (typeof path3 !== "string" || unreadable || carried !== null && carried.has(path3)) continue;
|
|
24590
24991
|
problems.push({
|
|
24591
24992
|
code: "transform_not_carried",
|
|
@@ -24609,7 +25010,7 @@ function readApp2(handlers, elements, manifest = null) {
|
|
|
24609
25010
|
let row = null;
|
|
24610
25011
|
if (HANDLER_LITERAL2.test(source)) {
|
|
24611
25012
|
const value = new Literals2(source, "py").constant("HANDLER");
|
|
24612
|
-
if (
|
|
25013
|
+
if (isPlainObject12(value)) row = value;
|
|
24613
25014
|
}
|
|
24614
25015
|
declared[handler.name] = { role: "custom", ...row ?? {} };
|
|
24615
25016
|
if (declared[handler.name].requiresRole === void 0) {
|
|
@@ -24637,9 +25038,9 @@ function screensOf2(elements, order2 = null) {
|
|
|
24637
25038
|
screens.push({
|
|
24638
25039
|
page: element.page,
|
|
24639
25040
|
component: element.type,
|
|
24640
|
-
id:
|
|
24641
|
-
entity:
|
|
24642
|
-
handler:
|
|
25041
|
+
id: named7(props.id) ? props.id : element.id ?? null,
|
|
25042
|
+
entity: named7(props.entity) ? props.entity : entityAbove2(elements, element),
|
|
25043
|
+
handler: named7(props.source) ? props.source : named7(props.action) ? props.action : null
|
|
24643
25044
|
});
|
|
24644
25045
|
}
|
|
24645
25046
|
return screens;
|
|
@@ -24651,7 +25052,7 @@ function entityAbove2(elements, element) {
|
|
|
24651
25052
|
seen.add(at);
|
|
24652
25053
|
const above = elements[at];
|
|
24653
25054
|
if (above === void 0) return null;
|
|
24654
|
-
if (
|
|
25055
|
+
if (named7(above.props?.entity)) return above.props.entity;
|
|
24655
25056
|
at = above.parent ?? null;
|
|
24656
25057
|
}
|
|
24657
25058
|
return null;
|
|
@@ -24680,7 +25081,7 @@ function backendSection2(app, manifest, pages, elements) {
|
|
|
24680
25081
|
if (!isComponentType2(element.type) || isDashboardCard2(element)) continue;
|
|
24681
25082
|
for (const key of ["action", "source"]) {
|
|
24682
25083
|
const name = element.props?.[key];
|
|
24683
|
-
if (!
|
|
25084
|
+
if (!named7(name)) continue;
|
|
24684
25085
|
record2(name, "attribute");
|
|
24685
25086
|
attributed.add(name);
|
|
24686
25087
|
}
|
|
@@ -24707,14 +25108,14 @@ function backendSection2(app, manifest, pages, elements) {
|
|
|
24707
25108
|
data_model: model,
|
|
24708
25109
|
handlers,
|
|
24709
25110
|
roles,
|
|
24710
|
-
capabilities:
|
|
25111
|
+
capabilities: isPlainObject12(manifest.capabilities) ? manifest.capabilities : {},
|
|
24711
25112
|
ui: {
|
|
24712
25113
|
entry,
|
|
24713
25114
|
handlers_called: [...called].sort(),
|
|
24714
25115
|
handlers_called_via: [...via.values()].sort((a, b) => natural5(a.name, b.name) || natural5(a.via, b.via)),
|
|
24715
25116
|
scripts_loaded: [...new Set(pages.flatMap((page) => page.scripts))].sort()
|
|
24716
25117
|
},
|
|
24717
|
-
connect_tables:
|
|
25118
|
+
connect_tables: isPlainObject12(manifest.connectTables) ? manifest.connectTables : isPlainObject12(manifest.connect_tables) ? manifest.connect_tables : {},
|
|
24718
25119
|
invariants: invariants2(model, roles, handlers)
|
|
24719
25120
|
};
|
|
24720
25121
|
}
|
|
@@ -24728,7 +25129,7 @@ function callsOnTable2(source, verb) {
|
|
|
24728
25129
|
return holders.some((holder) => new RegExp(`(?:^|[^\\w.])(?:${holder})\\s*\\.\\s*${verb}\\s*\\(`, "m").test(source));
|
|
24729
25130
|
}
|
|
24730
25131
|
function declaredHandler2(manifest, name) {
|
|
24731
|
-
return
|
|
25132
|
+
return isPlainObject12(manifest?.handlers) ? manifest.handlers[name] ?? null : null;
|
|
24732
25133
|
}
|
|
24733
25134
|
function signatureOk2(source) {
|
|
24734
25135
|
const code = source.split("\n").map((line2) => /^\s*#/.test(line2) ? "" : line2).join("\n");
|
|
@@ -24755,7 +25156,7 @@ function rolesOf3(manifest) {
|
|
|
24755
25156
|
// Declaration order IS the ladder: `authz.py` reads the manifest's list
|
|
24756
25157
|
// top down, so the first role is the most junior.
|
|
24757
25158
|
seniority: index,
|
|
24758
|
-
tables:
|
|
25159
|
+
tables: isPlainObject12(role?.tables) ? role.tables : {},
|
|
24759
25160
|
// Stated only when the manifest states it, like the platform's own
|
|
24760
25161
|
// derivation (`app_contract_store._roles_section`): the norm is a role
|
|
24761
25162
|
// that sees only its own screens, and `[]` on every role would say
|
|
@@ -24767,16 +25168,16 @@ function rolesOf3(manifest) {
|
|
|
24767
25168
|
}
|
|
24768
25169
|
function pageRolesOf2(manifest, path3) {
|
|
24769
25170
|
const pages = Array.isArray(manifest?.pages) ? manifest.pages : [];
|
|
24770
|
-
const entry = pages.find((one) =>
|
|
25171
|
+
const entry = pages.find((one) => isPlainObject12(one) && one.path === path3);
|
|
24771
25172
|
const roles = Array.isArray(entry?.roles) ? entry.roles.filter((role) => typeof role === "string" && role !== "") : [];
|
|
24772
25173
|
return roles.length > 0 ? { roles } : {};
|
|
24773
25174
|
}
|
|
24774
25175
|
function normaliseModel2(document) {
|
|
24775
25176
|
const tables = {};
|
|
24776
|
-
const raw =
|
|
25177
|
+
const raw = isPlainObject12(document?.tables) ? document.tables : {};
|
|
24777
25178
|
for (const [name, table] of Object.entries(raw)) {
|
|
24778
25179
|
const columns = {};
|
|
24779
|
-
for (const [column, spec] of Object.entries(
|
|
25180
|
+
for (const [column, spec] of Object.entries(isPlainObject12(table?.columns) ? table.columns : {})) {
|
|
24780
25181
|
const pk = Boolean(spec?.pk);
|
|
24781
25182
|
columns[column] = {
|
|
24782
25183
|
type: spec?.type ?? null,
|
|
@@ -24794,7 +25195,7 @@ function normaliseModel2(document) {
|
|
|
24794
25195
|
// `computed` (a child total the platform selects with the row) and
|
|
24795
25196
|
// `matches` (the column it should agree with), by `display`'s rule:
|
|
24796
25197
|
// only where stated, so a model without them normalises to its bytes.
|
|
24797
|
-
...
|
|
25198
|
+
...isPlainObject12(spec?.computed) ? { computed: spec.computed } : {},
|
|
24798
25199
|
...typeof spec?.matches === "string" && spec.matches !== "" ? { matches: spec.matches } : {}
|
|
24799
25200
|
};
|
|
24800
25201
|
}
|
|
@@ -24812,7 +25213,7 @@ function normaliseModel2(document) {
|
|
|
24812
25213
|
// row reads as before it is first saved. By `audit`'s rule: only where
|
|
24813
25214
|
// stated, so a model with neither keeps normalising to its bytes.
|
|
24814
25215
|
...table?.singleton === true ? { singleton: true } : {},
|
|
24815
|
-
...table?.singleton === true &&
|
|
25216
|
+
...table?.singleton === true && isPlainObject12(table?.defaults) ? { defaults: table.defaults } : {},
|
|
24816
25217
|
columns,
|
|
24817
25218
|
indexes: Array.isArray(table?.indexes) ? table.indexes.map((index) => Array.isArray(index) ? index.map(String) : [String(index)]) : [],
|
|
24818
25219
|
injected: [...INJECTED_COLUMNS3]
|
|
@@ -25059,7 +25460,7 @@ function shapeMisses3(found, at) {
|
|
|
25059
25460
|
return misses;
|
|
25060
25461
|
}
|
|
25061
25462
|
function shapes3(branches, root) {
|
|
25062
|
-
const
|
|
25463
|
+
const named13 = branches.map((branch) => {
|
|
25063
25464
|
let node = branch;
|
|
25064
25465
|
for (let hops = 0; isObject5(node) && typeof node.$ref === "string" && hops < 8; hops += 1) {
|
|
25065
25466
|
const target = resolve10(node.$ref, root);
|
|
@@ -25075,8 +25476,8 @@ function shapes3(branches, root) {
|
|
|
25075
25476
|
if ("type" in node) return (Array.isArray(node.type) ? node.type : [node.type]).map(String).join(" or ");
|
|
25076
25477
|
return "a shape";
|
|
25077
25478
|
});
|
|
25078
|
-
const shown2 =
|
|
25079
|
-
return `${shown2.join(" | ")}${
|
|
25479
|
+
const shown2 = named13.slice(0, 6);
|
|
25480
|
+
return `${shown2.join(" | ")}${named13.length > shown2.length ? ` | ${named13.length - shown2.length} more` : ""}`;
|
|
25080
25481
|
}
|
|
25081
25482
|
function checkObject3(instance, node, at, root, problems, ctx) {
|
|
25082
25483
|
const properties = isObject5(node.properties) ? node.properties : {};
|
|
@@ -25145,10 +25546,10 @@ function isObject5(value) {
|
|
|
25145
25546
|
var DECLARES_KEYS2 = Object.freeze(["reads", "calls", "produces", "shows"]);
|
|
25146
25547
|
var SHOWS_KEYS2 = Object.freeze(["field", "format", "scale"]);
|
|
25147
25548
|
var SHOWS_FORMATS2 = Object.freeze(["currency", "number", "percent"]);
|
|
25148
|
-
var
|
|
25149
|
-
var
|
|
25150
|
-
var listOfNames2 = (value) => Array.isArray(value) && value.every(
|
|
25151
|
-
var idOf3 = (element, uid) =>
|
|
25549
|
+
var isPlainObject13 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
25550
|
+
var named8 = (value) => typeof value === "string" && value !== "";
|
|
25551
|
+
var listOfNames2 = (value) => Array.isArray(value) && value.every(named8);
|
|
25552
|
+
var idOf3 = (element, uid) => named8(element?.props?.id) ? element.props.id : uid;
|
|
25152
25553
|
function pyCode2(source) {
|
|
25153
25554
|
let out = "";
|
|
25154
25555
|
let i = 0;
|
|
@@ -25217,7 +25618,7 @@ function handlerPayloadReads2(row, source) {
|
|
|
25217
25618
|
return { generated: false, required: pyPayloadReads2(source).required };
|
|
25218
25619
|
}
|
|
25219
25620
|
if (role === "save" || role === "submit") {
|
|
25220
|
-
return { generated: true, required: (Array.isArray(row.required) ? row.required : []).filter(
|
|
25621
|
+
return { generated: true, required: (Array.isArray(row.required) ? row.required : []).filter(named8) };
|
|
25221
25622
|
}
|
|
25222
25623
|
return { generated: true, required: [] };
|
|
25223
25624
|
}
|
|
@@ -25387,32 +25788,32 @@ function childIndex2(elements) {
|
|
|
25387
25788
|
};
|
|
25388
25789
|
for (const [uid, element] of Object.entries(elements)) {
|
|
25389
25790
|
for (const child of Array.isArray(element?.children) ? element.children : []) if (child in elements) add(uid, child);
|
|
25390
|
-
if (
|
|
25791
|
+
if (named8(element?.parent) && element.parent in elements) add(element.parent, uid);
|
|
25391
25792
|
}
|
|
25392
25793
|
return index;
|
|
25393
25794
|
}
|
|
25394
25795
|
var ROW_HOLDERS2 = /* @__PURE__ */ new Set(["list", "queue", "detail"]);
|
|
25395
25796
|
var INPUT_PARTS2 = /* @__PURE__ */ new Set(["number_input", "select", "date_picker", "toggle", "slider", "checkbox", "radio_group", "file_upload"]);
|
|
25396
25797
|
function fieldOf2(element) {
|
|
25397
|
-
if (element?.type === "field" &&
|
|
25398
|
-
if (INPUT_PARTS2.has(element?.type) &&
|
|
25798
|
+
if (element?.type === "field" && named8(element.props?.field)) return element.props.field;
|
|
25799
|
+
if (INPUT_PARTS2.has(element?.type) && named8(element.props?.props?.field)) return element.props.props.field;
|
|
25399
25800
|
return null;
|
|
25400
25801
|
}
|
|
25401
25802
|
function mentions2(text3, name) {
|
|
25402
25803
|
return new RegExp(`(^|[^A-Za-z0-9_])${reEscape2(name)}([^A-Za-z0-9_]|$)`).test(text3);
|
|
25403
25804
|
}
|
|
25404
25805
|
function payloadProducers2(contract, { moduleSource = () => void 0, pageSource = () => void 0 } = {}) {
|
|
25405
|
-
const elements =
|
|
25406
|
-
const handlers =
|
|
25806
|
+
const elements = isPlainObject13(contract?.elements) ? contract.elements : {};
|
|
25807
|
+
const handlers = isPlainObject13(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
25407
25808
|
const children = childIndex2(elements);
|
|
25408
25809
|
const out = /* @__PURE__ */ new Map();
|
|
25409
25810
|
const add = (handler, producer) => {
|
|
25410
|
-
if (!
|
|
25811
|
+
if (!named8(handler)) return;
|
|
25411
25812
|
if (!out.has(handler)) out.set(handler, []);
|
|
25412
25813
|
out.get(handler).push(producer);
|
|
25413
25814
|
};
|
|
25414
25815
|
const insideRow = (uid) => {
|
|
25415
|
-
for (let at = elements[uid]?.parent;
|
|
25816
|
+
for (let at = elements[uid]?.parent; named8(at) && at in elements; at = elements[at]?.parent) {
|
|
25416
25817
|
if (ROW_HOLDERS2.has(elements[at]?.type)) return true;
|
|
25417
25818
|
}
|
|
25418
25819
|
return false;
|
|
@@ -25433,16 +25834,16 @@ function payloadProducers2(contract, { moduleSource = () => void 0, pageSource =
|
|
|
25433
25834
|
};
|
|
25434
25835
|
for (const uid of Object.keys(elements).sort()) {
|
|
25435
25836
|
const element = elements[uid];
|
|
25436
|
-
const props =
|
|
25837
|
+
const props = isPlainObject13(element?.props) ? element.props : {};
|
|
25437
25838
|
const page = element?.page ?? null;
|
|
25438
|
-
const typed =
|
|
25439
|
-
if (
|
|
25839
|
+
const typed = isPlainObject13(props.props) ? props.props : {};
|
|
25840
|
+
if (named8(props.source)) add(props.source, { page, uid, what: "reader", keys: null });
|
|
25440
25841
|
switch (element?.type) {
|
|
25441
25842
|
case "form": {
|
|
25442
25843
|
const keys2 = fieldsUnder(uid);
|
|
25443
25844
|
if (insideRow(uid) && !keys2.includes("id")) keys2.push("id");
|
|
25444
25845
|
const by = handlers[props.action]?.by;
|
|
25445
|
-
if (
|
|
25846
|
+
if (named8(by) && !keys2.includes(by)) keys2.push(by);
|
|
25446
25847
|
add(props.action, { page, uid, what: "form", keys: keys2 });
|
|
25447
25848
|
break;
|
|
25448
25849
|
}
|
|
@@ -25457,44 +25858,44 @@ function payloadProducers2(contract, { moduleSource = () => void 0, pageSource =
|
|
|
25457
25858
|
// stepper's current step `{step}`; a file_upload's on_upload its field.
|
|
25458
25859
|
case "button":
|
|
25459
25860
|
add(typed.on_click, { page, uid, what: "button", keys: typed.reason === "required" || typed.reason === "optional" ? ["reason"] : [] });
|
|
25460
|
-
for (const entry of Array.isArray(typed.split) ? typed.split : []) if (
|
|
25861
|
+
for (const entry of Array.isArray(typed.split) ? typed.split : []) if (isPlainObject13(entry)) add(entry.on_click, { page, uid, what: "button", keys: [] });
|
|
25461
25862
|
break;
|
|
25462
25863
|
case "empty_state":
|
|
25463
25864
|
add(typed.on_action, { page, uid, what: "empty_state", keys: [] });
|
|
25464
25865
|
break;
|
|
25465
25866
|
case "stepper":
|
|
25466
|
-
for (const step of Array.isArray(typed.steps) ? typed.steps : []) if (
|
|
25867
|
+
for (const step of Array.isArray(typed.steps) ? typed.steps : []) if (isPlainObject13(step)) add(step.action, { page, uid, what: "stepper", keys: ["step"] });
|
|
25467
25868
|
break;
|
|
25468
25869
|
case "file_upload":
|
|
25469
|
-
add(typed.on_upload, { page, uid, what: "file_upload", keys:
|
|
25870
|
+
add(typed.on_upload, { page, uid, what: "file_upload", keys: named8(typed.field) ? [typed.field] : [] });
|
|
25470
25871
|
break;
|
|
25471
25872
|
case "data_grid":
|
|
25472
25873
|
add(typed.on_row, { page, uid, what: "data_grid", keys: ["row"] });
|
|
25473
25874
|
for (const action of Array.isArray(typed.row_actions) ? typed.row_actions : []) {
|
|
25474
|
-
if (
|
|
25875
|
+
if (isPlainObject13(action) && named8(action.call)) add(action.call, { page, uid, what: "data_grid", keys: ["id", "row"] });
|
|
25475
25876
|
}
|
|
25476
|
-
for (const action of
|
|
25477
|
-
if (
|
|
25877
|
+
for (const action of isPlainObject13(typed.selection) && Array.isArray(typed.selection.bulk_actions) ? typed.selection.bulk_actions : []) {
|
|
25878
|
+
if (isPlainObject13(action) && named8(action.call)) add(action.call, { page, uid, what: "data_grid", keys: ["ids"] });
|
|
25478
25879
|
}
|
|
25479
25880
|
for (const column of Array.isArray(typed.columns) ? typed.columns : []) {
|
|
25480
|
-
if (
|
|
25881
|
+
if (isPlainObject13(column) && column.cell === "toggle" && named8(column.call)) add(column.call, { page, uid, what: "data_grid", keys: ["id", "row", "field", "value"] });
|
|
25481
25882
|
}
|
|
25482
25883
|
break;
|
|
25483
25884
|
case "action": {
|
|
25484
25885
|
const keys2 = [];
|
|
25485
|
-
if (insideRow(uid)) keys2.push(
|
|
25486
|
-
else if (
|
|
25886
|
+
if (insideRow(uid)) keys2.push(named8(props.param) ? props.param : "id");
|
|
25887
|
+
else if (named8(props.param)) keys2.push(props.param);
|
|
25487
25888
|
if (handlers[props.action]?.role === "decide") keys2.push("decision", "note");
|
|
25488
25889
|
add(props.action, { page, uid, what: "action", keys: keys2 });
|
|
25489
25890
|
break;
|
|
25490
25891
|
}
|
|
25491
25892
|
case "custom": {
|
|
25492
|
-
const declares =
|
|
25493
|
-
const produces =
|
|
25893
|
+
const declares = isPlainObject13(props.declares) ? props.declares : {};
|
|
25894
|
+
const produces = isPlainObject13(declares.produces) ? declares.produces : {};
|
|
25494
25895
|
for (const [handler, keys2] of Object.entries(produces)) {
|
|
25495
25896
|
add(handler, { page, uid, what: "custom", keys: listOfNames2(keys2) ? [...keys2] : null });
|
|
25496
25897
|
}
|
|
25497
|
-
const source =
|
|
25898
|
+
const source = named8(props.module) ? moduleSource(props.module) : void 0;
|
|
25498
25899
|
if (typeof source !== "string") {
|
|
25499
25900
|
add("*", { page, uid, what: "custom", keys: null });
|
|
25500
25901
|
break;
|
|
@@ -25520,10 +25921,10 @@ function payloadProducers2(contract, { moduleSource = () => void 0, pageSource =
|
|
|
25520
25921
|
}
|
|
25521
25922
|
const modules = /* @__PURE__ */ new Set();
|
|
25522
25923
|
for (const element of Object.values(elements)) {
|
|
25523
|
-
if (element?.type === "custom" &&
|
|
25924
|
+
if (element?.type === "custom" && named8(element.props?.module)) modules.add(`${element.page}|${element.props.module}`);
|
|
25524
25925
|
}
|
|
25525
25926
|
for (const page of Array.isArray(contract?.pages) ? contract.pages : []) {
|
|
25526
|
-
if (!
|
|
25927
|
+
if (!named8(page?.path)) continue;
|
|
25527
25928
|
const text3 = pageSource(page.path);
|
|
25528
25929
|
const unknown = { page: page.path, uid: null, what: "script", keys: null };
|
|
25529
25930
|
if (typeof text3 !== "string") {
|
|
@@ -25541,7 +25942,7 @@ function payloadProducers2(contract, { moduleSource = () => void 0, pageSource =
|
|
|
25541
25942
|
}
|
|
25542
25943
|
var SCRIPT_TAG2 = /<script\b([^>]*)>([\s\S]*?)<\/script\s*>/gi;
|
|
25543
25944
|
var SRC_ATTR2 = /\bsrc\s*=\s*["']([^"']+)["']/i;
|
|
25544
|
-
var
|
|
25945
|
+
var JSON_TYPE3 = /\btype\s*=\s*["']application\/json["']/i;
|
|
25545
25946
|
var MOUNT_ONLY2 = /^\s*drApp\.mount\(\s*\{[^}]*\}\s*\)\s*;?\s*$/;
|
|
25546
25947
|
function pageCode2(html, path3, modules, pageSource) {
|
|
25547
25948
|
const out = [];
|
|
@@ -25549,7 +25950,7 @@ function pageCode2(html, path3, modules, pageSource) {
|
|
|
25549
25950
|
const attrs = match[1] ?? "";
|
|
25550
25951
|
const src = SRC_ATTR2.exec(attrs)?.[1];
|
|
25551
25952
|
if (src === void 0) {
|
|
25552
|
-
if (
|
|
25953
|
+
if (JSON_TYPE3.test(attrs) || MOUNT_ONLY2.test(match[2] ?? "") || (match[2] ?? "").trim() === "") continue;
|
|
25553
25954
|
out.push(match[2]);
|
|
25554
25955
|
continue;
|
|
25555
25956
|
}
|
|
@@ -25568,18 +25969,18 @@ function either4(items) {
|
|
|
25568
25969
|
return `${items.slice(0, -1).join(", ")} or ${items.at(-1)}`;
|
|
25569
25970
|
}
|
|
25570
25971
|
function unproducedPayloadKeys2(contract, { handlerSource = () => void 0, moduleSource, pageSource } = {}) {
|
|
25571
|
-
const handlers =
|
|
25972
|
+
const handlers = isPlainObject13(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
25572
25973
|
const producers = payloadProducers2(contract, { moduleSource, pageSource });
|
|
25573
25974
|
const anywhere = producers.get("*") ?? [];
|
|
25574
25975
|
const out = [];
|
|
25575
25976
|
for (const name of Object.keys(handlers).sort()) {
|
|
25576
25977
|
const reads = handlerPayloadReads2(handlers[name], handlerSource(name));
|
|
25577
25978
|
if (reads === null || reads.required.length === 0) continue;
|
|
25578
|
-
const
|
|
25579
|
-
if (
|
|
25979
|
+
const own2 = producers.get(name) ?? [];
|
|
25980
|
+
if (own2.length === 0 || anywhere.length > 0 || own2.some((one) => one.keys === null)) continue;
|
|
25580
25981
|
const produced = [];
|
|
25581
|
-
for (const one of
|
|
25582
|
-
const pages = [...new Set(
|
|
25982
|
+
for (const one of own2) for (const key of one.keys) if (!produced.includes(key)) produced.push(key);
|
|
25983
|
+
const pages = [...new Set(own2.map((one) => one.page).filter(named8))];
|
|
25583
25984
|
for (const key of reads.required) {
|
|
25584
25985
|
if (produced.includes(key)) continue;
|
|
25585
25986
|
const there = produced.length === 0 ? "the controls there produce no payload keys" : `the controls there produce ${produced.join(", ")}`;
|
|
@@ -25591,12 +25992,12 @@ function unproducedPayloadKeys2(contract, { handlerSource = () => void 0, module
|
|
|
25591
25992
|
return out;
|
|
25592
25993
|
}
|
|
25593
25994
|
function undeclaredCustomCalls2(contract, { moduleSource = () => void 0 } = {}) {
|
|
25594
|
-
const elements =
|
|
25995
|
+
const elements = isPlainObject13(contract?.elements) ? contract.elements : {};
|
|
25595
25996
|
const out = [];
|
|
25596
25997
|
for (const uid of Object.keys(elements).sort()) {
|
|
25597
25998
|
const element = elements[uid];
|
|
25598
|
-
const props =
|
|
25599
|
-
if (element?.type !== "custom" || !
|
|
25999
|
+
const props = isPlainObject13(element?.props) ? element.props : {};
|
|
26000
|
+
if (element?.type !== "custom" || !isPlainObject13(props.declares) || !named8(props.module)) continue;
|
|
25600
26001
|
const source = moduleSource(props.module);
|
|
25601
26002
|
if (typeof source !== "string") continue;
|
|
25602
26003
|
const calls = listOfNames2(props.declares.calls) ? props.declares.calls : [];
|
|
@@ -25624,32 +26025,32 @@ function undeclaredCallMessage2(id, module, handler, calls, how) {
|
|
|
25624
26025
|
return { problem: problem5, fix, message: `${problem5} ${fix}` };
|
|
25625
26026
|
}
|
|
25626
26027
|
function unreadUploadKeys2(contract, { handlerSource = () => void 0 } = {}) {
|
|
25627
|
-
const elements =
|
|
25628
|
-
const handlers =
|
|
26028
|
+
const elements = isPlainObject13(contract?.elements) ? contract.elements : {};
|
|
26029
|
+
const handlers = isPlainObject13(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
25629
26030
|
const parents = /* @__PURE__ */ new Map();
|
|
25630
26031
|
for (const [parent, kids] of childIndex2(elements)) for (const kid of kids) if (!parents.has(kid)) parents.set(kid, parent);
|
|
25631
26032
|
const out = [];
|
|
25632
26033
|
for (const uid of Object.keys(elements).sort()) {
|
|
25633
26034
|
const element = elements[uid];
|
|
25634
|
-
const typed =
|
|
25635
|
-
if (element?.type !== "file_upload" || !
|
|
26035
|
+
const typed = isPlainObject13(element?.props?.props) ? element.props.props : {};
|
|
26036
|
+
if (element?.type !== "file_upload" || !named8(typed.field)) continue;
|
|
25636
26037
|
const field = typed.field;
|
|
25637
26038
|
const receivers = [];
|
|
25638
|
-
if (
|
|
25639
|
-
for (let at = parents.get(uid);
|
|
26039
|
+
if (named8(typed.on_upload)) receivers.push([typed.on_upload, "its on_upload"]);
|
|
26040
|
+
for (let at = parents.get(uid); named8(at) && at in elements; at = parents.get(at)) {
|
|
25640
26041
|
const holder = elements[at];
|
|
25641
|
-
if (holder?.type === "dialog" &&
|
|
26042
|
+
if (holder?.type === "dialog" && named8(holder.props?.props?.on_confirm)) {
|
|
25642
26043
|
receivers.push([holder.props.props.on_confirm, `the dialog "${idOf3(holder, at)}" it sits in`]);
|
|
25643
26044
|
break;
|
|
25644
26045
|
}
|
|
25645
|
-
if (holder?.type === "form" &&
|
|
26046
|
+
if (holder?.type === "form" && named8(holder.props?.action)) {
|
|
25646
26047
|
receivers.push([holder.props.action, `the form "${idOf3(holder, at)}" it sits in`]);
|
|
25647
26048
|
break;
|
|
25648
26049
|
}
|
|
25649
26050
|
}
|
|
25650
26051
|
for (const [handler, via] of receivers) {
|
|
25651
26052
|
const row = handlers[handler];
|
|
25652
|
-
if (!
|
|
26053
|
+
if (!isPlainObject13(row)) continue;
|
|
25653
26054
|
const role = row.role ?? "custom";
|
|
25654
26055
|
let read;
|
|
25655
26056
|
if (role === "custom") {
|
|
@@ -25672,16 +26073,16 @@ function unreadUploadKeys2(contract, { handlerSource = () => void 0 } = {}) {
|
|
|
25672
26073
|
return out;
|
|
25673
26074
|
}
|
|
25674
26075
|
function stepsWithoutAction2(contract) {
|
|
25675
|
-
const elements =
|
|
26076
|
+
const elements = isPlainObject13(contract?.elements) ? contract.elements : {};
|
|
25676
26077
|
const out = [];
|
|
25677
26078
|
for (const uid of Object.keys(elements).sort()) {
|
|
25678
26079
|
const element = elements[uid];
|
|
25679
26080
|
const steps = element?.type === "stepper" && Array.isArray(element.props?.props?.steps) ? element.props.props.steps : [];
|
|
25680
26081
|
steps.forEach((step, index) => {
|
|
25681
|
-
if (index === steps.length - 1 || !
|
|
26082
|
+
if (index === steps.length - 1 || !isPlainObject13(step) || named8(step.action)) return;
|
|
25682
26083
|
const id = idOf3(element, uid);
|
|
25683
|
-
const name =
|
|
25684
|
-
const next =
|
|
26084
|
+
const name = named8(step.id) ? step.id : String(index);
|
|
26085
|
+
const next = named8(steps[index + 1]?.id) ? steps[index + 1].id : "the next step";
|
|
25685
26086
|
const problem5 = `stepper "${id}" step "${name}" names no action, so no handler moves the flow on from it \u2014 a flow that reaches it stays there.`;
|
|
25686
26087
|
const fix = `Give it the handler that moves it to "${next}" (action, action_label), or make it the last step.`;
|
|
25687
26088
|
out.push({ uid, id, page: element.page ?? null, step: name, index, problem: problem5, fix, message: `${problem5} ${fix}` });
|
|
@@ -26097,47 +26498,47 @@ function isReferenced4(name, pageText) {
|
|
|
26097
26498
|
}
|
|
26098
26499
|
|
|
26099
26500
|
// ../flows/appspace-builder/kit/contract/levers.mjs
|
|
26100
|
-
var
|
|
26101
|
-
var
|
|
26501
|
+
var isPlainObject14 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
26502
|
+
var named9 = (value) => typeof value === "string" && value !== "";
|
|
26102
26503
|
function both2(items) {
|
|
26103
26504
|
if (items.length <= 1) return items.join("");
|
|
26104
26505
|
return `${items.slice(0, -1).join(", ")} and ${items.at(-1)}`;
|
|
26105
26506
|
}
|
|
26106
26507
|
function paramPages(contract) {
|
|
26107
26508
|
return (Array.isArray(contract?.pages) ? contract.pages : []).filter(
|
|
26108
|
-
(page) => page?.owned !== true &&
|
|
26509
|
+
(page) => page?.owned !== true && named9(page?.path) && Array.isArray(page.params) && page.params.length > 0
|
|
26109
26510
|
);
|
|
26110
26511
|
}
|
|
26111
26512
|
function namesOf(page) {
|
|
26112
26513
|
const out = [];
|
|
26113
|
-
for (const param of page.params) if (
|
|
26514
|
+
for (const param of page.params) if (isPlainObject14(param) && named9(param.name) && !out.includes(param.name)) out.push(param.name);
|
|
26114
26515
|
return out;
|
|
26115
26516
|
}
|
|
26116
|
-
function
|
|
26117
|
-
const elements =
|
|
26517
|
+
function elementsOn2(contract, path3) {
|
|
26518
|
+
const elements = isPlainObject14(contract?.elements) ? contract.elements : {};
|
|
26118
26519
|
return Object.keys(elements).sort().filter((uid) => elements[uid]?.page === path3).map((uid) => [uid, elements[uid]]);
|
|
26119
26520
|
}
|
|
26120
26521
|
function paramModeOf(element) {
|
|
26121
|
-
const props =
|
|
26122
|
-
if (element?.type === "control") return
|
|
26522
|
+
const props = isPlainObject14(element?.props) ? element.props : {};
|
|
26523
|
+
if (element?.type === "control") return named9(props.param) ? props.param : named9(element.binding) ? element.binding : null;
|
|
26123
26524
|
if (element?.type === "field" || element?.type === "action") return null;
|
|
26124
|
-
if (
|
|
26125
|
-
const typed =
|
|
26126
|
-
return
|
|
26525
|
+
if (named9(props.param) && element?.type !== "custom") return props.param;
|
|
26526
|
+
const typed = isPlainObject14(props.props) ? props.props : {};
|
|
26527
|
+
return named9(typed.param) ? typed.param : null;
|
|
26127
26528
|
}
|
|
26128
26529
|
function paramsWrittenBy(element) {
|
|
26129
26530
|
const out = [];
|
|
26130
26531
|
const one = paramModeOf(element);
|
|
26131
26532
|
if (one !== null) out.push(one);
|
|
26132
26533
|
if (element?.type === "custom") return out;
|
|
26133
|
-
const typed =
|
|
26134
|
-
if (
|
|
26534
|
+
const typed = isPlainObject14(element?.props?.props) ? element.props.props : {};
|
|
26535
|
+
if (named9(typed.param_to)) out.push(typed.param_to);
|
|
26135
26536
|
for (const filter of Array.isArray(typed.filters) ? typed.filters : []) {
|
|
26136
|
-
if (!
|
|
26137
|
-
if (
|
|
26138
|
-
if (
|
|
26139
|
-
if (
|
|
26140
|
-
if (
|
|
26537
|
+
if (!isPlainObject14(filter)) continue;
|
|
26538
|
+
if (named9(filter.param)) out.push(filter.param);
|
|
26539
|
+
if (named9(filter.param_to)) out.push(filter.param_to);
|
|
26540
|
+
if (named9(filter.param_min)) out.push(filter.param_min);
|
|
26541
|
+
if (named9(filter.param_max)) out.push(filter.param_max);
|
|
26141
26542
|
}
|
|
26142
26543
|
return out;
|
|
26143
26544
|
}
|
|
@@ -26146,13 +26547,13 @@ function paramWriters(contract, { moduleSource = () => void 0 } = {}) {
|
|
|
26146
26547
|
for (const page of paramPages(contract)) {
|
|
26147
26548
|
const written = /* @__PURE__ */ new Set();
|
|
26148
26549
|
let unknown = false;
|
|
26149
|
-
for (const [, element] of
|
|
26550
|
+
for (const [, element] of elementsOn2(contract, page.path)) {
|
|
26150
26551
|
for (const name of paramsWrittenBy(element)) written.add(name);
|
|
26151
26552
|
if (element?.type !== "custom") continue;
|
|
26152
|
-
const props =
|
|
26153
|
-
const produces =
|
|
26154
|
-
for (const keys2 of Object.values(produces)) for (const key of Array.isArray(keys2) ? keys2 : []) if (
|
|
26155
|
-
if (!
|
|
26553
|
+
const props = isPlainObject14(element.props) ? element.props : {};
|
|
26554
|
+
const produces = isPlainObject14(props.declares?.produces) ? props.declares.produces : {};
|
|
26555
|
+
for (const keys2 of Object.values(produces)) for (const key of Array.isArray(keys2) ? keys2 : []) if (named9(key)) written.add(key);
|
|
26556
|
+
if (!named9(props.module)) continue;
|
|
26156
26557
|
const source = moduleSource(props.module);
|
|
26157
26558
|
if (typeof source !== "string") {
|
|
26158
26559
|
unknown = true;
|
|
@@ -26165,8 +26566,8 @@ function paramWriters(contract, { moduleSource = () => void 0 } = {}) {
|
|
|
26165
26566
|
}
|
|
26166
26567
|
if (contract?.app?.shell?.nav === "rail") {
|
|
26167
26568
|
for (const entry of Array.isArray(contract.app.shell.rail?.entries) ? contract.app.shell.rail.entries : []) {
|
|
26168
|
-
if (
|
|
26169
|
-
for (const name of Object.keys(entry.param)) if (
|
|
26569
|
+
if (isPlainObject14(entry?.param)) {
|
|
26570
|
+
for (const name of Object.keys(entry.param)) if (named9(name)) written.add(name);
|
|
26170
26571
|
}
|
|
26171
26572
|
}
|
|
26172
26573
|
}
|
|
@@ -26183,7 +26584,7 @@ function unboundControls(contract, files = {}) {
|
|
|
26183
26584
|
for (const name of namesOf(page)) {
|
|
26184
26585
|
if (written.has(name)) continue;
|
|
26185
26586
|
const param = page.params.find((one) => one?.name === name) ?? {};
|
|
26186
|
-
const kind =
|
|
26587
|
+
const kind = named9(param.kind) ? param.kind : null;
|
|
26187
26588
|
const opens3 = param.default === void 0 ? "no value at all" : `its default (${JSON.stringify(param.default)})`;
|
|
26188
26589
|
const problem5 = `${page.path} declares param "${name}"${kind === null ? "" : ` (${kind})`}, and no control on the page writes it \u2014 no part in param mode, no custom that calls ui.setParam("${name}", \u2026) or declares it in declares.produces \u2014 so the page only ever shows ${opens3}.`;
|
|
26189
26590
|
const fix = `Give it a control \u2014 a part in param mode (props.props.param "${name}": a slider, a number_input, a select, a date_picker or a toggle), or a custom whose module calls ui.setParam("${name}", value) \u2014 or drop the param.`;
|
|
@@ -26198,16 +26599,16 @@ function honoursOf(sources, name) {
|
|
|
26198
26599
|
const queue = [name];
|
|
26199
26600
|
while (queue.length > 0) {
|
|
26200
26601
|
const one = queue.shift();
|
|
26201
|
-
if (seen.has(one) || !
|
|
26602
|
+
if (seen.has(one) || !isPlainObject14(sources[one])) continue;
|
|
26202
26603
|
seen.add(one);
|
|
26203
|
-
for (const honored of Array.isArray(sources[one].honors) ? sources[one].honors : []) if (
|
|
26604
|
+
for (const honored of Array.isArray(sources[one].honors) ? sources[one].honors : []) if (named9(honored) && !out.includes(honored)) out.push(honored);
|
|
26204
26605
|
if (Array.isArray(sources[one].from)) queue.push(...sources[one].from);
|
|
26205
26606
|
}
|
|
26206
26607
|
return out;
|
|
26207
26608
|
}
|
|
26208
26609
|
function unreadParams(contract, { moduleSource = () => void 0, handlerSource = () => void 0 } = {}) {
|
|
26209
|
-
const sources =
|
|
26210
|
-
const handlers =
|
|
26610
|
+
const sources = isPlainObject14(contract?.data?.sources) ? contract.data.sources : {};
|
|
26611
|
+
const handlers = isPlainObject14(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
26211
26612
|
const out = [];
|
|
26212
26613
|
for (const page of paramPages(contract)) {
|
|
26213
26614
|
const read = /* @__PURE__ */ new Set();
|
|
@@ -26215,16 +26616,16 @@ function unreadParams(contract, { moduleSource = () => void 0, handlerSource = (
|
|
|
26215
26616
|
const modules = [];
|
|
26216
26617
|
const custom = [];
|
|
26217
26618
|
let unknown = false;
|
|
26218
|
-
for (const [, element] of
|
|
26219
|
-
const props =
|
|
26220
|
-
const reads = [props.source, ...Array.isArray(props.declares?.reads) ? props.declares.reads : []].filter(
|
|
26619
|
+
for (const [, element] of elementsOn2(contract, page.path)) {
|
|
26620
|
+
const props = isPlainObject14(element?.props) ? element.props : {};
|
|
26621
|
+
const reads = [props.source, ...Array.isArray(props.declares?.reads) ? props.declares.reads : []].filter(named9);
|
|
26221
26622
|
for (const one of reads) {
|
|
26222
26623
|
if (Object.prototype.hasOwnProperty.call(sources, one)) {
|
|
26223
26624
|
if (!named_.includes(one)) named_.push(one);
|
|
26224
26625
|
for (const honored of honoursOf(sources, one)) read.add(honored);
|
|
26225
26626
|
} else if (handlers[one]?.role === "custom" && !custom.includes(one)) custom.push(one);
|
|
26226
26627
|
}
|
|
26227
|
-
if (element?.type === "custom" &&
|
|
26628
|
+
if (element?.type === "custom" && named9(props.module)) {
|
|
26228
26629
|
const text3 = moduleSource(props.module);
|
|
26229
26630
|
if (typeof text3 === "string") modules.push(text3);
|
|
26230
26631
|
else unknown = true;
|
|
@@ -26266,8 +26667,8 @@ function checkLevers(app, contract, contractError) {
|
|
|
26266
26667
|
}
|
|
26267
26668
|
|
|
26268
26669
|
// ../flows/appspace-builder/kit/contract/look.mjs
|
|
26269
|
-
var
|
|
26270
|
-
var
|
|
26670
|
+
var isPlainObject15 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
26671
|
+
var named10 = (value) => typeof value === "string" && value !== "";
|
|
26271
26672
|
function humanise3(name) {
|
|
26272
26673
|
const words = String(name).replace(/^_+/, "").replace(/_/g, " ").trim();
|
|
26273
26674
|
return words === "" ? "" : words[0].toUpperCase() + words.slice(1);
|
|
@@ -26275,10 +26676,10 @@ function humanise3(name) {
|
|
|
26275
26676
|
var WORDING2 = {
|
|
26276
26677
|
custom: { noun: "card", fallback: (p) => [humanise3(String(p.module ?? "").replace(/^.*\/|\.js$/g, "")), "its module"] },
|
|
26277
26678
|
chart: { noun: "card", fallback: (p) => [humanise3(String(p.y ?? "")), "its y column"] },
|
|
26278
|
-
widget: { noun: "card", fallback: (p) => [humanise3(
|
|
26679
|
+
widget: { noun: "card", fallback: (p) => [humanise3(named10(p.source) ? p.source : "widget"), "its source"] },
|
|
26279
26680
|
stat: { noun: "tile", fallback: (p) => [humanise3(String(p.measure ?? "")), "its measure"] },
|
|
26280
|
-
list: { noun: "card", fallback: (p) => [humanise3(
|
|
26281
|
-
queue: { noun: "card", fallback: (p) => [humanise3(
|
|
26681
|
+
list: { noun: "card", fallback: (p) => [humanise3(named10(p.entity) ? p.entity : "list"), "its entity"] },
|
|
26682
|
+
queue: { noun: "card", fallback: (p) => [humanise3(named10(p.entity) ? p.entity : "queue"), "its entity"] },
|
|
26282
26683
|
form: { noun: "card", fallback: () => ["New", "the runtime\u2019s default"] },
|
|
26283
26684
|
dialog: { noun: "dialog" }
|
|
26284
26685
|
};
|
|
@@ -26287,7 +26688,7 @@ var TITLED2 = Object.fromEntries(
|
|
|
26287
26688
|
);
|
|
26288
26689
|
var LAYOUT2 = /* @__PURE__ */ new Set(["region", "tabs", "tab_panel"]);
|
|
26289
26690
|
function rootsOf3(contract, page) {
|
|
26290
|
-
const elements =
|
|
26691
|
+
const elements = isPlainObject15(contract?.elements) ? contract.elements : {};
|
|
26291
26692
|
if (Array.isArray(page?.tree) && page.tree.length > 0) return page.tree.filter((uid) => elements[uid] !== void 0);
|
|
26292
26693
|
const contained = /* @__PURE__ */ new Set();
|
|
26293
26694
|
for (const element of Object.values(elements)) for (const child of element?.children ?? []) contained.add(child);
|
|
@@ -26297,7 +26698,7 @@ function rootsOf3(contract, page) {
|
|
|
26297
26698
|
});
|
|
26298
26699
|
}
|
|
26299
26700
|
function untitledCards2(contract) {
|
|
26300
|
-
const elements =
|
|
26701
|
+
const elements = isPlainObject15(contract?.elements) ? contract.elements : {};
|
|
26301
26702
|
const out = [];
|
|
26302
26703
|
for (const page of Array.isArray(contract?.pages) ? contract.pages : []) {
|
|
26303
26704
|
if (page?.owned === true) continue;
|
|
@@ -26322,13 +26723,13 @@ function untitledCards2(contract) {
|
|
|
26322
26723
|
const type = element.type ?? "";
|
|
26323
26724
|
const titled = TITLED2[type];
|
|
26324
26725
|
if (titled === void 0) continue;
|
|
26325
|
-
const props =
|
|
26326
|
-
const typed =
|
|
26327
|
-
const card =
|
|
26328
|
-
if (
|
|
26329
|
-
if (type === "dialog" ?
|
|
26330
|
-
const id =
|
|
26331
|
-
const [title, from] =
|
|
26726
|
+
const props = isPlainObject15(element.props) ? element.props : {};
|
|
26727
|
+
const typed = isPlainObject15(props.props) ? props.props : {};
|
|
26728
|
+
const card = isPlainObject15(element.card) ? element.card : isPlainObject15(props.card) ? props.card : {};
|
|
26729
|
+
if (named10(props.label) || named10(card.title)) continue;
|
|
26730
|
+
if (type === "dialog" ? named10(typed.title) : named10(typed.label)) continue;
|
|
26731
|
+
const id = named10(element.id) ? element.id : named10(props.id) ? props.id : null;
|
|
26732
|
+
const [title, from] = named10(props.field) ? [humanise3(props.field), "its field"] : id !== null ? [humanise3(id), "its id"] : titled.fallback(props);
|
|
26332
26733
|
const what = id !== null ? `${type} "${id}"` : `the ${type} ${uid}`;
|
|
26333
26734
|
const shown2 = title === "" ? "has no title at all" : `is titled "${title}" after ${from}`;
|
|
26334
26735
|
out.push({ page: page.path, uid, type, id, noun: titled.noun, title, from, message: `${what} has no label, so its ${titled.noun} ${shown2}` });
|
|
@@ -26338,12 +26739,12 @@ function untitledCards2(contract) {
|
|
|
26338
26739
|
}
|
|
26339
26740
|
var BAND_LOOK5 = ["subtitle", "layout", "surface", "aside"];
|
|
26340
26741
|
function lookPages2(contract) {
|
|
26341
|
-
const elements =
|
|
26742
|
+
const elements = isPlainObject15(contract?.elements) ? contract.elements : {};
|
|
26342
26743
|
const out = /* @__PURE__ */ new Set();
|
|
26343
26744
|
for (const page of Array.isArray(contract?.pages) ? contract.pages : []) {
|
|
26344
|
-
if (page?.owned === true || !
|
|
26345
|
-
const bands = (Array.isArray(page.sections) ? page.sections : []).some((section) => BAND_LOOK5.some((key) =>
|
|
26346
|
-
const cards = Object.values(elements).some((element) => element?.page === page.path && (
|
|
26745
|
+
if (page?.owned === true || !named10(page?.path)) continue;
|
|
26746
|
+
const bands = (Array.isArray(page.sections) ? page.sections : []).some((section) => BAND_LOOK5.some((key) => named10(section?.[key])));
|
|
26747
|
+
const cards = Object.values(elements).some((element) => element?.page === page.path && (isPlainObject15(element.card) || named10(element.emphasis)));
|
|
26347
26748
|
if (bands || cards) out.add(page.path);
|
|
26348
26749
|
}
|
|
26349
26750
|
return out;
|
|
@@ -26351,34 +26752,34 @@ function lookPages2(contract) {
|
|
|
26351
26752
|
var SPAN_LAYOUTS2 = /* @__PURE__ */ new Set([void 0, "grid"]);
|
|
26352
26753
|
var CONTROL_TYPES2 = /* @__PURE__ */ new Set(["filter", "control", ...CONTROL_PARTS2]);
|
|
26353
26754
|
function bandsOf3(contract, page) {
|
|
26354
|
-
const elements =
|
|
26755
|
+
const elements = isPlainObject15(contract?.elements) ? contract.elements : {};
|
|
26355
26756
|
const roots = rootsOf3(contract, page);
|
|
26356
|
-
return (Array.isArray(page.sections) ? page.sections : []).filter((section) =>
|
|
26757
|
+
return (Array.isArray(page.sections) ? page.sections : []).filter((section) => named10(section?.id)).map((section) => ({ section, roots: roots.filter((uid) => elements[uid]?.section === section.id) }));
|
|
26357
26758
|
}
|
|
26358
26759
|
function nameOf4(uid, element) {
|
|
26359
|
-
const id =
|
|
26760
|
+
const id = named10(element?.props?.id) ? element.props.id : named10(element?.id) ? element.id : null;
|
|
26360
26761
|
return id === null ? `the ${element?.type ?? "element"} ${uid}` : `${element.type} "${id}"`;
|
|
26361
26762
|
}
|
|
26362
26763
|
function isControl2(element, moduleSource) {
|
|
26363
|
-
const props =
|
|
26764
|
+
const props = isPlainObject15(element?.props) ? element.props : {};
|
|
26364
26765
|
if (CONTROL_TYPES2.has(element?.type)) return true;
|
|
26365
|
-
if (element?.type !== "custom" && element?.type !== "action" &&
|
|
26366
|
-
if (
|
|
26367
|
-
if (element?.type === "custom" &&
|
|
26766
|
+
if (element?.type !== "custom" && element?.type !== "action" && named10(props.param)) return true;
|
|
26767
|
+
if (isPlainObject15(props.props) && named10(props.props.param)) return true;
|
|
26768
|
+
if (element?.type === "custom" && named10(props.module)) {
|
|
26368
26769
|
const source = moduleSource(props.module);
|
|
26369
26770
|
return typeof source === "string" && scanUiSetParams2(source).length > 0;
|
|
26370
26771
|
}
|
|
26371
26772
|
return false;
|
|
26372
26773
|
}
|
|
26373
26774
|
function layoutProblems2(contract, { moduleSource = () => void 0 } = {}) {
|
|
26374
|
-
const elements =
|
|
26775
|
+
const elements = isPlainObject15(contract?.elements) ? contract.elements : {};
|
|
26375
26776
|
const looked = lookPages2(contract);
|
|
26376
26777
|
const out = [];
|
|
26377
26778
|
for (const page of Array.isArray(contract?.pages) ? contract.pages : []) {
|
|
26378
26779
|
if (!looked.has(page?.path)) continue;
|
|
26379
26780
|
for (const { section, roots } of bandsOf3(contract, page)) {
|
|
26380
26781
|
const band = `band "${section.id}" on ${page.path}`;
|
|
26381
|
-
const layout =
|
|
26782
|
+
const layout = named10(section.layout) ? section.layout : void 0;
|
|
26382
26783
|
if (layout === "kpi_strip") {
|
|
26383
26784
|
for (const uid of roots) {
|
|
26384
26785
|
const element = elements[uid];
|
|
@@ -26442,10 +26843,10 @@ function layoutProblems2(contract, { moduleSource = () => void 0 } = {}) {
|
|
|
26442
26843
|
}
|
|
26443
26844
|
var HEADER_ACTION_TYPES2 = ["button", "dialog"];
|
|
26444
26845
|
function headerActionProblems2(contract) {
|
|
26445
|
-
const elements =
|
|
26846
|
+
const elements = isPlainObject15(contract?.elements) ? contract.elements : {};
|
|
26446
26847
|
const out = [];
|
|
26447
26848
|
for (const [pageIndex, page] of (Array.isArray(contract?.pages) ? contract.pages : []).entries()) {
|
|
26448
|
-
if (page?.owned === true || !
|
|
26849
|
+
if (page?.owned === true || !isPlainObject15(page?.header) || page.header.actions === void 0) continue;
|
|
26449
26850
|
const say = (index, id, uid, message) => out.push({ rule: "L5", code: "header_action_unknown", page: page.path, pageIndex, index, id, uid, message });
|
|
26450
26851
|
const actions = page.header.actions;
|
|
26451
26852
|
if (!Array.isArray(actions) || actions.length === 0) {
|
|
@@ -26454,7 +26855,7 @@ function headerActionProblems2(contract) {
|
|
|
26454
26855
|
}
|
|
26455
26856
|
const seen = /* @__PURE__ */ new Set();
|
|
26456
26857
|
for (const [index, id] of actions.entries()) {
|
|
26457
|
-
if (!
|
|
26858
|
+
if (!named10(id)) {
|
|
26458
26859
|
say(index, id, null, `${page.path}'s header.actions[${index}] is ${JSON.stringify(id)}; each entry is an element id (props.id) on the page`);
|
|
26459
26860
|
continue;
|
|
26460
26861
|
}
|
|
@@ -26465,7 +26866,7 @@ function headerActionProblems2(contract) {
|
|
|
26465
26866
|
seen.add(id);
|
|
26466
26867
|
const hits = Object.keys(elements).filter((uid) => {
|
|
26467
26868
|
const element2 = elements[uid];
|
|
26468
|
-
return element2?.page === page.path && element2.type !== "region" && (
|
|
26869
|
+
return element2?.page === page.path && element2.type !== "region" && (named10(element2.props?.id) ? element2.props.id : element2.id) === id;
|
|
26469
26870
|
});
|
|
26470
26871
|
if (hits.length === 0) {
|
|
26471
26872
|
say(index, id, null, `${page.path}'s header.actions names "${id}", and no element on the page has that id, so nothing is drawn beside the title; name a button or dialog of this page by its props.id`);
|
|
@@ -26481,11 +26882,11 @@ function headerActionProblems2(contract) {
|
|
|
26481
26882
|
}
|
|
26482
26883
|
function navCountProblems2(contract) {
|
|
26483
26884
|
const out = [];
|
|
26484
|
-
const handlers =
|
|
26885
|
+
const handlers = isPlainObject15(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
26485
26886
|
const pages = (Array.isArray(contract?.pages) ? contract.pages : []).filter((page) => page?.owned !== true);
|
|
26486
26887
|
const entries = Array.isArray(contract?.app?.shell?.rail?.entries) ? contract.app.shell.rail.entries : [];
|
|
26487
|
-
const measured = entries.map((entry, index) => ({ entry, index })).filter(({ entry }) =>
|
|
26488
|
-
const counted = pages.filter((page) =>
|
|
26888
|
+
const measured = entries.map((entry, index) => ({ entry, index })).filter(({ entry }) => named10(entry?.measure));
|
|
26889
|
+
const counted = pages.filter((page) => isPlainObject15(page.nav) && page.nav.counts !== void 0);
|
|
26489
26890
|
if (counted.length === 0) {
|
|
26490
26891
|
if (measured.length > 0) {
|
|
26491
26892
|
out.push({
|
|
@@ -26501,21 +26902,21 @@ function navCountProblems2(contract) {
|
|
|
26501
26902
|
for (const page of counted) {
|
|
26502
26903
|
const source = page.nav.counts?.source;
|
|
26503
26904
|
const where2 = `${page.path} nav.counts.source`;
|
|
26504
|
-
if (!
|
|
26905
|
+
if (!named10(source)) {
|
|
26505
26906
|
out.push({ rule: "L6", page: page.path, where: where2, message: `${where2} is ${JSON.stringify(source ?? null)}; it names the summary handler whose measures are the rail's counts` });
|
|
26506
26907
|
continue;
|
|
26507
26908
|
}
|
|
26508
26909
|
const handler = handlers[source];
|
|
26509
|
-
if (!
|
|
26910
|
+
if (!isPlainObject15(handler) || handler.role !== "summary") {
|
|
26510
26911
|
out.push({
|
|
26511
26912
|
rule: "L6",
|
|
26512
26913
|
page: page.path,
|
|
26513
26914
|
where: where2,
|
|
26514
|
-
message: `${where2} is "${source}", which is ${
|
|
26915
|
+
message: `${where2} is "${source}", which is ${isPlainObject15(handler) ? `a ${JSON.stringify(handler.role ?? null)} handler` : "not a handler of this app"}; the rail's counts are one number per measure, which a summary handler answers`
|
|
26515
26916
|
});
|
|
26516
26917
|
continue;
|
|
26517
26918
|
}
|
|
26518
|
-
if (!
|
|
26919
|
+
if (!isPlainObject15(handler.measures) || measuresChecked.has(source)) continue;
|
|
26519
26920
|
measuresChecked.add(source);
|
|
26520
26921
|
for (const { entry, index } of measured) {
|
|
26521
26922
|
if (Object.prototype.hasOwnProperty.call(handler.measures, entry.measure)) continue;
|
|
@@ -26531,17 +26932,17 @@ function navCountProblems2(contract) {
|
|
|
26531
26932
|
}
|
|
26532
26933
|
function appRoles2(contract) {
|
|
26533
26934
|
const raw = Array.isArray(contract?.backend?.roles) ? contract.backend.roles : [];
|
|
26534
|
-
return raw.map((role) => typeof role === "string" ? { name: role, sees: [] } : { name: role?.name, sees: Array.isArray(role?.sees) ? role.sees.filter(
|
|
26935
|
+
return raw.map((role) => typeof role === "string" ? { name: role, sees: [] } : { name: role?.name, sees: Array.isArray(role?.sees) ? role.sees.filter(named10) : [] }).filter((role) => named10(role.name));
|
|
26535
26936
|
}
|
|
26536
26937
|
function railReachProblems2(contract) {
|
|
26537
26938
|
const out = [];
|
|
26538
26939
|
const rail = contract?.app?.shell?.rail;
|
|
26539
|
-
if (!
|
|
26940
|
+
if (!isPlainObject15(rail)) return out;
|
|
26540
26941
|
const roles = appRoles2(contract);
|
|
26541
26942
|
if (roles.length === 0) return out;
|
|
26542
26943
|
const declared = new Map(roles.map((role) => [role.name, role]));
|
|
26543
|
-
const pages = new Map((Array.isArray(contract?.pages) ? contract.pages : []).filter((page) =>
|
|
26544
|
-
const openers = (page) => Array.isArray(page?.roles) && page.roles.filter(
|
|
26944
|
+
const pages = new Map((Array.isArray(contract?.pages) ? contract.pages : []).filter((page) => named10(page?.path)).map((page) => [page.path, page]));
|
|
26945
|
+
const openers = (page) => Array.isArray(page?.roles) && page.roles.filter(named10).length > 0 ? page.roles.filter(named10) : null;
|
|
26545
26946
|
const canOpen = (name, page) => {
|
|
26546
26947
|
const open2 = openers(page);
|
|
26547
26948
|
if (open2 === null) return true;
|
|
@@ -26550,11 +26951,11 @@ function railReachProblems2(contract) {
|
|
|
26550
26951
|
};
|
|
26551
26952
|
for (const key of ["entries", "footer"]) {
|
|
26552
26953
|
for (const [index, entry] of (Array.isArray(rail[key]) ? rail[key] : []).entries()) {
|
|
26553
|
-
if (!
|
|
26954
|
+
if (!isPlainObject15(entry) || !named10(entry.label) || !named10(entry.page)) continue;
|
|
26554
26955
|
const page = pages.get(entry.page);
|
|
26555
26956
|
if (page === void 0) continue;
|
|
26556
26957
|
const where2 = `app.shell.rail.${key}[${index}]`;
|
|
26557
|
-
const viewers =
|
|
26958
|
+
const viewers = named10(entry.role) ? entry.role.split(/\s+/).filter(named10) : openers(page) ?? roles.map((role) => role.name);
|
|
26558
26959
|
const known = viewers.filter((name) => declared.has(name));
|
|
26559
26960
|
if (known.length === 0) {
|
|
26560
26961
|
out.push({ rule: "L7", where: where2, message: `rail ${key === "footer" ? "footer link" : "entry"} "${entry.label}" is drawn for ${viewers.map((one) => JSON.stringify(one)).join(", ")}, and the app declares none of them (${roles.map((role) => JSON.stringify(role.name)).join(", ")}), so nobody sees it` });
|
|
@@ -26581,35 +26982,35 @@ var PAGES_FIX2 = "state `app.shell` with `nav: rail`; with no `rail.entries` the
|
|
|
26581
26982
|
var COUNTS_FIX2 = "state `app.shell` with `nav: rail` and one entry per state, each counting a measure of that summary (`pages[].nav.counts.source`); look.md rule 1";
|
|
26582
26983
|
function isAppContract2(contract) {
|
|
26583
26984
|
const kind = contract?.app?.kind;
|
|
26584
|
-
return kind !== "dashboard" && (kind === "app" ||
|
|
26985
|
+
return kind !== "dashboard" && (kind === "app" || isPlainObject15(contract?.data?.app));
|
|
26585
26986
|
}
|
|
26586
26987
|
function tablesOf3(contract) {
|
|
26587
26988
|
const tables = contract?.app?.data_model?.tables;
|
|
26588
|
-
return
|
|
26989
|
+
return isPlainObject15(tables) ? tables : {};
|
|
26589
26990
|
}
|
|
26590
26991
|
function enumOf2(column) {
|
|
26591
26992
|
return Array.isArray(column?.enum) ? column.enum.filter((one) => typeof one === "string") : [];
|
|
26592
26993
|
}
|
|
26593
26994
|
function stateColumn2(contract, table) {
|
|
26594
|
-
const columns =
|
|
26995
|
+
const columns = isPlainObject15(tablesOf3(contract)[table]?.columns) ? tablesOf3(contract)[table].columns : {};
|
|
26595
26996
|
return Object.keys(columns).find((name) => STATE_NAMES2.includes(name) && enumOf2(columns[name]).length >= 3) ?? null;
|
|
26596
26997
|
}
|
|
26597
26998
|
function realValues2(param) {
|
|
26598
26999
|
return Array.isArray(param?.values) ? param.values.map(String).filter((value) => !CATCH_ALL2.has(value.toLowerCase())) : [];
|
|
26599
27000
|
}
|
|
26600
27001
|
function ownPages2(contract) {
|
|
26601
|
-
return (Array.isArray(contract?.pages) ? contract.pages : []).filter((page) => page?.owned !== true &&
|
|
27002
|
+
return (Array.isArray(contract?.pages) ? contract.pages : []).filter((page) => page?.owned !== true && named10(page?.path));
|
|
26602
27003
|
}
|
|
26603
27004
|
function folderParams2(contract) {
|
|
26604
27005
|
const enums = [];
|
|
26605
27006
|
for (const [table, spec] of Object.entries(tablesOf3(contract))) {
|
|
26606
|
-
const columns =
|
|
27007
|
+
const columns = isPlainObject15(spec?.columns) ? spec.columns : {};
|
|
26607
27008
|
for (const name of STATE_NAMES2) if (enumOf2(columns[name]).length >= 3) enums.push({ where: `${table}.${name}`, values: new Set(enumOf2(columns[name])) });
|
|
26608
27009
|
}
|
|
26609
27010
|
const out = [];
|
|
26610
27011
|
for (const page of ownPages2(contract)) {
|
|
26611
27012
|
for (const param of Array.isArray(page.params) ? page.params : []) {
|
|
26612
|
-
if (!PICK_KINDS2.has(param?.kind) || !
|
|
27013
|
+
if (!PICK_KINDS2.has(param?.kind) || !named10(param?.name)) continue;
|
|
26613
27014
|
const values = realValues2(param);
|
|
26614
27015
|
if (STATE_NAMES2.includes(param.name) && (values.length >= 3 || !Array.isArray(param.values))) {
|
|
26615
27016
|
out.push({ page, param, values, why: `named "${param.name}"` });
|
|
@@ -26623,18 +27024,18 @@ function folderParams2(contract) {
|
|
|
26623
27024
|
return out;
|
|
26624
27025
|
}
|
|
26625
27026
|
function tableOf3(contract, element) {
|
|
26626
|
-
const props =
|
|
26627
|
-
if (
|
|
27027
|
+
const props = isPlainObject15(element?.props) ? element.props : {};
|
|
27028
|
+
if (named10(props.entity)) return props.entity;
|
|
26628
27029
|
const source = props.source;
|
|
26629
|
-
if (!
|
|
27030
|
+
if (!named10(source)) return null;
|
|
26630
27031
|
const handler = contract?.data?.app?.handlers?.[source];
|
|
26631
|
-
if (
|
|
27032
|
+
if (isPlainObject15(handler) && named10(handler.table)) return handler.table;
|
|
26632
27033
|
const read = contract?.data?.sources?.[source];
|
|
26633
|
-
if (
|
|
27034
|
+
if (isPlainObject15(read?.from) && named10(read.from.table)) return read.from.table;
|
|
26634
27035
|
return null;
|
|
26635
27036
|
}
|
|
26636
27037
|
function listingOn2(contract, page, test) {
|
|
26637
|
-
const elements =
|
|
27038
|
+
const elements = isPlainObject15(contract?.elements) ? contract.elements : {};
|
|
26638
27039
|
for (const [uid, element] of Object.entries(elements)) {
|
|
26639
27040
|
if (element?.page !== page.path || !ROW_TYPES2.has(element.type)) continue;
|
|
26640
27041
|
const table = tableOf3(contract, element);
|
|
@@ -26643,27 +27044,27 @@ function listingOn2(contract, page, test) {
|
|
|
26643
27044
|
return null;
|
|
26644
27045
|
}
|
|
26645
27046
|
function stateCountColumn2(spec) {
|
|
26646
|
-
if (!
|
|
26647
|
-
const where2 = spec.count === true ?
|
|
27047
|
+
if (!isPlainObject15(spec) || NOT_COUNTS2.some((key) => spec[key] !== void 0)) return null;
|
|
27048
|
+
const where2 = spec.count === true ? isPlainObject15(spec.where) ? spec.where : {} : spec.count === void 0 ? spec : null;
|
|
26648
27049
|
if (where2 === null) return null;
|
|
26649
27050
|
return Object.keys(where2).map((key) => key.split("__")[0]).find((column) => STATE_NAMES2.includes(column)) ?? null;
|
|
26650
27051
|
}
|
|
26651
27052
|
function stateCounts2(handler) {
|
|
26652
27053
|
const out = {};
|
|
26653
|
-
for (const [measure, spec] of Object.entries(
|
|
27054
|
+
for (const [measure, spec] of Object.entries(isPlainObject15(handler?.measures) ? handler.measures : {})) {
|
|
26654
27055
|
const column = stateCountColumn2(spec);
|
|
26655
27056
|
if (column !== null) out[measure] = column;
|
|
26656
27057
|
}
|
|
26657
27058
|
return out;
|
|
26658
27059
|
}
|
|
26659
27060
|
function pageReads2(contract, page, name) {
|
|
26660
|
-
const elements =
|
|
27061
|
+
const elements = isPlainObject15(contract?.elements) ? contract.elements : {};
|
|
26661
27062
|
if (page?.nav?.counts?.source === name) return true;
|
|
26662
27063
|
return Object.values(elements).some((element) => element?.page === page.path && element.props?.source === name);
|
|
26663
27064
|
}
|
|
26664
27065
|
function shellExpectedProblems2(contract) {
|
|
26665
27066
|
if (!isAppContract2(contract)) return [];
|
|
26666
|
-
const shell =
|
|
27067
|
+
const shell = isPlainObject15(contract?.app?.shell) ? contract.app.shell : {};
|
|
26667
27068
|
if (shell.nav === "rail") return [];
|
|
26668
27069
|
const out = [];
|
|
26669
27070
|
const say = (page, param, where2, message, fix) => out.push({ rule: "L8", code: "shell-expected", page, param, where: where2, message, fix });
|
|
@@ -26676,9 +27077,9 @@ function shellExpectedProblems2(contract) {
|
|
|
26676
27077
|
const listed = values.length > 0 ? ` with ${values.length} values (${values.map((one) => JSON.stringify(one)).join(", ")})` : " (its values from elsewhere)";
|
|
26677
27078
|
say(page.path, param.name, `${page.path} params.${param.name}`, `${page.path} declares ${param.kind} param "${param.name}" (${why})${listed} and the app ${has}, so its folders are a dropdown with no counts; they are the rail's entries`, VALUES_FIX2);
|
|
26678
27079
|
}
|
|
26679
|
-
const handlers =
|
|
27080
|
+
const handlers = isPlainObject15(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
26680
27081
|
for (const [name, handler] of Object.entries(handlers)) {
|
|
26681
|
-
if (handler?.role !== "summary" || !
|
|
27082
|
+
if (handler?.role !== "summary" || !named10(handler.table)) continue;
|
|
26682
27083
|
const column = stateColumn2(contract, handler.table);
|
|
26683
27084
|
const counts = Object.fromEntries(Object.entries(stateCounts2(handler)).filter(([, by]) => by === column));
|
|
26684
27085
|
if (column === null || Object.keys(counts).length < 3) continue;
|
|
@@ -26700,8 +27101,8 @@ function shellExpectedProblems2(contract) {
|
|
|
26700
27101
|
}
|
|
26701
27102
|
function kpiStripWorkflowProblems2(contract) {
|
|
26702
27103
|
if (!isAppContract2(contract)) return [];
|
|
26703
|
-
const elements =
|
|
26704
|
-
const handlers =
|
|
27104
|
+
const elements = isPlainObject15(contract?.elements) ? contract.elements : {};
|
|
27105
|
+
const handlers = isPlainObject15(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
26705
27106
|
const folderPages = /* @__PURE__ */ new Map();
|
|
26706
27107
|
for (const { page, param } of folderParams2(contract)) if (!folderPages.has(page.path)) folderPages.set(page.path, param.name);
|
|
26707
27108
|
const out = [];
|
|
@@ -26718,14 +27119,14 @@ function kpiStripWorkflowProblems2(contract) {
|
|
|
26718
27119
|
const bySource = /* @__PURE__ */ new Map();
|
|
26719
27120
|
for (const uid of stats) {
|
|
26720
27121
|
const source = elements[uid]?.props?.source;
|
|
26721
|
-
if (!
|
|
27122
|
+
if (!named10(source) || handlers[source]?.role !== "summary") continue;
|
|
26722
27123
|
if (!bySource.has(source)) bySource.set(source, []);
|
|
26723
27124
|
bySource.get(source).push(uid);
|
|
26724
27125
|
}
|
|
26725
27126
|
for (const [source, group] of bySource) {
|
|
26726
27127
|
if (group.length < 2) continue;
|
|
26727
27128
|
const measures = handlers[source].measures;
|
|
26728
|
-
const known =
|
|
27129
|
+
const known = isPlainObject15(measures) && group.every((uid) => Object.prototype.hasOwnProperty.call(measures, elements[uid]?.props?.measure));
|
|
26729
27130
|
const counts = stateCounts2(handlers[source]);
|
|
26730
27131
|
if (known ? !group.every((uid) => counts[elements[uid].props.measure] !== void 0) : group.length !== stats.length) continue;
|
|
26731
27132
|
const where2 = section === null ? page.path : `band "${section.id}" on ${page.path}`;
|
|
@@ -27159,7 +27560,7 @@ function rolesOf5(manifest) {
|
|
|
27159
27560
|
// ../flows/appspace-builder/kit/checks/rules/ux.mjs
|
|
27160
27561
|
var RULES4 = ["A3.23", "A3.24", "A3.25", "A3.26", "A3.27"];
|
|
27161
27562
|
var STATE_COLUMNS = ["status", "state"];
|
|
27162
|
-
var
|
|
27563
|
+
var isPlainObject16 = (v) => Boolean(v) && typeof v === "object" && !Array.isArray(v);
|
|
27163
27564
|
var said = (v) => typeof v === "string" && v.trim() !== "" ? v : null;
|
|
27164
27565
|
var OPENERS4 = /* @__PURE__ */ new Set(["dialog", "list", "queue", "detail"]);
|
|
27165
27566
|
var ROWS = /* @__PURE__ */ new Set(["list", "queue", "data_grid"]);
|
|
@@ -27168,9 +27569,9 @@ function checkUx(app, contract, contractError) {
|
|
|
27168
27569
|
const reason = `the contract could not be extracted (${contractError ?? "no reason given"}), so the forms, lists and dialogs are not knowable here`;
|
|
27169
27570
|
return { violations: [], skipped: RULES4.map((rule) => ({ rule, reason })) };
|
|
27170
27571
|
}
|
|
27171
|
-
const elements =
|
|
27172
|
-
const handlers =
|
|
27173
|
-
const tables =
|
|
27572
|
+
const elements = isPlainObject16(contract.elements) ? contract.elements : {};
|
|
27573
|
+
const handlers = isPlainObject16(contract.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
27574
|
+
const tables = isPlainObject16(contract.app?.data_model?.tables) ? contract.app.data_model.tables : {};
|
|
27174
27575
|
const uids = Object.keys(elements).sort(byPageThenRank);
|
|
27175
27576
|
const kidsOf = (uid) => {
|
|
27176
27577
|
const el = elements[uid];
|
|
@@ -27211,15 +27612,15 @@ function checkUx(app, contract, contractError) {
|
|
|
27211
27612
|
if (!ROWS.has(el?.type)) continue;
|
|
27212
27613
|
if (ancestors(uid).some((up) => up.type === "detail")) continue;
|
|
27213
27614
|
const table = tableOf4(el);
|
|
27214
|
-
if (!table || table.startsWith("_") || !
|
|
27615
|
+
if (!table || table.startsWith("_") || !isPlainObject16(tables[table]?.columns) || tables[table].singleton === true) continue;
|
|
27215
27616
|
const columns = tables[table].columns;
|
|
27216
|
-
const pk = Object.keys(columns).find((c) =>
|
|
27617
|
+
const pk = Object.keys(columns).find((c) => isPlainObject16(columns[c]) && columns[c].pk === true) ?? "id";
|
|
27217
27618
|
const fields = Object.keys(columns).filter((c) => c !== pk && !c.startsWith("_"));
|
|
27218
27619
|
let shown2;
|
|
27219
27620
|
let opens3;
|
|
27220
27621
|
if (el.type === "data_grid") {
|
|
27221
|
-
const typed =
|
|
27222
|
-
shown2 = new Set((Array.isArray(typed.columns) ? typed.columns : []).map((c) =>
|
|
27622
|
+
const typed = isPlainObject16(el.props?.props) ? el.props.props : {};
|
|
27623
|
+
shown2 = new Set((Array.isArray(typed.columns) ? typed.columns : []).map((c) => isPlainObject16(c) ? c.field : c).filter(said));
|
|
27223
27624
|
opens3 = Boolean(said(typed.on_row)) || Array.isArray(typed.row_actions) && typed.row_actions.length > 0 || typed.row_detail === "drawer" || bindsOn.has(el.page);
|
|
27224
27625
|
} else {
|
|
27225
27626
|
const kids = kidsOf(uid).map((k) => elements[k]);
|
|
@@ -27229,17 +27630,17 @@ function checkUx(app, contract, contractError) {
|
|
|
27229
27630
|
if (opens3) continue;
|
|
27230
27631
|
const hidden = fields.filter((c) => !shown2.has(c));
|
|
27231
27632
|
if (hidden.length === 0) continue;
|
|
27232
|
-
const
|
|
27633
|
+
const named13 = hidden.slice(0, 4).map((c) => `"${c}"`).join(", ") + (hidden.length > 4 ? ` and ${hidden.length - 4} more` : "");
|
|
27233
27634
|
violations.push({
|
|
27234
27635
|
rule: "A3.24",
|
|
27235
27636
|
where: uid,
|
|
27236
|
-
message: `${el.type} "${nameOf5(uid)}" on ${el.page} shows ${shown2.size} of ${table}'s ${fields.length} columns and gives no way to open a row \u2014 no detail, no row action, no on_row (row-no-detail) \u2014 so ${
|
|
27637
|
+
message: `${el.type} "${nameOf5(uid)}" on ${el.page} shows ${shown2.size} of ${table}'s ${fields.length} columns and gives no way to open a row \u2014 no detail, no row action, no on_row (row-no-detail) \u2014 so ${named13} can never be read. ` + (el.type === "data_grid" ? "Give it an on_row or a row action that opens a dialog, or show it as a list with a detail child" : 'Give it a detail child ({"type": "detail", "props": {"entity": "' + table + '"}}): a row click opens the record in a drawer, and a detail that names no field shows every column')
|
|
27237
27638
|
});
|
|
27238
27639
|
}
|
|
27239
27640
|
for (const uid of uids) {
|
|
27240
27641
|
const el = elements[uid];
|
|
27241
27642
|
if (el?.type !== "dialog") continue;
|
|
27242
|
-
const typed =
|
|
27643
|
+
const typed = isPlainObject16(el.props?.props) ? el.props.props : {};
|
|
27243
27644
|
if (typed.values_from === "row") continue;
|
|
27244
27645
|
if (said(typed.trigger) || said(el.props?.label) || said(typed.title)) continue;
|
|
27245
27646
|
violations.push({
|
|
@@ -27252,8 +27653,8 @@ function checkUx(app, contract, contractError) {
|
|
|
27252
27653
|
const el = elements[uid];
|
|
27253
27654
|
if (!ROWS.has(el?.type) || el.type === "data_grid") continue;
|
|
27254
27655
|
const table = tableOf4(el);
|
|
27255
|
-
const columns =
|
|
27256
|
-
const state = STATE_COLUMNS.find((c) =>
|
|
27656
|
+
const columns = isPlainObject16(tables[table]?.columns) ? tables[table].columns : {};
|
|
27657
|
+
const state = STATE_COLUMNS.find((c) => isPlainObject16(columns[c]) && Array.isArray(columns[c].enum));
|
|
27257
27658
|
const seen = /* @__PURE__ */ new Map();
|
|
27258
27659
|
for (const kid of kidsOf(uid)) {
|
|
27259
27660
|
const action = elements[kid];
|
|
@@ -27294,8 +27695,8 @@ function split(uid) {
|
|
|
27294
27695
|
}
|
|
27295
27696
|
|
|
27296
27697
|
// ../flows/appspace-builder/kit/contract/workflow.mjs
|
|
27297
|
-
var
|
|
27298
|
-
var
|
|
27698
|
+
var isPlainObject17 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
27699
|
+
var named11 = (value) => typeof value === "string" && value !== "";
|
|
27299
27700
|
function either5(items) {
|
|
27300
27701
|
if (items.length <= 1) return items.join("");
|
|
27301
27702
|
return `${items.slice(0, -1).join(", ")} or ${items.at(-1)}`;
|
|
@@ -27311,16 +27712,16 @@ function roleModel2(contract) {
|
|
|
27311
27712
|
const sees = /* @__PURE__ */ new Map();
|
|
27312
27713
|
const home = /* @__PURE__ */ new Map();
|
|
27313
27714
|
for (const row of rows2) {
|
|
27314
|
-
if (!
|
|
27715
|
+
if (!isPlainObject17(row) || !named11(row.name) || names.includes(row.name)) continue;
|
|
27315
27716
|
names.push(row.name);
|
|
27316
|
-
sees.set(row.name, new Set((Array.isArray(row.sees) ? row.sees : []).filter(
|
|
27317
|
-
if (
|
|
27717
|
+
sees.set(row.name, new Set((Array.isArray(row.sees) ? row.sees : []).filter(named11)));
|
|
27718
|
+
if (named11(row.home)) home.set(row.name, row.home);
|
|
27318
27719
|
}
|
|
27319
27720
|
return { names, sees, home };
|
|
27320
27721
|
}
|
|
27321
27722
|
function gateRoles2(gate) {
|
|
27322
|
-
if (Array.isArray(gate)) return gate.filter(
|
|
27323
|
-
return
|
|
27723
|
+
if (Array.isArray(gate)) return gate.filter(named11);
|
|
27724
|
+
return named11(gate) ? gate.split(/[\s,]+/).filter(named11) : [];
|
|
27324
27725
|
}
|
|
27325
27726
|
function seesGate2(model, role, gate) {
|
|
27326
27727
|
const wanted = gateRoles2(gate);
|
|
@@ -27338,7 +27739,7 @@ var HREF2 = /\bhref\s*=\s*["']([^"']*)["']/i;
|
|
|
27338
27739
|
var ROLE_ATTR4 = /\bdata-dr-role\s*=\s*["']([^"']*)["']/i;
|
|
27339
27740
|
var PAGE_LITERAL2 = /["'`](?:\.\/)?([A-Za-z0-9_-]+\.html)(?:[?#][^"'`]*)?["'`]/g;
|
|
27340
27741
|
function targetOf2(href) {
|
|
27341
|
-
if (!
|
|
27742
|
+
if (!named11(href) || /^(?:[a-z]+:)?\/\//i.test(href) || href.startsWith("/") || href.startsWith("#")) return null;
|
|
27342
27743
|
const file = href.replace(/^\.\//, "").replace(/[?#].*$/, "");
|
|
27343
27744
|
if (!/^[A-Za-z0-9_-]+\.html$/.test(file)) return null;
|
|
27344
27745
|
return `ui/${file}`;
|
|
@@ -27376,7 +27777,7 @@ function moduleLinks2(source) {
|
|
|
27376
27777
|
return out;
|
|
27377
27778
|
}
|
|
27378
27779
|
function pagesOf2(contract) {
|
|
27379
|
-
return (Array.isArray(contract?.pages) ? contract.pages : []).filter((page) =>
|
|
27780
|
+
return (Array.isArray(contract?.pages) ? contract.pages : []).filter((page) => named11(page?.path));
|
|
27380
27781
|
}
|
|
27381
27782
|
function entryPage2(contract) {
|
|
27382
27783
|
const pages = pagesOf2(contract);
|
|
@@ -27395,10 +27796,10 @@ function reachByRole2(contract, { pageSource = () => void 0, moduleSource = () =
|
|
|
27395
27796
|
const model = roleModel2(contract);
|
|
27396
27797
|
const pages = pagesOf2(contract);
|
|
27397
27798
|
const byPath = new Map(pages.map((page) => [page.path, page]));
|
|
27398
|
-
const elements =
|
|
27799
|
+
const elements = isPlainObject17(contract?.elements) ? contract.elements : {};
|
|
27399
27800
|
const modulesOn = /* @__PURE__ */ new Map();
|
|
27400
27801
|
for (const element of Object.values(elements)) {
|
|
27401
|
-
if (element?.type !== "custom" || !
|
|
27802
|
+
if (element?.type !== "custom" || !named11(element.page) || !named11(element.props?.module)) continue;
|
|
27402
27803
|
if (!modulesOn.has(element.page)) modulesOn.set(element.page, /* @__PURE__ */ new Set());
|
|
27403
27804
|
modulesOn.get(element.page).add(element.props.module);
|
|
27404
27805
|
}
|
|
@@ -27523,23 +27924,23 @@ function moduleAsks2(source) {
|
|
|
27523
27924
|
return ASKS2.some((pattern) => pattern.test(String(source ?? "")));
|
|
27524
27925
|
}
|
|
27525
27926
|
function decideAsks2(row) {
|
|
27526
|
-
if (row?.role !== "decide" || !
|
|
27927
|
+
if (row?.role !== "decide" || !isPlainObject17(row.decision)) return false;
|
|
27527
27928
|
const names = Object.keys(row.decision);
|
|
27528
27929
|
if (names.length !== 1) return false;
|
|
27529
27930
|
const spec = row.decision[names[0]];
|
|
27530
|
-
return
|
|
27931
|
+
return isPlainObject17(spec) && (spec.note === "required" || spec.note === "optional");
|
|
27531
27932
|
}
|
|
27532
27933
|
function insideDialog2(elements, uid) {
|
|
27533
27934
|
const seen = /* @__PURE__ */ new Set();
|
|
27534
|
-
for (let at = elements[uid]?.parent;
|
|
27935
|
+
for (let at = elements[uid]?.parent; named11(at) && !seen.has(at); at = elements[at]?.parent) {
|
|
27535
27936
|
seen.add(at);
|
|
27536
27937
|
if (elements[at]?.type === "dialog") return true;
|
|
27537
27938
|
}
|
|
27538
27939
|
return false;
|
|
27539
27940
|
}
|
|
27540
27941
|
function stateCallers2(contract, { moduleSource = () => void 0, handlerSource = () => void 0 } = {}) {
|
|
27541
|
-
const elements =
|
|
27542
|
-
const handlers =
|
|
27942
|
+
const elements = isPlainObject17(contract?.elements) ? contract.elements : {};
|
|
27943
|
+
const handlers = isPlainObject17(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
27543
27944
|
const changing = new Set(Object.keys(handlers).filter((name) => changesState2(name, handlers[name], handlerSource(name))));
|
|
27544
27945
|
const out = /* @__PURE__ */ new Map();
|
|
27545
27946
|
const add = (handler, caller) => {
|
|
@@ -27549,10 +27950,10 @@ function stateCallers2(contract, { moduleSource = () => void 0, handlerSource =
|
|
|
27549
27950
|
};
|
|
27550
27951
|
for (const uid of Object.keys(elements).sort()) {
|
|
27551
27952
|
const element = elements[uid];
|
|
27552
|
-
const props =
|
|
27553
|
-
const typed =
|
|
27953
|
+
const props = isPlainObject17(element?.props) ? element.props : {};
|
|
27954
|
+
const typed = isPlainObject17(props.props) ? props.props : {};
|
|
27554
27955
|
const page = element?.page ?? null;
|
|
27555
|
-
const id =
|
|
27956
|
+
const id = named11(props.id) ? props.id : uid;
|
|
27556
27957
|
switch (element?.type) {
|
|
27557
27958
|
case "form":
|
|
27558
27959
|
add(props.action, { page, uid, id, what: "form", asks: true });
|
|
@@ -27563,32 +27964,32 @@ function stateCallers2(contract, { moduleSource = () => void 0, handlerSource =
|
|
|
27563
27964
|
case "data_grid":
|
|
27564
27965
|
add(typed.on_row, { page, uid, id, what: "grid row click", asks: false });
|
|
27565
27966
|
for (const action of Array.isArray(typed.row_actions) ? typed.row_actions : []) {
|
|
27566
|
-
if (!
|
|
27567
|
-
const asks = action.confirm === true ||
|
|
27967
|
+
if (!isPlainObject17(action) || !named11(action.call)) continue;
|
|
27968
|
+
const asks = action.confirm === true || named11(action.confirm);
|
|
27568
27969
|
add(action.call, { page, uid, id, what: `row action "${action.label ?? action.call}"`, asks });
|
|
27569
27970
|
}
|
|
27570
|
-
for (const action of
|
|
27571
|
-
if (!
|
|
27572
|
-
const asks = action.confirm === true ||
|
|
27971
|
+
for (const action of isPlainObject17(typed.selection) && Array.isArray(typed.selection.bulk_actions) ? typed.selection.bulk_actions : []) {
|
|
27972
|
+
if (!isPlainObject17(action) || !named11(action.call)) continue;
|
|
27973
|
+
const asks = action.confirm === true || named11(action.confirm);
|
|
27573
27974
|
add(action.call, { page, uid, id, what: `bulk action "${action.label ?? action.call}"`, asks });
|
|
27574
27975
|
}
|
|
27575
27976
|
break;
|
|
27576
27977
|
case "action":
|
|
27577
27978
|
case "detail": {
|
|
27578
|
-
if (!
|
|
27579
|
-
const asks = insideDialog2(elements, uid) || decideAsks2(handlers[props.action]) || props.confirm === true ||
|
|
27979
|
+
if (!named11(props.action)) break;
|
|
27980
|
+
const asks = insideDialog2(elements, uid) || decideAsks2(handlers[props.action]) || props.confirm === true || named11(props.confirm);
|
|
27580
27981
|
add(props.action, { page, uid, id, what: element.type === "action" ? "button" : "detail button", asks });
|
|
27581
27982
|
break;
|
|
27582
27983
|
}
|
|
27583
27984
|
case "custom": {
|
|
27584
|
-
const declares =
|
|
27985
|
+
const declares = isPlainObject17(props.declares) ? props.declares : {};
|
|
27585
27986
|
const called = /* @__PURE__ */ new Set([
|
|
27586
|
-
...Array.isArray(declares.calls) ? declares.calls.filter(
|
|
27587
|
-
...Object.keys(
|
|
27987
|
+
...Array.isArray(declares.calls) ? declares.calls.filter(named11) : [],
|
|
27988
|
+
...Object.keys(isPlainObject17(declares.produces) ? declares.produces : {})
|
|
27588
27989
|
]);
|
|
27589
|
-
const source =
|
|
27990
|
+
const source = named11(props.module) ? moduleSource(props.module) : void 0;
|
|
27590
27991
|
if (typeof source === "string") {
|
|
27591
|
-
for (const call of scanUiCalls2(source)) if (
|
|
27992
|
+
for (const call of scanUiCalls2(source)) if (named11(call.handler)) called.add(call.handler);
|
|
27592
27993
|
}
|
|
27593
27994
|
const asks = typeof source === "string" ? moduleAsks2(source) : null;
|
|
27594
27995
|
for (const handler of called) add(handler, { page, uid, id, what: `custom ${props.module ?? ""}`.trim(), asks, module: props.module });
|
|
@@ -27601,7 +28002,7 @@ function stateCallers2(contract, { moduleSource = () => void 0, handlerSource =
|
|
|
27601
28002
|
return out;
|
|
27602
28003
|
}
|
|
27603
28004
|
function unconfirmedStateChanges2(contract, files = {}) {
|
|
27604
|
-
const handlers =
|
|
28005
|
+
const handlers = isPlainObject17(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
27605
28006
|
const out = [];
|
|
27606
28007
|
for (const [handler, callers] of [...stateCallers2(contract, files).entries()].sort((a, b) => a[0] < b[0] ? -1 : 1)) {
|
|
27607
28008
|
const bare = callers.filter((one) => one.asks === false);
|
|
@@ -27673,7 +28074,7 @@ function requiresReason2(lines, key, vars) {
|
|
|
27673
28074
|
return false;
|
|
27674
28075
|
}
|
|
27675
28076
|
function unreadReasons2(contract, { handlerSource = () => void 0, moduleSource, pageSource } = {}) {
|
|
27676
|
-
const handlers =
|
|
28077
|
+
const handlers = isPlainObject17(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
27677
28078
|
const producers = payloadProducers2(contract, { moduleSource, pageSource });
|
|
27678
28079
|
const out = [];
|
|
27679
28080
|
for (const name of Object.keys(handlers).sort()) {
|
|
@@ -27683,13 +28084,13 @@ function unreadReasons2(contract, { handlerSource = () => void 0, moduleSource,
|
|
|
27683
28084
|
if (typeof source !== "string") continue;
|
|
27684
28085
|
const reads = pyPayloadReads2(source);
|
|
27685
28086
|
const read = /* @__PURE__ */ new Set([...reads.required, ...reads.optional]);
|
|
27686
|
-
const
|
|
28087
|
+
const own2 = (producers.get(name) ?? []).filter((one) => Array.isArray(one.keys));
|
|
27687
28088
|
const sent = /* @__PURE__ */ new Map();
|
|
27688
|
-
for (const one of
|
|
28089
|
+
for (const one of own2) {
|
|
27689
28090
|
for (const key of one.keys) {
|
|
27690
28091
|
if (!REASON_KEYS2.includes(key)) continue;
|
|
27691
28092
|
if (!sent.has(key)) sent.set(key, /* @__PURE__ */ new Set());
|
|
27692
|
-
if (
|
|
28093
|
+
if (named11(one.page)) sent.get(key).add(one.page);
|
|
27693
28094
|
}
|
|
27694
28095
|
}
|
|
27695
28096
|
for (const [key, pages] of [...sent.entries()].sort()) {
|
|
@@ -27712,7 +28113,7 @@ function unreadReasons2(contract, { handlerSource = () => void 0, moduleSource,
|
|
|
27712
28113
|
return out;
|
|
27713
28114
|
}
|
|
27714
28115
|
function unliftableRefusals2(contract, { handlerSource = () => void 0 } = {}) {
|
|
27715
|
-
const handlers =
|
|
28116
|
+
const handlers = isPlainObject17(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
27716
28117
|
const out = [];
|
|
27717
28118
|
for (const name of Object.keys(handlers).sort()) {
|
|
27718
28119
|
if ((handlers[name]?.role ?? "custom") !== "custom") continue;
|
|
@@ -27770,13 +28171,13 @@ function rolesLandingNowhere2(contract) {
|
|
|
27770
28171
|
function sourceRoles2(contract, name) {
|
|
27771
28172
|
const source = contract?.data?.sources?.[name];
|
|
27772
28173
|
const stated2 = gateRoles2(source?.requiresRole);
|
|
27773
|
-
const elements =
|
|
28174
|
+
const elements = isPlainObject17(contract?.elements) ? contract.elements : {};
|
|
27774
28175
|
const pages = new Map(pagesOf2(contract).map((page) => [page.path, page]));
|
|
27775
28176
|
const union = [];
|
|
27776
28177
|
let readers = 0;
|
|
27777
28178
|
let open2 = false;
|
|
27778
28179
|
for (const element of Object.values(elements)) {
|
|
27779
|
-
const props =
|
|
28180
|
+
const props = isPlainObject17(element?.props) ? element.props : {};
|
|
27780
28181
|
const reads = [props.source, ...Array.isArray(props.declares?.reads) ? props.declares.reads : []];
|
|
27781
28182
|
if (!reads.includes(name)) continue;
|
|
27782
28183
|
readers += 1;
|
|
@@ -27811,14 +28212,14 @@ function openToEveryRole2(contract) {
|
|
|
27811
28212
|
{ one: "page", many: "pages", verb: "open" },
|
|
27812
28213
|
"State pages[].roles on each: the roles it is for (all of them, if that is what you mean)."
|
|
27813
28214
|
);
|
|
27814
|
-
const handlers =
|
|
28215
|
+
const handlers = isPlainObject17(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
27815
28216
|
say(
|
|
27816
28217
|
"handler",
|
|
27817
28218
|
Object.keys(handlers).sort().filter((name) => gateRoles2(handlers[name]?.requiresRole).length === 0).map((name) => `"${name}"`),
|
|
27818
28219
|
{ one: "handler", many: "handlers", verb: "call" },
|
|
27819
28220
|
"State requiresRole on each: the roles that may call it (all of them, if that is what you mean)."
|
|
27820
28221
|
);
|
|
27821
|
-
const sources =
|
|
28222
|
+
const sources = isPlainObject17(contract?.data?.sources) ? contract.data.sources : {};
|
|
27822
28223
|
say(
|
|
27823
28224
|
"source",
|
|
27824
28225
|
Object.keys(sources).sort().filter((name) => {
|
|
@@ -28789,8 +29190,8 @@ function compareElements(want, got, diffs) {
|
|
|
28789
29190
|
}
|
|
28790
29191
|
}
|
|
28791
29192
|
function describeElement(element) {
|
|
28792
|
-
const
|
|
28793
|
-
if (
|
|
29193
|
+
const named13 = element["id"] ?? element["role"];
|
|
29194
|
+
if (named13 !== void 0 && named13 !== null) return describeValue(named13);
|
|
28794
29195
|
return `on ${describeValue(element["page"])}`;
|
|
28795
29196
|
}
|
|
28796
29197
|
function compare(want, got, at, diffs) {
|
|
@@ -28916,10 +29317,10 @@ async function loadContract(dir, opts) {
|
|
|
28916
29317
|
}
|
|
28917
29318
|
return { contract: assembled.contract, assemble: record2, headline, overlay };
|
|
28918
29319
|
}
|
|
28919
|
-
function pageDrafts(
|
|
28920
|
-
const doc =
|
|
28921
|
-
if (!isPlain(doc) || !Array.isArray(doc["drafts"]) || Object.keys(doc).length !== 1) return [
|
|
28922
|
-
return doc["drafts"].map((one, index) => ({ name: `${
|
|
29320
|
+
function pageDrafts(named13) {
|
|
29321
|
+
const doc = named13.doc;
|
|
29322
|
+
if (!isPlain(doc) || !Array.isArray(doc["drafts"]) || Object.keys(doc).length !== 1) return [named13];
|
|
29323
|
+
return doc["drafts"].map((one, index) => ({ name: `${named13.name}[${String(index + 1)}]`, doc: one }));
|
|
28923
29324
|
}
|
|
28924
29325
|
function nothingWritten(report) {
|
|
28925
29326
|
const transforms = report.assemble?.transforms ?? [];
|
|
@@ -30004,18 +30405,18 @@ async function resolveFile(path3, base, { read = readFile11, confine = false, re
|
|
|
30004
30405
|
const filename = basename2(full);
|
|
30005
30406
|
return { bytes, filename, mimeType: mimeTypeOf(filename) };
|
|
30006
30407
|
}
|
|
30007
|
-
function
|
|
30408
|
+
function isPlainObject18(value) {
|
|
30008
30409
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
30009
30410
|
}
|
|
30010
30411
|
function fileColumnsOf(model) {
|
|
30011
30412
|
const out = /* @__PURE__ */ new Set();
|
|
30012
|
-
const tables =
|
|
30013
|
-
if (!
|
|
30413
|
+
const tables = isPlainObject18(model) ? model["tables"] : void 0;
|
|
30414
|
+
if (!isPlainObject18(tables)) return out;
|
|
30014
30415
|
for (const table of Object.values(tables)) {
|
|
30015
|
-
const columns =
|
|
30016
|
-
if (!
|
|
30416
|
+
const columns = isPlainObject18(table) ? table["columns"] : void 0;
|
|
30417
|
+
if (!isPlainObject18(columns)) continue;
|
|
30017
30418
|
for (const [name, column] of Object.entries(columns)) {
|
|
30018
|
-
const type = typeof column === "string" ? column :
|
|
30419
|
+
const type = typeof column === "string" ? column : isPlainObject18(column) ? column["type"] : void 0;
|
|
30019
30420
|
if (type === "file") out.add(name);
|
|
30020
30421
|
}
|
|
30021
30422
|
}
|
|
@@ -30079,7 +30480,7 @@ function typeOf(value) {
|
|
|
30079
30480
|
if (Array.isArray(value)) return "array";
|
|
30080
30481
|
return typeof value;
|
|
30081
30482
|
}
|
|
30082
|
-
function
|
|
30483
|
+
function isPlainObject19(value) {
|
|
30083
30484
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
30084
30485
|
}
|
|
30085
30486
|
function describe6(value) {
|
|
@@ -30106,7 +30507,7 @@ function validateShape(shape, path3 = "$") {
|
|
|
30106
30507
|
}
|
|
30107
30508
|
return shape.length === 0 ? [] : validateShape(shape[0], `${path3}[]`);
|
|
30108
30509
|
}
|
|
30109
|
-
if (
|
|
30510
|
+
if (isPlainObject19(shape)) {
|
|
30110
30511
|
return Object.entries(shape).flatMap(
|
|
30111
30512
|
([key, sub]) => validateShape(sub, `${path3}.${key}`)
|
|
30112
30513
|
);
|
|
@@ -30135,8 +30536,8 @@ function matchShape(value, shape, path3 = "$") {
|
|
|
30135
30536
|
if (shape.length === 0) return [];
|
|
30136
30537
|
return value.flatMap((item, i) => matchShape(item, shape[0], `${path3}[${String(i)}]`));
|
|
30137
30538
|
}
|
|
30138
|
-
if (
|
|
30139
|
-
if (!
|
|
30539
|
+
if (isPlainObject19(shape)) {
|
|
30540
|
+
if (!isPlainObject19(value)) {
|
|
30140
30541
|
return [`${path3}: expected object, got ${typeOf(value)} (${describe6(value)}).`];
|
|
30141
30542
|
}
|
|
30142
30543
|
return Object.entries(shape).flatMap(
|
|
@@ -30146,8 +30547,8 @@ function matchShape(value, shape, path3 = "$") {
|
|
|
30146
30547
|
return [`${path3}: ${describe6(shape)} is not a shape.`];
|
|
30147
30548
|
}
|
|
30148
30549
|
function matchExpect(value, expected, path3 = "$") {
|
|
30149
|
-
if (
|
|
30150
|
-
if (!
|
|
30550
|
+
if (isPlainObject19(expected)) {
|
|
30551
|
+
if (!isPlainObject19(value)) {
|
|
30151
30552
|
return [`${path3}: expected an object, got ${typeOf(value)} (${describe6(value)}).`];
|
|
30152
30553
|
}
|
|
30153
30554
|
return Object.entries(expected).flatMap(
|
|
@@ -30178,7 +30579,7 @@ function isUiEntry(entry) {
|
|
|
30178
30579
|
}
|
|
30179
30580
|
function parseError(raw, where2) {
|
|
30180
30581
|
if (raw === true) return {};
|
|
30181
|
-
if (!
|
|
30582
|
+
if (!isPlainObject19(raw)) {
|
|
30182
30583
|
throw new FixtureError(
|
|
30183
30584
|
`${where2}: "error" is true, or an object with "type" and/or "contains" \u2014 not ${describe6(raw)}.`
|
|
30184
30585
|
);
|
|
@@ -30196,12 +30597,12 @@ function parseError(raw, where2) {
|
|
|
30196
30597
|
}
|
|
30197
30598
|
var FILE_FIELD = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
30198
30599
|
function parseFiles(raw, payload, where2) {
|
|
30199
|
-
if (!
|
|
30600
|
+
if (!isPlainObject19(raw)) {
|
|
30200
30601
|
throw new FixtureError(
|
|
30201
30602
|
`${where2}: "files" maps a payload key to "sample" or a path in the app folder \u2014 not ${describe6(raw)}.`
|
|
30202
30603
|
);
|
|
30203
30604
|
}
|
|
30204
|
-
if (!
|
|
30605
|
+
if (!isPlainObject19(payload)) {
|
|
30205
30606
|
throw new FixtureError(
|
|
30206
30607
|
`${where2}: a case with "files" puts {"upload": \u2026} under a payload key, so its "payload" is an object.`
|
|
30207
30608
|
);
|
|
@@ -30227,7 +30628,7 @@ function parseFiles(raw, payload, where2) {
|
|
|
30227
30628
|
}
|
|
30228
30629
|
function parseCase(handler, raw, index) {
|
|
30229
30630
|
const where2 = `handlers.${handler}[${String(index)}]`;
|
|
30230
|
-
if (!
|
|
30631
|
+
if (!isPlainObject19(raw)) {
|
|
30231
30632
|
throw new FixtureError(`${where2}: a case is an object, not ${describe6(raw)}.`);
|
|
30232
30633
|
}
|
|
30233
30634
|
const name = raw["name"];
|
|
@@ -30260,11 +30661,11 @@ function parseCase(handler, raw, index) {
|
|
|
30260
30661
|
return kase;
|
|
30261
30662
|
}
|
|
30262
30663
|
function parseFixtures(raw, source) {
|
|
30263
|
-
if (!
|
|
30664
|
+
if (!isPlainObject19(raw)) {
|
|
30264
30665
|
throw new FixtureError(`${source}: the fixtures file is a JSON object, not ${describe6(raw)}.`);
|
|
30265
30666
|
}
|
|
30266
30667
|
const handlersRaw = raw["handlers"] ?? {};
|
|
30267
|
-
if (!
|
|
30668
|
+
if (!isPlainObject19(handlersRaw)) {
|
|
30268
30669
|
throw new FixtureError(`${source}: "handlers" is an object keyed by handler name.`);
|
|
30269
30670
|
}
|
|
30270
30671
|
const cases = [];
|
|
@@ -30284,7 +30685,7 @@ function parseFixtures(raw, source) {
|
|
|
30284
30685
|
});
|
|
30285
30686
|
}
|
|
30286
30687
|
const uiRaw = raw["ui"] ?? {};
|
|
30287
|
-
if (!
|
|
30688
|
+
if (!isPlainObject19(uiRaw)) {
|
|
30288
30689
|
throw new FixtureError(`${source}: "ui" is an object.`);
|
|
30289
30690
|
}
|
|
30290
30691
|
const entry = uiRaw["entry"] ?? "index.html";
|
|
@@ -30995,7 +31396,7 @@ function nullTexts(document) {
|
|
|
30995
31396
|
out.push({ literal: match[0], near: near.length <= NULL_TEXT_CONTEXT ? near : `${near.slice(0, NULL_TEXT_CONTEXT - 1)}\u2026` });
|
|
30996
31397
|
}
|
|
30997
31398
|
};
|
|
30998
|
-
const
|
|
31399
|
+
const own2 = /* @__PURE__ */ new Map();
|
|
30999
31400
|
const walker = doc.createTreeWalker(
|
|
31000
31401
|
body,
|
|
31001
31402
|
4
|
|
@@ -31004,9 +31405,9 @@ function nullTexts(document) {
|
|
|
31004
31405
|
for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) {
|
|
31005
31406
|
const parent = node.parentElement;
|
|
31006
31407
|
if (parent === null) continue;
|
|
31007
|
-
|
|
31408
|
+
own2.set(parent, (own2.get(parent) ?? "") + (node.nodeValue ?? ""));
|
|
31008
31409
|
}
|
|
31009
|
-
for (const [parent, text3] of
|
|
31410
|
+
for (const [parent, text3] of own2) {
|
|
31010
31411
|
NULL_LITERAL.lastIndex = 0;
|
|
31011
31412
|
if (!NULL_LITERAL.test(text3)) continue;
|
|
31012
31413
|
NULL_LITERAL.lastIndex = 0;
|
|
@@ -31937,9 +32338,9 @@ async function runUiSmoke(opts) {
|
|
|
31937
32338
|
}
|
|
31938
32339
|
const takers = how.readers === void 0 ? null : how.readers[name] ?? [];
|
|
31939
32340
|
if (takers !== null && !refetched.some((call) => takers.some((source) => callsHandler(call.url, source)))) {
|
|
31940
|
-
const
|
|
32341
|
+
const named13 = takers.length === 0 ? "no source honours it" : `none of ${takers.join(", ")} (the sources that honour it) refetched`;
|
|
31941
32342
|
return {
|
|
31942
|
-
inconclusive: `moving it re-read no source that takes it: ${
|
|
32343
|
+
inconclusive: `moving it re-read no source that takes it: ${named13} within ${String(graceMs)}ms, so no transform was asked with it moved`
|
|
31943
32344
|
};
|
|
31944
32345
|
}
|
|
31945
32346
|
return judged;
|
|
@@ -32145,13 +32546,13 @@ async function readRoleView(client, appId) {
|
|
|
32145
32546
|
problem: `could not read which role you hold on this app (${err instanceof Error ? err.message : String(err)}); only its owner can view its preview as another role`
|
|
32146
32547
|
};
|
|
32147
32548
|
}
|
|
32148
|
-
const
|
|
32549
|
+
const own2 = typeof mine.role === "string" ? mine.role : null;
|
|
32149
32550
|
const roles = Array.isArray(mine.roles) ? mine.roles.filter((one) => typeof one === "string") : [];
|
|
32150
|
-
const view = { own, roles, allowed: {} };
|
|
32151
|
-
if (
|
|
32551
|
+
const view = { own: own2, roles, allowed: {} };
|
|
32552
|
+
if (own2 !== null) view.allowed[own2] = asAllowed(mine.allowed);
|
|
32152
32553
|
const failed = [];
|
|
32153
32554
|
for (const role of roles) {
|
|
32154
|
-
if (role ===
|
|
32555
|
+
if (role === own2) continue;
|
|
32155
32556
|
try {
|
|
32156
32557
|
view.allowed[role] = asAllowed((await readAccess(client, appId, role)).allowed);
|
|
32157
32558
|
} catch {
|
|
@@ -32358,47 +32759,47 @@ __export(levers_exports, {
|
|
|
32358
32759
|
unboundControls: () => unboundControls2,
|
|
32359
32760
|
unreadParams: () => unreadParams2
|
|
32360
32761
|
});
|
|
32361
|
-
var
|
|
32362
|
-
var
|
|
32762
|
+
var isPlainObject20 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
32763
|
+
var named12 = (value) => typeof value === "string" && value !== "";
|
|
32363
32764
|
function both4(items) {
|
|
32364
32765
|
if (items.length <= 1) return items.join("");
|
|
32365
32766
|
return `${items.slice(0, -1).join(", ")} and ${items.at(-1)}`;
|
|
32366
32767
|
}
|
|
32367
32768
|
function paramPages2(contract) {
|
|
32368
32769
|
return (Array.isArray(contract?.pages) ? contract.pages : []).filter(
|
|
32369
|
-
(page) => page?.owned !== true &&
|
|
32770
|
+
(page) => page?.owned !== true && named12(page?.path) && Array.isArray(page.params) && page.params.length > 0
|
|
32370
32771
|
);
|
|
32371
32772
|
}
|
|
32372
32773
|
function namesOf2(page) {
|
|
32373
32774
|
const out = [];
|
|
32374
|
-
for (const param of page.params) if (
|
|
32775
|
+
for (const param of page.params) if (isPlainObject20(param) && named12(param.name) && !out.includes(param.name)) out.push(param.name);
|
|
32375
32776
|
return out;
|
|
32376
32777
|
}
|
|
32377
|
-
function
|
|
32378
|
-
const elements =
|
|
32778
|
+
function elementsOn3(contract, path3) {
|
|
32779
|
+
const elements = isPlainObject20(contract?.elements) ? contract.elements : {};
|
|
32379
32780
|
return Object.keys(elements).sort().filter((uid) => elements[uid]?.page === path3).map((uid) => [uid, elements[uid]]);
|
|
32380
32781
|
}
|
|
32381
32782
|
function paramModeOf2(element) {
|
|
32382
|
-
const props =
|
|
32383
|
-
if (element?.type === "control") return
|
|
32783
|
+
const props = isPlainObject20(element?.props) ? element.props : {};
|
|
32784
|
+
if (element?.type === "control") return named12(props.param) ? props.param : named12(element.binding) ? element.binding : null;
|
|
32384
32785
|
if (element?.type === "field" || element?.type === "action") return null;
|
|
32385
|
-
if (
|
|
32386
|
-
const typed =
|
|
32387
|
-
return
|
|
32786
|
+
if (named12(props.param) && element?.type !== "custom") return props.param;
|
|
32787
|
+
const typed = isPlainObject20(props.props) ? props.props : {};
|
|
32788
|
+
return named12(typed.param) ? typed.param : null;
|
|
32388
32789
|
}
|
|
32389
32790
|
function paramsWrittenBy2(element) {
|
|
32390
32791
|
const out = [];
|
|
32391
32792
|
const one = paramModeOf2(element);
|
|
32392
32793
|
if (one !== null) out.push(one);
|
|
32393
32794
|
if (element?.type === "custom") return out;
|
|
32394
|
-
const typed =
|
|
32395
|
-
if (
|
|
32795
|
+
const typed = isPlainObject20(element?.props?.props) ? element.props.props : {};
|
|
32796
|
+
if (named12(typed.param_to)) out.push(typed.param_to);
|
|
32396
32797
|
for (const filter of Array.isArray(typed.filters) ? typed.filters : []) {
|
|
32397
|
-
if (!
|
|
32398
|
-
if (
|
|
32399
|
-
if (
|
|
32400
|
-
if (
|
|
32401
|
-
if (
|
|
32798
|
+
if (!isPlainObject20(filter)) continue;
|
|
32799
|
+
if (named12(filter.param)) out.push(filter.param);
|
|
32800
|
+
if (named12(filter.param_to)) out.push(filter.param_to);
|
|
32801
|
+
if (named12(filter.param_min)) out.push(filter.param_min);
|
|
32802
|
+
if (named12(filter.param_max)) out.push(filter.param_max);
|
|
32402
32803
|
}
|
|
32403
32804
|
return out;
|
|
32404
32805
|
}
|
|
@@ -32407,13 +32808,13 @@ function paramWriters2(contract, { moduleSource = () => void 0 } = {}) {
|
|
|
32407
32808
|
for (const page of paramPages2(contract)) {
|
|
32408
32809
|
const written = /* @__PURE__ */ new Set();
|
|
32409
32810
|
let unknown = false;
|
|
32410
|
-
for (const [, element] of
|
|
32811
|
+
for (const [, element] of elementsOn3(contract, page.path)) {
|
|
32411
32812
|
for (const name of paramsWrittenBy2(element)) written.add(name);
|
|
32412
32813
|
if (element?.type !== "custom") continue;
|
|
32413
|
-
const props =
|
|
32414
|
-
const produces =
|
|
32415
|
-
for (const keys2 of Object.values(produces)) for (const key of Array.isArray(keys2) ? keys2 : []) if (
|
|
32416
|
-
if (!
|
|
32814
|
+
const props = isPlainObject20(element.props) ? element.props : {};
|
|
32815
|
+
const produces = isPlainObject20(props.declares?.produces) ? props.declares.produces : {};
|
|
32816
|
+
for (const keys2 of Object.values(produces)) for (const key of Array.isArray(keys2) ? keys2 : []) if (named12(key)) written.add(key);
|
|
32817
|
+
if (!named12(props.module)) continue;
|
|
32417
32818
|
const source = moduleSource(props.module);
|
|
32418
32819
|
if (typeof source !== "string") {
|
|
32419
32820
|
unknown = true;
|
|
@@ -32426,8 +32827,8 @@ function paramWriters2(contract, { moduleSource = () => void 0 } = {}) {
|
|
|
32426
32827
|
}
|
|
32427
32828
|
if (contract?.app?.shell?.nav === "rail") {
|
|
32428
32829
|
for (const entry of Array.isArray(contract.app.shell.rail?.entries) ? contract.app.shell.rail.entries : []) {
|
|
32429
|
-
if (
|
|
32430
|
-
for (const name of Object.keys(entry.param)) if (
|
|
32830
|
+
if (isPlainObject20(entry?.param)) {
|
|
32831
|
+
for (const name of Object.keys(entry.param)) if (named12(name)) written.add(name);
|
|
32431
32832
|
}
|
|
32432
32833
|
}
|
|
32433
32834
|
}
|
|
@@ -32444,7 +32845,7 @@ function unboundControls2(contract, files = {}) {
|
|
|
32444
32845
|
for (const name of namesOf2(page)) {
|
|
32445
32846
|
if (written.has(name)) continue;
|
|
32446
32847
|
const param = page.params.find((one) => one?.name === name) ?? {};
|
|
32447
|
-
const kind =
|
|
32848
|
+
const kind = named12(param.kind) ? param.kind : null;
|
|
32448
32849
|
const opens3 = param.default === void 0 ? "no value at all" : `its default (${JSON.stringify(param.default)})`;
|
|
32449
32850
|
const problem5 = `${page.path} declares param "${name}"${kind === null ? "" : ` (${kind})`}, and no control on the page writes it \u2014 no part in param mode, no custom that calls ui.setParam("${name}", \u2026) or declares it in declares.produces \u2014 so the page only ever shows ${opens3}.`;
|
|
32450
32851
|
const fix = `Give it a control \u2014 a part in param mode (props.props.param "${name}": a slider, a number_input, a select, a date_picker or a toggle), or a custom whose module calls ui.setParam("${name}", value) \u2014 or drop the param.`;
|
|
@@ -32459,16 +32860,16 @@ function honoursOf2(sources, name) {
|
|
|
32459
32860
|
const queue = [name];
|
|
32460
32861
|
while (queue.length > 0) {
|
|
32461
32862
|
const one = queue.shift();
|
|
32462
|
-
if (seen.has(one) || !
|
|
32863
|
+
if (seen.has(one) || !isPlainObject20(sources[one])) continue;
|
|
32463
32864
|
seen.add(one);
|
|
32464
|
-
for (const honored of Array.isArray(sources[one].honors) ? sources[one].honors : []) if (
|
|
32865
|
+
for (const honored of Array.isArray(sources[one].honors) ? sources[one].honors : []) if (named12(honored) && !out.includes(honored)) out.push(honored);
|
|
32465
32866
|
if (Array.isArray(sources[one].from)) queue.push(...sources[one].from);
|
|
32466
32867
|
}
|
|
32467
32868
|
return out;
|
|
32468
32869
|
}
|
|
32469
32870
|
function unreadParams2(contract, { moduleSource = () => void 0, handlerSource = () => void 0 } = {}) {
|
|
32470
|
-
const sources =
|
|
32471
|
-
const handlers =
|
|
32871
|
+
const sources = isPlainObject20(contract?.data?.sources) ? contract.data.sources : {};
|
|
32872
|
+
const handlers = isPlainObject20(contract?.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
32472
32873
|
const out = [];
|
|
32473
32874
|
for (const page of paramPages2(contract)) {
|
|
32474
32875
|
const read = /* @__PURE__ */ new Set();
|
|
@@ -32476,16 +32877,16 @@ function unreadParams2(contract, { moduleSource = () => void 0, handlerSource =
|
|
|
32476
32877
|
const modules = [];
|
|
32477
32878
|
const custom = [];
|
|
32478
32879
|
let unknown = false;
|
|
32479
|
-
for (const [, element] of
|
|
32480
|
-
const props =
|
|
32481
|
-
const reads = [props.source, ...Array.isArray(props.declares?.reads) ? props.declares.reads : []].filter(
|
|
32880
|
+
for (const [, element] of elementsOn3(contract, page.path)) {
|
|
32881
|
+
const props = isPlainObject20(element?.props) ? element.props : {};
|
|
32882
|
+
const reads = [props.source, ...Array.isArray(props.declares?.reads) ? props.declares.reads : []].filter(named12);
|
|
32482
32883
|
for (const one of reads) {
|
|
32483
32884
|
if (Object.prototype.hasOwnProperty.call(sources, one)) {
|
|
32484
32885
|
if (!named_.includes(one)) named_.push(one);
|
|
32485
32886
|
for (const honored of honoursOf2(sources, one)) read.add(honored);
|
|
32486
32887
|
} else if (handlers[one]?.role === "custom" && !custom.includes(one)) custom.push(one);
|
|
32487
32888
|
}
|
|
32488
|
-
if (element?.type === "custom" &&
|
|
32889
|
+
if (element?.type === "custom" && named12(props.module)) {
|
|
32489
32890
|
const text3 = moduleSource(props.module);
|
|
32490
32891
|
if (typeof text3 === "string") modules.push(text3);
|
|
32491
32892
|
else unknown = true;
|
|
@@ -33116,8 +33517,8 @@ function partFindings(runs) {
|
|
|
33116
33517
|
if (fresh.length > 0) {
|
|
33117
33518
|
const seen = unfilled.get(where2);
|
|
33118
33519
|
const blank4 = [.../* @__PURE__ */ new Set([...seen?.blank ?? [], ...fresh])];
|
|
33119
|
-
const
|
|
33120
|
-
const answerer = typeof
|
|
33520
|
+
const named13 = component.props.source;
|
|
33521
|
+
const answerer = typeof named13 === "string" && named13 !== "" ? `"${named13}"` : "its source";
|
|
33121
33522
|
const finding = {
|
|
33122
33523
|
code: "callout-unfilled",
|
|
33123
33524
|
where: where2,
|
|
@@ -33455,16 +33856,16 @@ function partCalls3(element) {
|
|
|
33455
33856
|
if (entry === void 0) return [];
|
|
33456
33857
|
const inner = element.props?.["props"];
|
|
33457
33858
|
const typed = typeof inner === "object" && inner !== null && !Array.isArray(inner) ? inner : {};
|
|
33458
|
-
const
|
|
33859
|
+
const named13 = (value) => typeof value === "string" && value !== "";
|
|
33459
33860
|
const out = [];
|
|
33460
33861
|
for (const prop of Object.keys(entry.events ?? {})) {
|
|
33461
33862
|
const value = typed[prop];
|
|
33462
|
-
if (prop !== "on_row_action" &&
|
|
33863
|
+
if (prop !== "on_row_action" && named13(value)) out.push(value);
|
|
33463
33864
|
}
|
|
33464
33865
|
const actions = typed["row_actions"];
|
|
33465
33866
|
for (const action of Array.isArray(actions) ? actions : []) {
|
|
33466
33867
|
const call = typeof action === "object" && action !== null ? action.call : void 0;
|
|
33467
|
-
if (
|
|
33868
|
+
if (named13(call)) out.push(call);
|
|
33468
33869
|
}
|
|
33469
33870
|
return out;
|
|
33470
33871
|
}
|
|
@@ -33564,14 +33965,14 @@ function roleFindings(contract, held) {
|
|
|
33564
33965
|
}
|
|
33565
33966
|
}
|
|
33566
33967
|
if (gatedElsewhere === 0 || mine > 0) return [];
|
|
33567
|
-
const
|
|
33968
|
+
const own2 = JSON.stringify(held.own);
|
|
33568
33969
|
const whose = others.join(", ");
|
|
33569
33970
|
return [
|
|
33570
33971
|
{
|
|
33571
33972
|
code: "role-sees-nothing",
|
|
33572
33973
|
where: "backend.roles",
|
|
33573
|
-
message: `you hold ${
|
|
33574
|
-
fix: `Declare backend.roles least senior first, the default first and the role these screens are for last, so the creator holds it; if ${
|
|
33974
|
+
message: `you hold ${own2} \u2014 the app's creator holds the last role declared \u2014 and every one of the ${String(gatedElsewhere)} role-gated components is for ${whose}, so opening the app you see none of them (only the ${String(open2)} ungated)`,
|
|
33975
|
+
fix: `Declare backend.roles least senior first, the default first and the role these screens are for last, so the creator holds it; if ${own2} really is the most senior, gate those components to it too, or add ${whose} to its sees and to the requiresRole of their handlers.`,
|
|
33575
33976
|
see: SEE.roles
|
|
33576
33977
|
}
|
|
33577
33978
|
];
|
|
@@ -33940,13 +34341,13 @@ function componentRoles(html) {
|
|
|
33940
34341
|
function rendersFor(gated, view, as, pageFor = []) {
|
|
33941
34342
|
if (as !== void 0) return [as];
|
|
33942
34343
|
if (view === void 0 || view.own === null) return [null];
|
|
33943
|
-
const
|
|
33944
|
-
const sees = view.allowed[
|
|
34344
|
+
const own2 = view.own;
|
|
34345
|
+
const sees = view.allowed[own2] ?? {};
|
|
33945
34346
|
const extra = gated.filter(
|
|
33946
|
-
(role) => role !==
|
|
34347
|
+
(role) => role !== own2 && sees[role] !== true && view.roles.includes(role)
|
|
33947
34348
|
);
|
|
33948
34349
|
const declared = pageFor.filter((role) => view.roles.includes(role));
|
|
33949
|
-
if (declared.length > 0 && !declared.some((role) => role ===
|
|
34350
|
+
if (declared.length > 0 && !declared.some((role) => role === own2 || sees[role] === true)) {
|
|
33950
34351
|
const first = declared[0];
|
|
33951
34352
|
return [first, ...extra.filter((role) => role !== first && declared.includes(role))];
|
|
33952
34353
|
}
|
|
@@ -33989,9 +34390,9 @@ function wrapNote(message, width = 92) {
|
|
|
33989
34390
|
}
|
|
33990
34391
|
function heldRole(view) {
|
|
33991
34392
|
if (view.own === null) return { own: null, sees: [] };
|
|
33992
|
-
const
|
|
33993
|
-
const allowed = view.allowed[
|
|
33994
|
-
return { own, sees: view.roles.filter((role) => role !==
|
|
34393
|
+
const own2 = view.own;
|
|
34394
|
+
const allowed = view.allowed[own2] ?? {};
|
|
34395
|
+
return { own: own2, sees: view.roles.filter((role) => role !== own2 && allowed[role] === true) };
|
|
33995
34396
|
}
|
|
33996
34397
|
function rolesLine(view, as) {
|
|
33997
34398
|
if (as !== void 0) return `role ${as} (--as), of ${view.roles.join(", ")}
|
|
@@ -35371,8 +35772,8 @@ function registerScaffoldCommand(parent) {
|
|
|
35371
35772
|
printJson({ id, name, dir, kind, table, record: record2, files: [CONTRACT_FILE], overwritten });
|
|
35372
35773
|
return;
|
|
35373
35774
|
}
|
|
35374
|
-
const
|
|
35375
|
-
const server =
|
|
35775
|
+
const named13 = (opts.server ?? "").trim();
|
|
35776
|
+
const server = named13 === "" ? "" : ` -s ${named13}`;
|
|
35376
35777
|
process.stdout.write(
|
|
35377
35778
|
`${id} ${name} (${kind})
|
|
35378
35779
|
${dir}
|