@datarails-ai/dr-appspace-cli 0.12.34 → 0.12.36

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 +67 -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.12.34",
16
+ version: "0.12.36",
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-26T12:43:18.858Z";
60
+ var BUILD_TIME = "2026-10-26T13:45:46.965Z";
61
61
  function formatBuildTime(iso) {
62
62
  if (iso === "") return "";
63
63
  const d = new Date(iso);
@@ -5961,6 +5961,11 @@ function readPage(app, page, spec, elements, problems) {
5961
5961
  well_formed: tree.well_formed,
5962
5962
  tree: [],
5963
5963
  owned: true,
5964
+ // A page `app-space eject page:` made owned still embeds the contract
5965
+ // it was compiled from, and the runtime's param store reads the
5966
+ // page's params from there, as it does on a compiled page. A page
5967
+ // written by hand with no embedded contract says nothing, as before.
5968
+ ...paramsOf(page),
5964
5969
  ...pageRolesOf(app.manifest, page.path)
5965
5970
  },
5966
5971
  pins: pinsOf(page, tree)
@@ -10517,7 +10522,7 @@ function compile(contract, { kit, extra = {} } = {}) {
10517
10522
  for (const page of pages) {
10518
10523
  files[page.path] = pageFile(page, { contract: composed, pages, kit, problems });
10519
10524
  }
10520
- return { files, problems: problems.sort(byProblem2) };
10525
+ return { files, problems: problems.sort(byProblem2), warnings: narrativeOrderWarnings(composed, applied) };
10521
10526
  }
10522
10527
  var RUNTIME_RENDERER2 = "runtime";
10523
10528
  function dashboardHandler(composed, handlerName, extra = {}) {
@@ -10593,6 +10598,43 @@ function missingExpressions(widgets) {
10593
10598
  }
10594
10599
  return problems;
10595
10600
  }
10601
+ function narrativeOrderWarnings(composed, applied) {
10602
+ const picked = new Set(applied.filter((fill) => fill.what === "layout.order").map((fill) => fill.where));
10603
+ const elements = isPlainObject6(composed?.elements) ? composed.elements : {};
10604
+ const widgets = isPlainObject6(composed?.data?.widgets) ? composed.data.widgets : {};
10605
+ const pages = [...Array.isArray(composed?.pages) ? composed.pages : []].sort((a, b) => natural3(a.path ?? "", b.path ?? ""));
10606
+ const named13 = (value) => typeof value === "string" && value !== "";
10607
+ const quoted3 = (ids) => ids.map((id) => JSON.stringify(id)).join(", ");
10608
+ const warnings = [];
10609
+ for (const page of pages) {
10610
+ if (picked.has(page.path)) continue;
10611
+ const sections = Array.isArray(page.sections) ? page.sections : [];
10612
+ const sectionOf4 = (uid) => sections.findIndex((entry) => isPlainObject6(entry) && entry.id === elements[uid].section);
10613
+ const cards = Object.keys(elements).filter((uid) => elements[uid]?.page === page.path && elements[uid]?.type === "widget").sort((a, b) => sectionOf4(a) - sectionOf4(b) || order(elements[a]) - order(elements[b]) || natural3(a, b));
10614
+ const specOf2 = (uid) => {
10615
+ const element = elements[uid];
10616
+ const props = isPlainObject6(element.props) ? element.props : {};
10617
+ return named13(element.id) ? element.id : named13(props.card) ? props.card : named13(props.id) ? props.id : uid;
10618
+ };
10619
+ cards.forEach((uid, at) => {
10620
+ const own2 = specOf2(uid);
10621
+ const row = widgets[own2];
10622
+ const about = isPlainObject6(row) && row.kind === "narrative" && Array.isArray(row.about) ? row.about.filter((id) => named13(id) && id !== own2) : [];
10623
+ if (about.length === 0) return;
10624
+ const after = cards.slice(at + 1).filter((other) => sectionOf4(other) === sectionOf4(uid) && about.includes(specOf2(other)));
10625
+ if (after.length === 0) return;
10626
+ const later = [...new Set(after.map(specOf2))];
10627
+ const last = specOf2(after[after.length - 1]);
10628
+ const where2 = sectionOf4(uid) === -1 ? page.path : `section "${sections[sectionOf4(uid)].id}" of ${page.path}`;
10629
+ warnings.push({
10630
+ code: "narrative_before_about",
10631
+ pointer: `/elements/${escapePointer3(uid)}/order`,
10632
+ message: `narrative "${own2}" comes before ${quoted3(later)}, ${later.length === 1 ? "a card" : "cards"} it is about, on ${where2}: it reads the cards it is about as it loads, so its context gives ${quoted3(later)} as "(not loaded yet)". The page keeps the order you stated; move "${own2}" after "${last}"`
10633
+ });
10634
+ });
10635
+ }
10636
+ return warnings;
10637
+ }
10596
10638
  var DEFAULT_CAPABILITIES = { financeos: ["metrics:read", "raw:read"] };
10597
10639
  function manifestFile(contract, handlerName) {
10598
10640
  const app = contract.app ?? {};
@@ -11073,7 +11115,7 @@ function compileDashboard(contract, { kit = null, extra = {}, uiKit, charts, fmt
11073
11115
  files[handlerPath(composed, handlerName)] = handler.text;
11074
11116
  const shelf2 = { runtime: DASHBOARD_RUNTIME, widgets: DASHBOARD_WIDGETS, bar: DASHBOARD_BAR, assistant, markdown, uiKit, charts, fmt, appComponents, appControls };
11075
11117
  for (const page of whole.pages) files[page.path] = pageHtml(whole, page, elements, tree, shelf2);
11076
- return { files, problems, contract: whole, order: order2, warnings: [] };
11118
+ return { files, problems, contract: whole, order: order2, warnings: narrativeOrderWarnings(composed, applied) };
11077
11119
  }
11078
11120
  function isRefusal(problem5) {
11079
11121
  return problem5.lossy !== false;
@@ -24755,6 +24797,11 @@ function readPage2(app, page, spec, elements, problems) {
24755
24797
  well_formed: tree.well_formed,
24756
24798
  tree: [],
24757
24799
  owned: true,
24800
+ // A page `app-space eject page:` made owned still embeds the contract
24801
+ // it was compiled from, and the runtime's param store reads the
24802
+ // page's params from there, as it does on a compiled page. A page
24803
+ // written by hand with no embedded contract says nothing, as before.
24804
+ ...paramsOf2(page),
24758
24805
  ...pageRolesOf2(app.manifest, page.path)
24759
24806
  },
24760
24807
  pins: pinsOf2(page, tree)
@@ -30581,21 +30628,32 @@ async function ejectPage(dir, contract, path3) {
30581
30628
  element["children"] = element["children"].filter((uid) => !gone.has(String(uid)));
30582
30629
  }
30583
30630
  const kept = {};
30584
- for (const key of ["path", "route", "title"]) if (page[key] !== void 0) kept[key] = page[key];
30631
+ for (const key of ["path", "route", "title", "params", "roles"]) if (page[key] !== void 0) kept[key] = page[key];
30585
30632
  kept["owned"] = true;
30586
- if (gate !== null) kept["role"] = gate;
30633
+ if (gate !== null && kept["roles"] === void 0) kept["role"] = gate;
30587
30634
  pages[index] = kept;
30588
30635
  const app = obj3(obj3(contract["data"])["app"]);
30589
30636
  if (Array.isArray(app["screens"])) {
30590
30637
  app["screens"] = app["screens"].filter((screen) => !(isPlain2(screen) && screen["page"] === path3));
30591
30638
  }
30592
- const wrote = await commit(dir, contract, path3, marked2);
30639
+ const sources = Object.keys(obj3(obj3(contract["data"])["sources"]));
30640
+ const served = sources.filter((name) => files[`handlers/${name}.py`] !== void 0);
30641
+ const wrote = await commit(dir, contract, path3, marked2, (after) => {
30642
+ const lost = served.filter((name) => after[`handlers/${name}.py`] === void 0).sort();
30643
+ if (lost.length > 0) throw new Refused(unservedAfter(path3, lost));
30644
+ });
30593
30645
  return {
30594
30646
  target: `page:${path3}`,
30595
30647
  wrote,
30596
30648
  said: `page ${path3} is yours now: owned, and its ${String(gone.size)} element${gone.size === 1 ? "" : "s"} left the contract.`
30597
30649
  };
30598
30650
  }
30651
+ function unservedAfter(path3, lost) {
30652
+ const one = lost.length === 1;
30653
+ const names = lost.map((name) => JSON.stringify(name)).join(", ");
30654
+ const files = lost.map((name) => `handlers/${name}.py`).join(", ");
30655
+ return `${path3} is the only page whose cards read source${one ? "" : "s"} ${names}. Owned, the page has no elements in the contract, and the compiler writes a handler only for a source some element reads, so ${files} would go and the page would call ${one ? "a handler" : "handlers"} that ${one ? "is" : "are"} not there. Keep a card that reads ${one ? "it" : "each"} on a compiled page, or eject ${one ? "the source" : "each source"} first (app-space eject ${lost.map((name) => `source:${name}`).join(", ")}), which makes it a custom handler the compiler always carries, where that eject is allowed: eject source: says when it is not.`;
30656
+ }
30599
30657
  function navGateOf(files, path3) {
30600
30658
  const gates = navGates;
30601
30659
  for (const [file, text3] of Object.entries(files)) {
@@ -30727,7 +30785,7 @@ async function mayPlace(dir, path3, text3, same5) {
30727
30785
  `${path3} already holds something other than what eject would write there. It is yours: move it aside first, or keep it and edit contract.json by hand.`
30728
30786
  );
30729
30787
  }
30730
- async function commit(dir, contract, path3, text3) {
30788
+ async function commit(dir, contract, path3, text3, check5) {
30731
30789
  const invalid = validateContract(contract, NO_CATALOG).problems;
30732
30790
  if (invalid.length > 0) {
30733
30791
  throw new Refused(`after this eject contract.json would not validate, so eject leaves the folder as it was:
@@ -30745,6 +30803,7 @@ ${formatProblems(invalid)}`);
30745
30803
  ${lines}
30746
30804
  `);
30747
30805
  }
30806
+ check5?.(built.files);
30748
30807
  const full = join16(dir, path3);
30749
30808
  const wrote = await readIfPresent(full) === text3 ? [] : [path3];
30750
30809
  if (wrote.length > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datarails-ai/dr-appspace-cli",
3
- "version": "0.12.34",
3
+ "version": "0.12.36",
4
4
  "description": "Datarails App Space CLI — build, try, push and publish App Space apps",
5
5
  "type": "module",
6
6
  "engines": {