@datarails-ai/dr-appspace-cli 0.9.3 → 0.9.5
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 +110 -14
- 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.9.
|
|
16
|
+
version: "0.9.5",
|
|
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-09-
|
|
60
|
+
var BUILD_TIME = "2026-09-30T11:19:50.440Z";
|
|
61
61
|
function formatBuildTime(iso) {
|
|
62
62
|
if (iso === "") return "";
|
|
63
63
|
const d = new Date(iso);
|
|
@@ -4009,6 +4009,7 @@ var FILTER_ATTR = "data-dr-filter";
|
|
|
4009
4009
|
var ARGS_ATTR = "data-dr-args";
|
|
4010
4010
|
var PROPS_ATTR = "data-dr-props";
|
|
4011
4011
|
var DECLARES_ATTR = "data-dr-declares";
|
|
4012
|
+
var WHEN_ATTR = "data-dr-when";
|
|
4012
4013
|
var COMPONENT_TYPES2 = COMPONENT_TYPES;
|
|
4013
4014
|
var COMPONENT_TYPE_SET = new Set(COMPONENT_TYPES2);
|
|
4014
4015
|
var MODULE_RE = /^components\/[a-z][a-z0-9_-]*\.js$/;
|
|
@@ -5236,6 +5237,7 @@ function propsOf(node, text3, tree) {
|
|
|
5236
5237
|
if (ARGS_ATTR in node.attrs) props.args = parseArgs(node.attrs[ARGS_ATTR]);
|
|
5237
5238
|
if (PROPS_ATTR in node.attrs) props.props = parseProps(node.attrs[PROPS_ATTR]);
|
|
5238
5239
|
if (DECLARES_ATTR in node.attrs) props.declares = parseDeclares(node.attrs[DECLARES_ATTR]);
|
|
5240
|
+
if (WHEN_ATTR in node.attrs) props.when = parseArgs(node.attrs[WHEN_ATTR]);
|
|
5239
5241
|
const inputType = inputTypeOf(node);
|
|
5240
5242
|
if (inputType !== null) props.input_type = inputType;
|
|
5241
5243
|
if ("required" in node.attrs) props.required = true;
|
|
@@ -7702,6 +7704,8 @@ var COMPONENT_PROPS3 = /* @__PURE__ */ new Set([
|
|
|
7702
7704
|
"props",
|
|
7703
7705
|
// What a custom's module declares it does (`data-dr-declares`, DC-5), one JSON object.
|
|
7704
7706
|
"declares",
|
|
7707
|
+
// A row action's states (`data-dr-when`, dr-app-runtime 1.6.3), one JSON object.
|
|
7708
|
+
"when",
|
|
7705
7709
|
"input_type",
|
|
7706
7710
|
"required",
|
|
7707
7711
|
"options"
|
|
@@ -12761,7 +12765,7 @@ var HANDLER_KEYS = [
|
|
|
12761
12765
|
"measures",
|
|
12762
12766
|
"requiresRole"
|
|
12763
12767
|
];
|
|
12764
|
-
var ATTR_ORDER = ["component", "id", "entity", "action", "source", "field", "filter", "param", "measure", "kind", "x", "y", "series", "format", "module", "args", "props", "declares", "role", "label", "tone"];
|
|
12768
|
+
var ATTR_ORDER = ["component", "id", "entity", "action", "source", "field", "filter", "param", "measure", "kind", "x", "y", "series", "format", "module", "args", "props", "declares", "when", "role", "label", "tone"];
|
|
12765
12769
|
var ENTITY_WRITERS2 = /* @__PURE__ */ new Set(["form", "list", "queue", "detail", "filter"]);
|
|
12766
12770
|
var DECIMAL_TYPES = /* @__PURE__ */ new Set(["decimal", "numeric", "money"]);
|
|
12767
12771
|
var NUMBER_TYPES = /* @__PURE__ */ new Set(["decimal", "numeric", "money", "int", "integer", "bigint", "float"]);
|
|
@@ -12798,28 +12802,35 @@ function roleListOf(element) {
|
|
|
12798
12802
|
const role = isPlainObject7(element?.props) ? element.props.role : void 0;
|
|
12799
12803
|
return Array.isArray(role) ? role : null;
|
|
12800
12804
|
}
|
|
12801
|
-
function
|
|
12805
|
+
function roleListCoversHandler(element, handlers) {
|
|
12806
|
+
const listed = roleListOf(element);
|
|
12807
|
+
if (listed === null || element?.type !== "action" || !listed.every(named4)) return false;
|
|
12808
|
+
const gate = handlers?.[element.props?.action]?.requiresRole;
|
|
12809
|
+
return Array.isArray(gate) && gate.length > 0 && gate.every((role) => listed.includes(role));
|
|
12810
|
+
}
|
|
12811
|
+
function foldRoleLists(elements, handlers = {}) {
|
|
12802
12812
|
const folding = Object.keys(elements).filter((uid) => {
|
|
12803
12813
|
const listed = roleListOf(elements[uid]);
|
|
12804
|
-
return listed !== null && listed.length === 1 && named4(listed[0]);
|
|
12814
|
+
return listed !== null && (listed.length === 1 && named4(listed[0]) || roleListCoversHandler(elements[uid], handlers));
|
|
12805
12815
|
});
|
|
12806
12816
|
if (folding.length === 0) return elements;
|
|
12807
12817
|
const out = { ...elements };
|
|
12808
12818
|
for (const uid of folding) {
|
|
12809
12819
|
const element = out[uid];
|
|
12810
|
-
|
|
12820
|
+
const { role, ...rest } = element.props;
|
|
12821
|
+
out[uid] = { ...element, props: role.length === 1 && named4(role[0]) ? { ...element.props, role: role[0] } : rest };
|
|
12811
12822
|
}
|
|
12812
12823
|
return out;
|
|
12813
12824
|
}
|
|
12814
|
-
function roleListProblems(elements) {
|
|
12825
|
+
function roleListProblems(elements, handlers = {}) {
|
|
12815
12826
|
const problems = [];
|
|
12816
12827
|
for (const uid of Object.keys(elements).sort(natural)) {
|
|
12817
12828
|
const listed = roleListOf(elements[uid]);
|
|
12818
|
-
if (listed === null || listed.length === 1 && named4(listed[0])) continue;
|
|
12829
|
+
if (listed === null || listed.length === 1 && named4(listed[0]) || roleListCoversHandler(elements[uid], handlers)) continue;
|
|
12819
12830
|
problems.push({
|
|
12820
12831
|
code: "app_model_mismatch",
|
|
12821
12832
|
pointer: `/elements/${escapePointer3(uid)}/props/role`,
|
|
12822
|
-
message: `"${uid}" states props.role ${JSON.stringify(listed)}, and a role gate names one role: it is written as data-dr-role and the page shows the element only to the caller who holds that role \u2014 write the one role as a string, or leave props.role off and let the handler's requiresRole name every role that may call it`
|
|
12833
|
+
message: `"${uid}" states props.role ${JSON.stringify(listed)}, and a role gate names one role: it is written as data-dr-role and the page shows the element only to the caller who holds that role \u2014 write the one role as a string, or leave props.role off and let the handler's requiresRole name every role that may call it (a button is drawn only for a caller the handler lets through)`
|
|
12823
12834
|
});
|
|
12824
12835
|
}
|
|
12825
12836
|
return problems;
|
|
@@ -13458,8 +13469,9 @@ function compileApp(contract, { kit = null, runtime = RUNTIME, extra = {} } = {}
|
|
|
13458
13469
|
}
|
|
13459
13470
|
problems.push(...appIdProblems(app));
|
|
13460
13471
|
const typed = isPlainObject7(contract.elements) ? contract.elements : {};
|
|
13461
|
-
|
|
13462
|
-
|
|
13472
|
+
const gates = isPlainObject7(contract.data?.app?.handlers) ? contract.data.app.handlers : {};
|
|
13473
|
+
problems.push(...statedIdProblems(typed), ...roleListProblems(typed, gates));
|
|
13474
|
+
const declared = foldDialogTriggers(foldNavRegions(foldHeaderRoles(foldRoleLists(foldStatedIds(typed), gates))));
|
|
13463
13475
|
problems.push(...headerPropsProblems(declared));
|
|
13464
13476
|
const fromBundle = Object.keys(declared).filter((uid) => declared[uid]?.source === "bundle").sort(natural);
|
|
13465
13477
|
if (fromBundle.length > 0) {
|
|
@@ -13549,7 +13561,7 @@ function compileApp(contract, { kit = null, runtime = RUNTIME, extra = {} } = {}
|
|
|
13549
13561
|
}
|
|
13550
13562
|
const tree = treeOf(declared);
|
|
13551
13563
|
problems.push(...tree.problems);
|
|
13552
|
-
if (tree.problems.length === 0) problems.push(...byProblems(declared, tree, handlers, model), ...pickerProblems(declared, tree, handlers, model));
|
|
13564
|
+
if (tree.problems.length === 0) problems.push(...byProblems(declared, tree, handlers, model), ...pickerProblems(declared, tree, handlers, model), ...whenProblems(declared, tree, model));
|
|
13553
13565
|
problems.push(...computedProblems(declared, tree, handlers, model));
|
|
13554
13566
|
if (tree.problems.length === 0) problems.push(...modelProblems(declared, tree, handlers, model, rolesOf2(contract.backend?.roles, model), sources));
|
|
13555
13567
|
if (tree.problems.length === 0) problems.push(...historyProblems(declared, tree, handlers, model));
|
|
@@ -14808,6 +14820,45 @@ function computedProblems(elements, tree, handlers, model) {
|
|
|
14808
14820
|
}
|
|
14809
14821
|
return problems;
|
|
14810
14822
|
}
|
|
14823
|
+
function whenProblems(elements, tree, model) {
|
|
14824
|
+
const problems = [];
|
|
14825
|
+
for (const uid of Object.keys(elements).sort(natural)) {
|
|
14826
|
+
const element = elements[uid] ?? {};
|
|
14827
|
+
const when = element.props?.when;
|
|
14828
|
+
if (when === void 0 || when === null) continue;
|
|
14829
|
+
const at = `/elements/${escapePointer3(uid)}/props/when`;
|
|
14830
|
+
const push = (message, pointer2 = at) => problems.push({ code: "app_model_mismatch", pointer: pointer2, message });
|
|
14831
|
+
const holder = elements[parentOf(tree, uid)] ?? null;
|
|
14832
|
+
if (element.type !== "action" || !["list", "queue"].includes(holder?.type)) {
|
|
14833
|
+
push(`"${uid}" states when, and when says which rows a row action shows on: only an action inside a list or a queue has a row \u2014 drop when`);
|
|
14834
|
+
continue;
|
|
14835
|
+
}
|
|
14836
|
+
if (!isPlainObject7(when) || Object.keys(when).length === 0) {
|
|
14837
|
+
push(`"${uid}" states when as ${JSON.stringify(when)}; when is an object of column to the list of values the row may hold \u2014 {"status": ["open", "in_progress"]}`);
|
|
14838
|
+
continue;
|
|
14839
|
+
}
|
|
14840
|
+
const entity = entityOf(uid, elements, tree);
|
|
14841
|
+
for (const column of Object.keys(when).sort()) {
|
|
14842
|
+
const states = when[column];
|
|
14843
|
+
const pointer2 = `${at}/${escapePointer3(column)}`;
|
|
14844
|
+
if (!Array.isArray(states) || states.length === 0 || !states.every(named4)) {
|
|
14845
|
+
push(`"${uid}" states when.${column} as ${JSON.stringify(states)}; each column takes a non-empty list of the values it may hold, as strings \u2014 even one: ["open"]`, pointer2);
|
|
14846
|
+
continue;
|
|
14847
|
+
}
|
|
14848
|
+
if (entity === null || !Object.prototype.hasOwnProperty.call(model.tables, entity)) continue;
|
|
14849
|
+
if (!hasColumn(model, entity, column)) {
|
|
14850
|
+
push(`"${uid}" shows only where "${column}" holds ${JSON.stringify(states)}, and "${entity}" has no column "${column}"`, pointer2);
|
|
14851
|
+
continue;
|
|
14852
|
+
}
|
|
14853
|
+
const values = model.tables[entity].columns[column]?.enum ?? null;
|
|
14854
|
+
const stray = values === null ? [] : states.filter((state) => !values.includes(state));
|
|
14855
|
+
if (stray.length > 0) {
|
|
14856
|
+
push(`"${uid}" shows only where "${column}" holds ${JSON.stringify(stray)}, which "${entity}.${column}" cannot hold (${values.join(", ")})`, pointer2);
|
|
14857
|
+
}
|
|
14858
|
+
}
|
|
14859
|
+
}
|
|
14860
|
+
return problems;
|
|
14861
|
+
}
|
|
14811
14862
|
function pickerProblems(elements, tree, handlers, model) {
|
|
14812
14863
|
const problems = [];
|
|
14813
14864
|
const push = (pointer2, message) => problems.push({ code: "app_model_mismatch", pointer: pointer2, message });
|
|
@@ -16365,6 +16416,11 @@ function attrsOf(element) {
|
|
|
16365
16416
|
if (written !== "") out.push(["declares", written]);
|
|
16366
16417
|
continue;
|
|
16367
16418
|
}
|
|
16419
|
+
if (key === "when") {
|
|
16420
|
+
const written = writeArgs(value);
|
|
16421
|
+
if (written !== "") out.push(["when", written]);
|
|
16422
|
+
continue;
|
|
16423
|
+
}
|
|
16368
16424
|
if (typeof value !== "string") continue;
|
|
16369
16425
|
out.push([key, value]);
|
|
16370
16426
|
}
|
|
@@ -17643,6 +17699,7 @@ var FILTER_ATTR2 = "data-dr-filter";
|
|
|
17643
17699
|
var ARGS_ATTR2 = "data-dr-args";
|
|
17644
17700
|
var PROPS_ATTR2 = "data-dr-props";
|
|
17645
17701
|
var DECLARES_ATTR2 = "data-dr-declares";
|
|
17702
|
+
var WHEN_ATTR2 = "data-dr-when";
|
|
17646
17703
|
var COMPONENT_TYPES5 = COMPONENT_TYPES4;
|
|
17647
17704
|
var COMPONENT_TYPE_SET2 = new Set(COMPONENT_TYPES5);
|
|
17648
17705
|
var MODULE_RE2 = /^components\/[a-z][a-z0-9_-]*\.js$/;
|
|
@@ -18822,6 +18879,7 @@ function propsOf2(node, text3, tree) {
|
|
|
18822
18879
|
if (ARGS_ATTR2 in node.attrs) props.args = parseArgs2(node.attrs[ARGS_ATTR2]);
|
|
18823
18880
|
if (PROPS_ATTR2 in node.attrs) props.props = parseProps2(node.attrs[PROPS_ATTR2]);
|
|
18824
18881
|
if (DECLARES_ATTR2 in node.attrs) props.declares = parseDeclares2(node.attrs[DECLARES_ATTR2]);
|
|
18882
|
+
if (WHEN_ATTR2 in node.attrs) props.when = parseArgs2(node.attrs[WHEN_ATTR2]);
|
|
18825
18883
|
const inputType = inputTypeOf2(node);
|
|
18826
18884
|
if (inputType !== null) props.input_type = inputType;
|
|
18827
18885
|
if ("required" in node.attrs) props.required = true;
|
|
@@ -20423,11 +20481,15 @@ function checkComponents(app, contract, contractError) {
|
|
|
20423
20481
|
if (typeof role !== "string" || role === "" || roles.includes(role)) continue;
|
|
20424
20482
|
violations.push({ rule: "A3.8", where: uid, message: `data-dr-role="${role}" names no role manifest.json declares (${roles.join(", ")})` });
|
|
20425
20483
|
}
|
|
20484
|
+
const mounted = new Set(
|
|
20485
|
+
app.ui.filter((file) => [...file.text.matchAll(SCRIPT_SRC)].some((match) => RUNTIME_JS.test(match[1]))).map((file) => file.path)
|
|
20486
|
+
);
|
|
20426
20487
|
let flat = false;
|
|
20427
20488
|
for (const uid of uids) {
|
|
20428
20489
|
const element = elements[uid];
|
|
20429
20490
|
const required = actions[element.props?.action]?.requires_role ?? [];
|
|
20430
20491
|
if (required.length === 0) continue;
|
|
20492
|
+
if (element.type === "action" && mounted.has(element.page)) continue;
|
|
20431
20493
|
if (marked(elements, uid, required, pageRoles3)) continue;
|
|
20432
20494
|
if (element.source === "bundle") {
|
|
20433
20495
|
flat = true;
|
|
@@ -21162,7 +21224,8 @@ function rolesOf5(manifest) {
|
|
|
21162
21224
|
}
|
|
21163
21225
|
|
|
21164
21226
|
// ../flows/appspace-builder/kit/checks/rules/ux.mjs
|
|
21165
|
-
var RULES4 = ["A3.23", "A3.24", "A3.25"];
|
|
21227
|
+
var RULES4 = ["A3.23", "A3.24", "A3.25", "A3.26", "A3.27"];
|
|
21228
|
+
var STATE_COLUMNS = ["status", "state"];
|
|
21166
21229
|
var isPlainObject13 = (v) => Boolean(v) && typeof v === "object" && !Array.isArray(v);
|
|
21167
21230
|
var said = (v) => typeof v === "string" && v.trim() !== "" ? v : null;
|
|
21168
21231
|
var OPENERS3 = /* @__PURE__ */ new Set(["dialog", "list", "queue", "detail"]);
|
|
@@ -21249,6 +21312,39 @@ function checkUx(app, contract, contractError) {
|
|
|
21249
21312
|
message: `dialog "${nameOf5(uid)}" on ${el.page} states no props.trigger, no label and no title, so its button has no words (dialog-no-trigger); state props.trigger \u2014 what the button says ("New ticket")`
|
|
21250
21313
|
});
|
|
21251
21314
|
}
|
|
21315
|
+
for (const uid of uids) {
|
|
21316
|
+
const el = elements[uid];
|
|
21317
|
+
if (!ROWS.has(el?.type) || el.type === "data_grid") continue;
|
|
21318
|
+
const table = tableOf2(el);
|
|
21319
|
+
const columns = isPlainObject13(tables[table]?.columns) ? tables[table].columns : {};
|
|
21320
|
+
const state = STATE_COLUMNS.find((c) => isPlainObject13(columns[c]) && Array.isArray(columns[c].enum));
|
|
21321
|
+
const seen = /* @__PURE__ */ new Map();
|
|
21322
|
+
for (const kid of kidsOf(uid)) {
|
|
21323
|
+
const action = elements[kid];
|
|
21324
|
+
if (action?.type !== "action" || !said(action.props?.action)) continue;
|
|
21325
|
+
const name = action.props.action;
|
|
21326
|
+
const role = handlers[name]?.role ?? "custom";
|
|
21327
|
+
const label = said(action.props?.label) ?? name;
|
|
21328
|
+
const twin = seen.get(`${name}
|
|
21329
|
+
${label}`);
|
|
21330
|
+
if (twin !== void 0) {
|
|
21331
|
+
violations.push({
|
|
21332
|
+
rule: "A3.27",
|
|
21333
|
+
where: kid,
|
|
21334
|
+
message: `${el.type} "${nameOf5(uid)}" on ${el.page} draws "${label}" (${name}) twice \u2014 ${twin} and ${kid} (row-action-twins); keep one action element and drop its props.role: the button shows for every caller the handler's requiresRole lets through`
|
|
21335
|
+
});
|
|
21336
|
+
continue;
|
|
21337
|
+
}
|
|
21338
|
+
seen.set(`${name}
|
|
21339
|
+
${label}`, kid);
|
|
21340
|
+
if (role !== "custom" || state === void 0 || action.props?.when != null) continue;
|
|
21341
|
+
violations.push({
|
|
21342
|
+
rule: "A3.26",
|
|
21343
|
+
where: kid,
|
|
21344
|
+
message: `row action "${label}" (${name}, a custom handler) on ${el.type} "${nameOf5(uid)}" states no when, so it shows on every row whatever its ${state} (row-action-stateless). State the values of ${state} it runs from \u2014 "when": {"${state}": ["${columns[state].enum.slice(0, 2).join('", "')}"]} \u2014 and have the handler refuse any other; a handler that only moves the row from one state to another is a decide with a guard, which hides its button the same way`
|
|
21345
|
+
});
|
|
21346
|
+
}
|
|
21347
|
+
}
|
|
21252
21348
|
return { violations, skipped: [] };
|
|
21253
21349
|
}
|
|
21254
21350
|
function byPageThenRank(a, b) {
|
|
@@ -21828,7 +21924,7 @@ function kitFromDisk2() {
|
|
|
21828
21924
|
}
|
|
21829
21925
|
return kitOnDisk2;
|
|
21830
21926
|
}
|
|
21831
|
-
var ADVISORY = /* @__PURE__ */ new Set(["A1.5", "A1.6", "A2.5", "A2.6", "A3.15", "A3.17", "A3.19", "A3.20", "A3.21", "A3.22", "A3.23", "A3.24", "A3.25"]);
|
|
21927
|
+
var ADVISORY = /* @__PURE__ */ new Set(["A1.5", "A1.6", "A2.5", "A2.6", "A3.15", "A3.17", "A3.19", "A3.20", "A3.21", "A3.22", "A3.23", "A3.24", "A3.25", "A3.26", "A3.27"]);
|
|
21832
21928
|
function verify2({ app: appRef }, { extractContract: extractContract2 = extract2, kit } = {}) {
|
|
21833
21929
|
const KIT = kit ?? kitFromDisk2();
|
|
21834
21930
|
const app = loadApp2(resolve12(appRef.dir));
|