@datarails-ai/dr-appspace-cli 0.10.0 → 0.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +71 -8
- 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.10.
|
|
16
|
+
version: "0.10.2",
|
|
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-30T13:41:20.692Z";
|
|
61
61
|
function formatBuildTime(iso) {
|
|
62
62
|
if (iso === "") return "";
|
|
63
63
|
const d = new Date(iso);
|
|
@@ -639,6 +639,8 @@ var ordered = [
|
|
|
639
639
|
{ key: "US2", host: "https://us-2.datarails.com", subDomain: "us-2" },
|
|
640
640
|
{ key: "UK", host: "https://ukapp.datarails.com", subDomain: "uk" },
|
|
641
641
|
{ key: "CA", host: "https://caapp.datarails.com", subDomain: "canada" },
|
|
642
|
+
{ key: "EU", host: "https://euapp.datarails.com", subDomain: "eu" },
|
|
643
|
+
{ key: "STUDIO", host: "https://studio.datarails.com", subDomain: "studio" },
|
|
642
644
|
{ key: "DEV", host: "https://dev.datarails.com", subDomain: "dev" },
|
|
643
645
|
{ key: "DEV-1", host: "https://dev-1.datarails.com", subDomain: "dev-1" },
|
|
644
646
|
{ key: "TEST", host: "https://testapp.datarails.com", subDomain: "test" },
|
|
@@ -24703,6 +24705,11 @@ function judgeInteraction(interaction, calls) {
|
|
|
24703
24705
|
`${call.method} ${call.url} \u2014 ${call.failure} (the flow made this call; the UI runtime catches it and paints a banner, so nothing reaches the console)`
|
|
24704
24706
|
);
|
|
24705
24707
|
}
|
|
24708
|
+
const askedForReason = mine.some((call) => call.needsReason);
|
|
24709
|
+
const reasonCovered = askedForReason && mine.some((call) => call.reasonSupplied === true && call.needsReason !== true && call.failure === void 0 && call.error === void 0 && call.status !== void 0 && call.status >= 200 && call.status < 300);
|
|
24710
|
+
if (askedForReason && !reasonCovered) {
|
|
24711
|
+
problems.push(`${interaction.handler} needs a reason \u2014 no successful retry with a reason was observed.`);
|
|
24712
|
+
}
|
|
24706
24713
|
for (const field of interaction.fields) {
|
|
24707
24714
|
if (field.unknownColumn === true) {
|
|
24708
24715
|
problems.push(
|
|
@@ -24719,6 +24726,7 @@ function judgeInteraction(interaction, calls) {
|
|
|
24719
24726
|
calls,
|
|
24720
24727
|
problems
|
|
24721
24728
|
};
|
|
24729
|
+
if (reasonCovered) outcome.reasonCovered = true;
|
|
24722
24730
|
if (interaction.id !== void 0) outcome.id = interaction.id;
|
|
24723
24731
|
if (interaction.entity !== void 0) outcome.entity = interaction.entity;
|
|
24724
24732
|
const attached = interaction.fields.flatMap(
|
|
@@ -25162,6 +25170,7 @@ function movedBetween(before, after, param, echoes = []) {
|
|
|
25162
25170
|
}
|
|
25163
25171
|
|
|
25164
25172
|
// src/commands/appspace/_uiSmoke.ts
|
|
25173
|
+
var SMOKE_REASON = "App Space smoke test: exercising the required reason flow.";
|
|
25165
25174
|
var NULL_TEXT_CONTEXT = 80;
|
|
25166
25175
|
var NULL_TEXT_LIMIT = 10;
|
|
25167
25176
|
var NULL_LITERAL = /(?<![A-Za-z0-9_])(?:(?:null|undefined|NaN)+|\[object Object\])(?![A-Za-z0-9_])/g;
|
|
@@ -25336,7 +25345,13 @@ function answerOf(res) {
|
|
|
25336
25345
|
}
|
|
25337
25346
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
|
|
25338
25347
|
const out = { object: true };
|
|
25339
|
-
const { rows: rows2, error } = parsed;
|
|
25348
|
+
const { rows: rows2, error, needs } = parsed;
|
|
25349
|
+
if (needs && typeof needs === "object" && !Array.isArray(needs)) {
|
|
25350
|
+
const reason = needs.reason;
|
|
25351
|
+
if (reason === true || reason && typeof reason === "object" && !Array.isArray(reason)) {
|
|
25352
|
+
out.needsReason = true;
|
|
25353
|
+
}
|
|
25354
|
+
}
|
|
25340
25355
|
if (Array.isArray(rows2)) {
|
|
25341
25356
|
out.rows = rows2.length;
|
|
25342
25357
|
const { keys: keys2, numeric } = rowKeys(rows2);
|
|
@@ -25866,6 +25881,13 @@ async function runUiSmoke(opts) {
|
|
|
25866
25881
|
let lastActivity = Date.now();
|
|
25867
25882
|
async function record2(req, via, refuse) {
|
|
25868
25883
|
const call = { method: req.method, url: maskToken(req.url), via };
|
|
25884
|
+
if (via === "fetch" && req.body) {
|
|
25885
|
+
try {
|
|
25886
|
+
const payload = JSON.parse(req.body);
|
|
25887
|
+
if (typeof payload?.reason === "string" && payload.reason.trim()) call.reasonSupplied = true;
|
|
25888
|
+
} catch {
|
|
25889
|
+
}
|
|
25890
|
+
}
|
|
25869
25891
|
const auth = req.headers["authorization"];
|
|
25870
25892
|
if (auth !== void 0) call.authorization = summariseAuthorization(auth);
|
|
25871
25893
|
calls.push(call);
|
|
@@ -25893,12 +25915,13 @@ async function runUiSmoke(opts) {
|
|
|
25893
25915
|
call.status = res.status;
|
|
25894
25916
|
if (res.status >= 400) call.failure = `HTTP ${String(res.status)}`;
|
|
25895
25917
|
else if (via === "fetch") {
|
|
25896
|
-
const { rows: rows2, error, keys: keys2, numeric, object } = answerOf(res);
|
|
25918
|
+
const { rows: rows2, error, keys: keys2, numeric, object, needsReason } = answerOf(res);
|
|
25897
25919
|
if (rows2 !== void 0) call.rows = rows2;
|
|
25898
25920
|
if (error !== void 0) call.error = error;
|
|
25899
25921
|
if (keys2 !== void 0) call.keys = keys2;
|
|
25900
25922
|
if (numeric !== void 0) call.numeric = numeric;
|
|
25901
25923
|
if (object !== void 0) call.object = object;
|
|
25924
|
+
if (needsReason) call.needsReason = true;
|
|
25902
25925
|
}
|
|
25903
25926
|
return res;
|
|
25904
25927
|
} catch (err) {
|
|
@@ -25981,9 +26004,10 @@ async function runUiSmoke(opts) {
|
|
|
25981
26004
|
const displaced = process.listeners("unhandledRejection");
|
|
25982
26005
|
process.removeAllListeners("unhandledRejection");
|
|
25983
26006
|
process.on("unhandledRejection", onUnhandled);
|
|
25984
|
-
async function settle(deadline, since = calls.length, graceMs = 0) {
|
|
26007
|
+
async function settle(deadline, since = calls.length, graceMs = 0, drivePrompt) {
|
|
25985
26008
|
const graceUntil = Date.now() + graceMs;
|
|
25986
26009
|
while (Date.now() < deadline) {
|
|
26010
|
+
if (drivePrompt?.()) lastActivity = Date.now();
|
|
25987
26011
|
const parsed = dom.window.document.readyState === "complete";
|
|
25988
26012
|
const started = calls.length > since || inFlight > 0;
|
|
25989
26013
|
if (!started && Date.now() < graceUntil) {
|
|
@@ -26213,7 +26237,24 @@ async function runUiSmoke(opts) {
|
|
|
26213
26237
|
} catch (err) {
|
|
26214
26238
|
notes = [`firing it threw: ${describeRejection(err)}`];
|
|
26215
26239
|
}
|
|
26216
|
-
|
|
26240
|
+
const answeredPrompts = /* @__PURE__ */ new Set();
|
|
26241
|
+
await settle(Math.min(Date.now() + perInteraction, budgetUntil), since, graceMs, () => {
|
|
26242
|
+
const pending = calls.slice(since).filter((call) => callsHandler(call.url, interaction.handler)).at(-1);
|
|
26243
|
+
if (!pending?.needsReason || answeredPrompts.has(pending) || answeredPrompts.size >= 3) return false;
|
|
26244
|
+
const dialog = dom.window.document.querySelector('[data-test="confirmDialog"]');
|
|
26245
|
+
const reason = dialog?.querySelector('textarea[data-test="confirmReason"]');
|
|
26246
|
+
const confirm = dialog?.querySelector('button[data-test="confirmOk"]');
|
|
26247
|
+
if (!(reason instanceof dom.window.HTMLTextAreaElement) || !(confirm instanceof dom.window.HTMLButtonElement)) return false;
|
|
26248
|
+
if (reason.value !== SMOKE_REASON) {
|
|
26249
|
+
Object.getOwnPropertyDescriptor(dom.window.HTMLTextAreaElement.prototype, "value").set.call(reason, SMOKE_REASON);
|
|
26250
|
+
reason.dispatchEvent(new dom.window.Event("input", { bubbles: true }));
|
|
26251
|
+
return true;
|
|
26252
|
+
}
|
|
26253
|
+
if (confirm.disabled) return false;
|
|
26254
|
+
answeredPrompts.add(pending);
|
|
26255
|
+
confirm.click();
|
|
26256
|
+
return true;
|
|
26257
|
+
});
|
|
26217
26258
|
const outcome = judgeInteraction(
|
|
26218
26259
|
interaction,
|
|
26219
26260
|
calls.slice(since).map((call) => ({ ...call }))
|
|
@@ -27870,6 +27911,26 @@ function cardTitleFindings(contract) {
|
|
|
27870
27911
|
see: SEE.labels
|
|
27871
27912
|
}));
|
|
27872
27913
|
}
|
|
27914
|
+
function uxFindings(contract) {
|
|
27915
|
+
if (contract === null || !isAppContract(contract)) return [];
|
|
27916
|
+
const codes = {
|
|
27917
|
+
"A3.23": "create-form-inline",
|
|
27918
|
+
"A3.24": "row-no-detail",
|
|
27919
|
+
"A3.25": "dialog-no-trigger"
|
|
27920
|
+
};
|
|
27921
|
+
const { violations } = checkUx({}, contract, null);
|
|
27922
|
+
return violations.flatMap((found) => {
|
|
27923
|
+
if (!(found.rule in codes)) return [];
|
|
27924
|
+
return [{
|
|
27925
|
+
code: codes[found.rule],
|
|
27926
|
+
where: found.where,
|
|
27927
|
+
message: found.message,
|
|
27928
|
+
fix: "Apply the kit guidance above to the named element in contract.json and recompile",
|
|
27929
|
+
see: `App kit ${found.rule} (checks/rules/ux.mjs)`,
|
|
27930
|
+
severity: "warning"
|
|
27931
|
+
}];
|
|
27932
|
+
});
|
|
27933
|
+
}
|
|
27873
27934
|
function collectFindings(input) {
|
|
27874
27935
|
const levers = input.bundle === void 0 ? [] : leverFindings(input.contract, input.bundle, input.handlerCode ?? {});
|
|
27875
27936
|
const unread = new Set(levers.filter((one) => one.code === "param-unread").map((one) => one.where));
|
|
@@ -27895,7 +27956,8 @@ function collectFindings(input) {
|
|
|
27895
27956
|
...input.contract === null ? [] : auditFindings(input.contract),
|
|
27896
27957
|
...input.declared === null ? [] : unreachedFindings(input.declared, input.cases, input.contract, input.runs),
|
|
27897
27958
|
// Last: said and never counted, so they never push a counted finding down the list.
|
|
27898
|
-
...workflow.filter((one) => one.severity === "warning")
|
|
27959
|
+
...workflow.filter((one) => one.severity === "warning"),
|
|
27960
|
+
...uxFindings(input.contract)
|
|
27899
27961
|
];
|
|
27900
27962
|
}
|
|
27901
27963
|
function formatFindings(findings) {
|
|
@@ -28390,7 +28452,8 @@ function renderInteractions(report) {
|
|
|
28390
28452
|
if (outcome.problems.length === 0) {
|
|
28391
28453
|
const answered = outcome.calls.find((call) => callsHandler(call.url, outcome.handler));
|
|
28392
28454
|
const status = answered?.status === void 0 ? "" : ` (HTTP ${String(answered.status)})`;
|
|
28393
|
-
|
|
28455
|
+
const reason = outcome.reasonCovered ? " \u2014 needs a reason: covered (canned smoke-test reason)" : "";
|
|
28456
|
+
out += ` ok ${head}${status}${reason}
|
|
28394
28457
|
${attached}`;
|
|
28395
28458
|
continue;
|
|
28396
28459
|
}
|