@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.
Files changed (2) hide show
  1. package/dist/cli.js +71 -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.10.0",
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-30T12:30:07.259Z";
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
- await settle(Math.min(Date.now() + perInteraction, budgetUntil), since, graceMs);
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
- out += ` ok ${head}${status}
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datarails-ai/dr-appspace-cli",
3
- "version": "0.10.0",
3
+ "version": "0.10.2",
4
4
  "description": "Datarails App Space CLI — build, try, push and publish App Space apps",
5
5
  "type": "module",
6
6
  "engines": {