@datarails-ai/dr-appspace-cli 0.9.7 → 0.10.1

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.
Files changed (2) hide show
  1. package/dist/cli.js +69 -8
  2. 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.7",
16
+ version: "0.10.1",
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-30T12:27:06.209Z";
60
+ var BUILD_TIME = "2026-09-30T13:19:16.568Z";
61
61
  function formatBuildTime(iso) {
62
62
  if (iso === "") return "";
63
63
  const d = new Date(iso);
@@ -24703,6 +24703,11 @@ function judgeInteraction(interaction, calls) {
24703
24703
  `${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
24704
  );
24705
24705
  }
24706
+ const askedForReason = mine.some((call) => call.needsReason);
24707
+ 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);
24708
+ if (askedForReason && !reasonCovered) {
24709
+ problems.push(`${interaction.handler} needs a reason \u2014 no successful retry with a reason was observed.`);
24710
+ }
24706
24711
  for (const field of interaction.fields) {
24707
24712
  if (field.unknownColumn === true) {
24708
24713
  problems.push(
@@ -24719,6 +24724,7 @@ function judgeInteraction(interaction, calls) {
24719
24724
  calls,
24720
24725
  problems
24721
24726
  };
24727
+ if (reasonCovered) outcome.reasonCovered = true;
24722
24728
  if (interaction.id !== void 0) outcome.id = interaction.id;
24723
24729
  if (interaction.entity !== void 0) outcome.entity = interaction.entity;
24724
24730
  const attached = interaction.fields.flatMap(
@@ -25162,6 +25168,7 @@ function movedBetween(before, after, param, echoes = []) {
25162
25168
  }
25163
25169
 
25164
25170
  // src/commands/appspace/_uiSmoke.ts
25171
+ var SMOKE_REASON = "App Space smoke test: exercising the required reason flow.";
25165
25172
  var NULL_TEXT_CONTEXT = 80;
25166
25173
  var NULL_TEXT_LIMIT = 10;
25167
25174
  var NULL_LITERAL = /(?<![A-Za-z0-9_])(?:(?:null|undefined|NaN)+|\[object Object\])(?![A-Za-z0-9_])/g;
@@ -25336,7 +25343,13 @@ function answerOf(res) {
25336
25343
  }
25337
25344
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
25338
25345
  const out = { object: true };
25339
- const { rows: rows2, error } = parsed;
25346
+ const { rows: rows2, error, needs } = parsed;
25347
+ if (needs && typeof needs === "object" && !Array.isArray(needs)) {
25348
+ const reason = needs.reason;
25349
+ if (reason === true || reason && typeof reason === "object" && !Array.isArray(reason)) {
25350
+ out.needsReason = true;
25351
+ }
25352
+ }
25340
25353
  if (Array.isArray(rows2)) {
25341
25354
  out.rows = rows2.length;
25342
25355
  const { keys: keys2, numeric } = rowKeys(rows2);
@@ -25866,6 +25879,13 @@ async function runUiSmoke(opts) {
25866
25879
  let lastActivity = Date.now();
25867
25880
  async function record2(req, via, refuse) {
25868
25881
  const call = { method: req.method, url: maskToken(req.url), via };
25882
+ if (via === "fetch" && req.body) {
25883
+ try {
25884
+ const payload = JSON.parse(req.body);
25885
+ if (typeof payload?.reason === "string" && payload.reason.trim()) call.reasonSupplied = true;
25886
+ } catch {
25887
+ }
25888
+ }
25869
25889
  const auth = req.headers["authorization"];
25870
25890
  if (auth !== void 0) call.authorization = summariseAuthorization(auth);
25871
25891
  calls.push(call);
@@ -25893,12 +25913,13 @@ async function runUiSmoke(opts) {
25893
25913
  call.status = res.status;
25894
25914
  if (res.status >= 400) call.failure = `HTTP ${String(res.status)}`;
25895
25915
  else if (via === "fetch") {
25896
- const { rows: rows2, error, keys: keys2, numeric, object } = answerOf(res);
25916
+ const { rows: rows2, error, keys: keys2, numeric, object, needsReason } = answerOf(res);
25897
25917
  if (rows2 !== void 0) call.rows = rows2;
25898
25918
  if (error !== void 0) call.error = error;
25899
25919
  if (keys2 !== void 0) call.keys = keys2;
25900
25920
  if (numeric !== void 0) call.numeric = numeric;
25901
25921
  if (object !== void 0) call.object = object;
25922
+ if (needsReason) call.needsReason = true;
25902
25923
  }
25903
25924
  return res;
25904
25925
  } catch (err) {
@@ -25981,9 +26002,10 @@ async function runUiSmoke(opts) {
25981
26002
  const displaced = process.listeners("unhandledRejection");
25982
26003
  process.removeAllListeners("unhandledRejection");
25983
26004
  process.on("unhandledRejection", onUnhandled);
25984
- async function settle(deadline, since = calls.length, graceMs = 0) {
26005
+ async function settle(deadline, since = calls.length, graceMs = 0, drivePrompt) {
25985
26006
  const graceUntil = Date.now() + graceMs;
25986
26007
  while (Date.now() < deadline) {
26008
+ if (drivePrompt?.()) lastActivity = Date.now();
25987
26009
  const parsed = dom.window.document.readyState === "complete";
25988
26010
  const started = calls.length > since || inFlight > 0;
25989
26011
  if (!started && Date.now() < graceUntil) {
@@ -26213,7 +26235,24 @@ async function runUiSmoke(opts) {
26213
26235
  } catch (err) {
26214
26236
  notes = [`firing it threw: ${describeRejection(err)}`];
26215
26237
  }
26216
- await settle(Math.min(Date.now() + perInteraction, budgetUntil), since, graceMs);
26238
+ const answeredPrompts = /* @__PURE__ */ new Set();
26239
+ await settle(Math.min(Date.now() + perInteraction, budgetUntil), since, graceMs, () => {
26240
+ const pending = calls.slice(since).filter((call) => callsHandler(call.url, interaction.handler)).at(-1);
26241
+ if (!pending?.needsReason || answeredPrompts.has(pending) || answeredPrompts.size >= 3) return false;
26242
+ const dialog = dom.window.document.querySelector('[data-test="confirmDialog"]');
26243
+ const reason = dialog?.querySelector('textarea[data-test="confirmReason"]');
26244
+ const confirm = dialog?.querySelector('button[data-test="confirmOk"]');
26245
+ if (!(reason instanceof dom.window.HTMLTextAreaElement) || !(confirm instanceof dom.window.HTMLButtonElement)) return false;
26246
+ if (reason.value !== SMOKE_REASON) {
26247
+ Object.getOwnPropertyDescriptor(dom.window.HTMLTextAreaElement.prototype, "value").set.call(reason, SMOKE_REASON);
26248
+ reason.dispatchEvent(new dom.window.Event("input", { bubbles: true }));
26249
+ return true;
26250
+ }
26251
+ if (confirm.disabled) return false;
26252
+ answeredPrompts.add(pending);
26253
+ confirm.click();
26254
+ return true;
26255
+ });
26217
26256
  const outcome = judgeInteraction(
26218
26257
  interaction,
26219
26258
  calls.slice(since).map((call) => ({ ...call }))
@@ -27870,6 +27909,26 @@ function cardTitleFindings(contract) {
27870
27909
  see: SEE.labels
27871
27910
  }));
27872
27911
  }
27912
+ function uxFindings(contract) {
27913
+ if (contract === null || !isAppContract(contract)) return [];
27914
+ const codes = {
27915
+ "A3.23": "create-form-inline",
27916
+ "A3.24": "row-no-detail",
27917
+ "A3.25": "dialog-no-trigger"
27918
+ };
27919
+ const { violations } = checkUx({}, contract, null);
27920
+ return violations.flatMap((found) => {
27921
+ if (!(found.rule in codes)) return [];
27922
+ return [{
27923
+ code: codes[found.rule],
27924
+ where: found.where,
27925
+ message: found.message,
27926
+ fix: "Apply the kit guidance above to the named element in contract.json and recompile",
27927
+ see: `App kit ${found.rule} (checks/rules/ux.mjs)`,
27928
+ severity: "warning"
27929
+ }];
27930
+ });
27931
+ }
27873
27932
  function collectFindings(input) {
27874
27933
  const levers = input.bundle === void 0 ? [] : leverFindings(input.contract, input.bundle, input.handlerCode ?? {});
27875
27934
  const unread = new Set(levers.filter((one) => one.code === "param-unread").map((one) => one.where));
@@ -27895,7 +27954,8 @@ function collectFindings(input) {
27895
27954
  ...input.contract === null ? [] : auditFindings(input.contract),
27896
27955
  ...input.declared === null ? [] : unreachedFindings(input.declared, input.cases, input.contract, input.runs),
27897
27956
  // Last: said and never counted, so they never push a counted finding down the list.
27898
- ...workflow.filter((one) => one.severity === "warning")
27957
+ ...workflow.filter((one) => one.severity === "warning"),
27958
+ ...uxFindings(input.contract)
27899
27959
  ];
27900
27960
  }
27901
27961
  function formatFindings(findings) {
@@ -28390,7 +28450,8 @@ function renderInteractions(report) {
28390
28450
  if (outcome.problems.length === 0) {
28391
28451
  const answered = outcome.calls.find((call) => callsHandler(call.url, outcome.handler));
28392
28452
  const status = answered?.status === void 0 ? "" : ` (HTTP ${String(answered.status)})`;
28393
- out += ` ok ${head}${status}
28453
+ const reason = outcome.reasonCovered ? " \u2014 needs a reason: covered (canned smoke-test reason)" : "";
28454
+ out += ` ok ${head}${status}${reason}
28394
28455
  ${attached}`;
28395
28456
  continue;
28396
28457
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datarails-ai/dr-appspace-cli",
3
- "version": "0.9.7",
3
+ "version": "0.10.1",
4
4
  "description": "Datarails App Space CLI — build, try, push and publish App Space apps",
5
5
  "type": "module",
6
6
  "engines": {