@datarails-ai/dr-appspace-cli 0.12.27 → 0.12.28

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 +190 -59
  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.27",
16
+ version: "0.12.28",
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-25T11:18:27.773Z";
60
+ var BUILD_TIME = "2026-10-25T11:21:22.420Z";
61
61
  function formatBuildTime(iso) {
62
62
  if (iso === "") return "";
63
63
  const d = new Date(iso);
@@ -3803,7 +3803,7 @@ function parseHtml(html) {
3803
3803
  elements.push({ tag, attrs, line: line2 });
3804
3804
  if (tag === "script" && !("src" in attrs)) {
3805
3805
  const bodyStart = match.index + whole.length;
3806
- const end = html.indexOf("</script", bodyStart);
3806
+ const end = rawTextEnd(html, "script", bodyStart);
3807
3807
  const body = html.slice(bodyStart, end === -1 ? void 0 : end);
3808
3808
  scripts.push({ text: body, line: lineAt(html, bodyStart) });
3809
3809
  if (end !== -1) TAG.lastIndex = end;
@@ -3811,6 +3811,25 @@ function parseHtml(html) {
3811
3811
  }
3812
3812
  return { elements, scripts };
3813
3813
  }
3814
+ function rawTextEnd(html, tag, from) {
3815
+ const end = new RegExp(`</${tag}(?=[\\t\\n\\f\\r />])`, "gi");
3816
+ end.lastIndex = from;
3817
+ const found = end.exec(html);
3818
+ return found === null ? -1 : found.index;
3819
+ }
3820
+ var CONTRACT_ID = "dr-contract";
3821
+ var JSON_TYPE = "application/json";
3822
+ function contractBlockOf(elements) {
3823
+ const first = elements.find((el) => el.attrs.id === CONTRACT_ID);
3824
+ if (first === void 0 || first.tag !== "script" || first.attrs.src !== void 0) return null;
3825
+ return (first.attrs.type ?? "").trim().toLowerCase() === JSON_TYPE ? first : null;
3826
+ }
3827
+ function contractBlockText(html) {
3828
+ const { elements, scripts } = parseHtml(html);
3829
+ const block = contractBlockOf(elements);
3830
+ if (block === null) return null;
3831
+ return scripts[elements.filter((el) => el.tag === "script" && !("src" in el.attrs)).indexOf(block)]?.text ?? null;
3832
+ }
3814
3833
  function parseTree(html) {
3815
3834
  const nodes = [];
3816
3835
  const roots = [];
@@ -3849,7 +3868,7 @@ function parseTree(html) {
3849
3868
  else nodes[parent].children.push(node.index);
3850
3869
  if (tag === "script" || tag === "style") {
3851
3870
  if (selfClosed) continue;
3852
- const end = html.indexOf(`</${tag}`, match.index + whole.length);
3871
+ const end = rawTextEnd(html, tag, match.index + whole.length);
3853
3872
  if (end === -1) {
3854
3873
  wellFormed = false;
3855
3874
  continue;
@@ -6080,7 +6099,6 @@ function readPage(app, page, spec, elements, problems) {
6080
6099
  pins: pinsOf(page, tree)
6081
6100
  };
6082
6101
  }
6083
- var EMBEDDED_CONTRACT = /<script\b[^>]*\bid\s*=\s*["']dr-contract["'][^>]*>([\s\S]*?)<\/script>/i;
6084
6102
  function paramsOf(page) {
6085
6103
  const entry = embeddedPageOf(page);
6086
6104
  return Array.isArray(entry?.params) ? { params: entry.params } : {};
@@ -6096,10 +6114,10 @@ function mountOf(page) {
6096
6114
  };
6097
6115
  }
6098
6116
  function embeddedContractOf(page) {
6099
- const found = EMBEDDED_CONTRACT.exec(page?.text ?? "");
6100
- if (found === null) return null;
6117
+ const text3 = contractBlockText(page?.text ?? "");
6118
+ if (text3 === null) return null;
6101
6119
  try {
6102
- const embedded = JSON.parse((found[1] ?? "").replace(/<\\\//g, "</"));
6120
+ const embedded = JSON.parse(text3.replace(/<\\\//g, "</"));
6103
6121
  return isPlainObject(embedded) ? embedded : null;
6104
6122
  } catch {
6105
6123
  return null;
@@ -6441,7 +6459,7 @@ function pageScripts(app, page, tree) {
6441
6459
  for (const node of tree.nodes) {
6442
6460
  if (node.tag !== "script") continue;
6443
6461
  if (!node.attrs.src) {
6444
- const end = page.text.indexOf("</script", node.end);
6462
+ const end = rawTextEnd(page.text, "script", node.end);
6445
6463
  inline.push({ path: page.path, text: page.text.slice(node.end, end === -1 ? void 0 : end) });
6446
6464
  continue;
6447
6465
  }
@@ -8604,7 +8622,7 @@ function payloadProducers(contract, { moduleSource = () => void 0, pageSource =
8604
8622
  }
8605
8623
  var SCRIPT_TAG = /<script\b([^>]*)>([\s\S]*?)<\/script\s*>/gi;
8606
8624
  var SRC_ATTR = /\bsrc\s*=\s*["']([^"']+)["']/i;
8607
- var JSON_TYPE = /\btype\s*=\s*["']application\/json["']/i;
8625
+ var JSON_TYPE2 = /\btype\s*=\s*["']application\/json["']/i;
8608
8626
  var MOUNT_ONLY = /^\s*drApp\.mount\(\s*\{[^}]*\}\s*\)\s*;?\s*$/;
8609
8627
  function pageCode(html, path3, modules, pageSource) {
8610
8628
  const out = [];
@@ -8612,7 +8630,7 @@ function pageCode(html, path3, modules, pageSource) {
8612
8630
  const attrs = match[1] ?? "";
8613
8631
  const src = SRC_ATTR.exec(attrs)?.[1];
8614
8632
  if (src === void 0) {
8615
- if (JSON_TYPE.test(attrs) || MOUNT_ONLY.test(match[2] ?? "") || (match[2] ?? "").trim() === "") continue;
8633
+ if (JSON_TYPE2.test(attrs) || MOUNT_ONLY.test(match[2] ?? "") || (match[2] ?? "").trim() === "") continue;
8616
8634
  out.push(match[2]);
8617
8635
  continue;
8618
8636
  }
@@ -11098,13 +11116,25 @@ function restateDashboard(composed, pages, handlerName, handler, { refuse, note,
11098
11116
  slots: {},
11099
11117
  binding: null
11100
11118
  });
11101
- const header = regions("header")[0] ?? fresh();
11102
- const chrome = HEADER_CHROME.map((role) => regions(role)[0] ?? fresh());
11119
+ const duplicates = /* @__PURE__ */ new Set();
11120
+ const onlyOne = (uids, what) => {
11121
+ for (const uid of uids.slice(1)) {
11122
+ duplicates.add(uid);
11123
+ refuse(
11124
+ "chrome_duplicate",
11125
+ `/elements/${escapePointer3(uid)}`,
11126
+ `"${uid}" is a second ${what} on ${path3}, after "${uids[0]}"; a dashboard page has one of each piece of chrome \u2014 remove it, or merge it into "${uids[0]}"`
11127
+ );
11128
+ }
11129
+ return uids[0];
11130
+ };
11131
+ const header = onlyOne(regions("header"), "header region") ?? fresh();
11132
+ const chrome = HEADER_CHROME.map((role) => onlyOne(regions(role), `${role} region`) ?? fresh());
11103
11133
  elements[header] = region(header, "header", "header", chrome);
11104
11134
  chrome.forEach((uid, index) => {
11105
11135
  elements[uid] = region(uid, HEADER_CHROME[index], "section", [], header);
11106
11136
  });
11107
- let nav = own2.find((uid) => typed[uid].type === "nav");
11137
+ let nav = onlyOne(own2.filter((uid) => typed[uid].type === "nav"), "nav");
11108
11138
  const declaredTabs = own2.filter((uid) => typed[uid].type === "tab").sort((a, b) => orderOf(typed[a]) - orderOf(typed[b]) || natural3(a, b));
11109
11139
  let tabs = [];
11110
11140
  if (declaredTabs.length > 0) {
@@ -11159,7 +11189,7 @@ function restateDashboard(composed, pages, handlerName, handler, { refuse, note,
11159
11189
  };
11160
11190
  });
11161
11191
  }
11162
- const filters = regions("filters")[0] ?? fresh();
11192
+ const filters = onlyOne(regions("filters"), "filters region") ?? fresh();
11163
11193
  elements[filters] = region(filters, "filters", "section", []);
11164
11194
  const params = (Array.isArray(page.params) ? page.params : []).filter(isPlainObject7).map(canonical);
11165
11195
  const parts = [];
@@ -11195,7 +11225,7 @@ function restateDashboard(composed, pages, handlerName, handler, { refuse, note,
11195
11225
  if (conflicts.has(uid) && (parent !== null || kept.includes(typed[uid].parent))) refuse(conflicts.get(uid).code, conflicts.get(uid).pointer, conflicts.get(uid).message);
11196
11226
  elements[uid] = cardOf(typed[uid], uid, path3, handlerName, sections, refuse, parent);
11197
11227
  }
11198
- const otherRegions = own2.filter((uid) => typed[uid].type === "region" && !handled.has(uid) && !dropped.has(uid));
11228
+ const otherRegions = own2.filter((uid) => typed[uid].type === "region" && !handled.has(uid) && !dropped.has(uid) && !duplicates.has(uid));
11199
11229
  for (const uid of otherRegions) elements[uid] = region(uid, typed[uid].role ?? "header", "section", []);
11200
11230
  const indexOfSection = (uid) => sections.findIndex((entry) => entry.id === elements[uid]?.section);
11201
11231
  const ownRoots = [...parts, ...kept.filter((uid) => elements[uid].parent === null), ...otherRegions].sort((a, b) => rank(a) - rank(b) || natural3(a, b));
@@ -21730,14 +21760,57 @@ function endOfQuote(source, start, quote) {
21730
21760
  }
21731
21761
  return source.length;
21732
21762
  }
21763
+ function withoutRegexLiterals(code) {
21764
+ const out = code.split("");
21765
+ let opens3 = true;
21766
+ let i = 0;
21767
+ while (i < code.length) {
21768
+ const ch = code[i];
21769
+ if (/\s/.test(ch)) {
21770
+ i += 1;
21771
+ continue;
21772
+ }
21773
+ if (WORD.test(ch)) {
21774
+ let j = i;
21775
+ while (j < code.length && WORD.test(code[j])) j += 1;
21776
+ opens3 = BEFORE_LITERAL_WORDS.has(code.slice(i, j));
21777
+ i = j;
21778
+ continue;
21779
+ }
21780
+ const end = ch === "/" && opens3 ? literalEnd(code, i) : -1;
21781
+ if (end !== -1) {
21782
+ blank2(out, i + 1, end);
21783
+ i = end + 1;
21784
+ while (i < code.length && WORD.test(code[i])) i += 1;
21785
+ opens3 = false;
21786
+ continue;
21787
+ }
21788
+ opens3 = ch === "/" || BEFORE_LITERAL.includes(ch);
21789
+ i += 1;
21790
+ }
21791
+ return out.join("");
21792
+ }
21793
+ var WORD = /[\w$]/;
21794
+ var BEFORE_LITERAL = "(,=:[!&|?{};+-*%<>~^}";
21795
+ var BEFORE_LITERAL_WORDS = /* @__PURE__ */ new Set(["return", "typeof", "instanceof", "in", "of", "new", "delete", "void", "throw", "case", "do", "else", "yield", "await"]);
21796
+ function literalEnd(code, start) {
21797
+ let inClass = false;
21798
+ for (let i = start + 1; i < code.length; i++) {
21799
+ const ch = code[i];
21800
+ if (ch === "\n") return -1;
21801
+ if (ch === "\\") i += 1;
21802
+ else if (ch === "[") inClass = true;
21803
+ else if (ch === "]") inClass = false;
21804
+ else if (ch === "/" && !inClass) return i;
21805
+ }
21806
+ return -1;
21807
+ }
21733
21808
 
21734
21809
  // ../flows/insights-builder/kit/checks/rules/runtime.mjs
21735
21810
  var RUNTIME_RENDERER3 = "runtime";
21736
21811
  var CARD_COMPONENT = "widget";
21737
21812
  var SPEC_CARD = "data-dr-spec-card";
21738
21813
  var COMPONENT_ATTR2 = "data-dr-component";
21739
- var CONTRACT_ID = "dr-contract";
21740
- var JSON_TYPE2 = "application/json";
21741
21814
  var MOUNT_CALL = /\bdrApp\s*\.\s*mount\s*\(/;
21742
21815
  var RUNTIME_LIBRARIES = [
21743
21816
  {
@@ -21772,19 +21845,15 @@ function compareVersions(a, b) {
21772
21845
  return 0;
21773
21846
  }
21774
21847
  function contractBlock(elements) {
21775
- const first = elements.find((el) => el.tag === "script" && el.attrs.id === CONTRACT_ID);
21776
- if (first === void 0 || first.attrs.src !== void 0) return null;
21777
- return (first.attrs.type ?? "").trim().toLowerCase() === JSON_TYPE2 ? first : null;
21848
+ return contractBlockOf(elements);
21778
21849
  }
21779
21850
  function isEmbeddedContract(el, elements) {
21780
21851
  return el === contractBlock(elements);
21781
21852
  }
21782
21853
  function runtimeContractOf(app, page) {
21783
21854
  if (app.manifest?.kind !== "dashboard") return null;
21784
- const { elements, scripts } = parseHtml(page.text ?? "");
21785
- const block = contractBlock(elements);
21786
- if (block === null) return null;
21787
- const body = scripts[elements.filter((el) => el.tag === "script" && !("src" in el.attrs)).indexOf(block)]?.text ?? "";
21855
+ const body = contractBlockText(page.text ?? "");
21856
+ if (body === null) return null;
21788
21857
  let embedded;
21789
21858
  try {
21790
21859
  embedded = JSON.parse(body.replace(/<\\\//g, "</"));
@@ -21798,7 +21867,7 @@ function contractCards(embedded, path3) {
21798
21867
  const props = isPlainObject11(element.props) ? element.props : {};
21799
21868
  const id = named6(props.id) ? props.id.trim() : "";
21800
21869
  const card = named6(props.card) ? props.card.trim() : null;
21801
- return { key: id || card || "", id, card, source: named6(props.source) ? props.source.trim() : "" };
21870
+ return { key: id || card || "", id, card, source: named6(props.source) ? props.source : "" };
21802
21871
  });
21803
21872
  }
21804
21873
  function contractParams(embedded, path3) {
@@ -21826,8 +21895,7 @@ function libraryOrderProblems(page) {
21826
21895
  const { elements, scripts } = parseHtml(page.text);
21827
21896
  const indexOf = (module) => elements.findIndex((el) => el.tag === "script" && el.attrs.src !== void 0 && RUNTIME_LIBRARIES.find((one) => one.module === module).pattern.test(resolveFromPlatformRoot(page.path, el.attrs.src) ?? ""));
21828
21897
  const inline = elements.filter((el) => el.tag === "script" && !("src" in el.attrs));
21829
- const block = contractBlock(elements);
21830
- const mountScript = inline.find((el, index) => el !== block && MOUNT_CALL.test(executableText(scripts[index]?.text ?? "")));
21898
+ const mountScript = inline.find((el, index) => runsInPlace(el) && MOUNT_CALL.test(withoutRegexLiterals(executableText(scripts[index]?.text ?? ""))));
21831
21899
  const mount = mountScript === void 0 ? -1 : elements.indexOf(mountScript);
21832
21900
  const runtime = indexOf("dr-app-runtime");
21833
21901
  const problems = [];
@@ -21851,6 +21919,28 @@ function libraryOrderProblems(page) {
21851
21919
  }
21852
21920
  return problems;
21853
21921
  }
21922
+ function runsInPlace(el) {
21923
+ const type = (el.attrs.type ?? "").trim().toLowerCase();
21924
+ return type === "" || JAVASCRIPT_TYPES.has(type);
21925
+ }
21926
+ var JAVASCRIPT_TYPES = /* @__PURE__ */ new Set([
21927
+ "application/ecmascript",
21928
+ "application/javascript",
21929
+ "application/x-ecmascript",
21930
+ "application/x-javascript",
21931
+ "text/ecmascript",
21932
+ "text/javascript",
21933
+ "text/javascript1.0",
21934
+ "text/javascript1.1",
21935
+ "text/javascript1.2",
21936
+ "text/javascript1.3",
21937
+ "text/javascript1.4",
21938
+ "text/javascript1.5",
21939
+ "text/jscript",
21940
+ "text/livescript",
21941
+ "text/x-ecmascript",
21942
+ "text/x-javascript"
21943
+ ]);
21854
21944
  function deferralOf(el) {
21855
21945
  if ("async" in el.attrs) return "async";
21856
21946
  if ("defer" in el.attrs) return "defer";
@@ -22085,9 +22175,13 @@ function checkMount(page, embedded, declared) {
22085
22175
  return [{ rule: "R2.2", where: page.path, message: `${page.path}'s mount names handler "${handler}", which this package does not declare; the filter bar's catalog and saved filters would reach no handler` }];
22086
22176
  }
22087
22177
  function runtimeSpecKeys(app) {
22088
- const spec = readSpec(app.handlers);
22089
- if (!spec.found || spec.unresolved.length > 0) return null;
22090
- return new Set(Object.keys(spec.table).filter((key) => !key.startsWith("_") && !key.startsWith("$")));
22178
+ const keys2 = /* @__PURE__ */ new Map();
22179
+ for (const handler of app.handlers) {
22180
+ const spec = readSpec([handler]);
22181
+ if (!spec.found || spec.unresolved.length > 0) continue;
22182
+ keys2.set(handler.name, new Set(Object.keys(spec.table).filter((key) => !key.startsWith("_") && !key.startsWith("$"))));
22183
+ }
22184
+ return keys2.size === 0 ? null : keys2;
22091
22185
  }
22092
22186
  function checkScripts(scripts) {
22093
22187
  const violations = [];
@@ -22265,7 +22359,8 @@ function checkRuntimeCards(app, page, elements, runtime, violations) {
22265
22359
  continue;
22266
22360
  }
22267
22361
  if (drawn2 !== own2) {
22268
- const unknown = runtime.specKeys !== null && !runtime.specKeys.has(drawn2) ? `, and "${drawn2}" is no row of the handler's SPEC` : "";
22362
+ const keys2 = runtime.specKeys?.get(twin.source);
22363
+ const unknown = keys2 !== void 0 && !keys2.has(drawn2) ? `, and "${drawn2}" is no row of the handler's SPEC` : "";
22269
22364
  violations.push({ rule: "R2.5", where: where2(el.line), message: `the card${id === "" ? "" : ` with data-dr-id "${id}"`} draws "${own2}" in the skeleton and "${drawn2}" in #dr-contract, which the runtime mounts from${unknown}; the two name one SPEC card, or the page shows one card and a round trip writes back another` });
22270
22365
  claim(id, own2, drawn2);
22271
22366
  continue;
@@ -22288,12 +22383,22 @@ function checkRuntimeCards(app, page, elements, runtime, violations) {
22288
22383
  if (!(el.attrs[SPAN_MARK] ?? "").trim()) {
22289
22384
  violations.push({ rule: "R2.7", where: where2(el.line), message: `widget "${own2}" declares no ${SPAN_MARK}; every card on the kit's 12-column grid says how many columns it takes` });
22290
22385
  }
22291
- const handler = twin.source || (el.attrs["data-dr-source"] ?? "").trim();
22292
- for (const name of new Set([handler, (el.attrs["data-dr-source"] ?? "").trim()].filter((one) => one !== ""))) {
22386
+ const handler = twin.source;
22387
+ const written = (el.attrs["data-dr-source"] ?? "").trim();
22388
+ for (const name of new Set([handler, written].filter((one) => one !== ""))) {
22293
22389
  if (!runtime.declared.has(name)) {
22294
22390
  violations.push({ rule: "R2.2", where: where2(el.line), message: `widget "${own2}" is posted to handler "${name}", which this package does not declare; the runtime's request for it would reach no handler` });
22391
+ claim(own2);
22295
22392
  }
22296
22393
  }
22394
+ if (handler === "") {
22395
+ violations.push({
22396
+ rule: "R2.2",
22397
+ where: where2(el.line),
22398
+ message: `widget "${own2}" is posted to no handler: its props.source in #dr-contract, which the runtime mounts from, names none${written === "" ? "" : ` (the skeleton's data-dr-source "${written}" is not what the runtime reads)`}, so the runtime never loads it`
22399
+ });
22400
+ claim(own2);
22401
+ }
22297
22402
  const source = specSource(runtime.data, own2);
22298
22403
  widgets.push({
22299
22404
  id: own2,
@@ -22505,20 +22610,26 @@ function checkAgainstSpec(app, pages, contract, specKeys = null) {
22505
22610
  );
22506
22611
  }
22507
22612
  if (!runtime || specKeys === null) return orphans;
22508
- const runtimeOrphans = pages.filter((page) => runtimePages.has(page.path)).flatMap(
22509
- (page) => page.widgets.filter((widget) => !specKeys.has(widget.id)).map((widget) => ({ rule: "R2.5", where: `${page.path}:${widget.line}`, message: `widget "${widget.id}" is not a key in the handler's SPEC; the page would ask for a widget the handler cannot answer for` }))
22510
- );
22511
- const drawn2 = new Set(pages.flatMap((page) => [...page.widgets.map((widget) => widget.id), ...page.claimed ?? []]));
22512
- return [...orphans, ...runtimeOrphans, ...orphanSpecRows(app, [...specKeys].filter((key) => !drawn2.has(key)).sort())];
22513
- }
22514
- function orphanSpecRows(app, keys2) {
22515
- const handler = app.handlers.find((one) => SPEC_TABLE.test(one.source)) ?? app.handlers[0];
22516
- const file = handler?.file || app.handlersSource;
22517
- return keys2.map((key) => ({
22613
+ const cards = pages.filter((page) => runtimePages.has(page.path)).flatMap((page) => page.widgets.map((widget) => ({ page, widget })));
22614
+ const unanswered = cards.filter(({ widget }) => specKeys.has(widget.handler) && !specKeys.get(widget.handler).has(widget.id));
22615
+ const runtimeOrphans = unanswered.map(({ page, widget }) => ({
22518
22616
  rule: "R2.5",
22519
- where: `${file}#SPEC.${key}`,
22520
- message: `the SPEC declares "${key}", and no card on any page draws it (no [data-dr-component="widget"] names it, in ${SPEC_CARD} or in #dr-contract); the handler answers for a figure the dashboard never shows`
22617
+ where: `${page.path}:${widget.line}`,
22618
+ message: `widget "${widget.id}" is not a key in the handler's SPEC; the page would ask for a widget the handler cannot answer for`
22521
22619
  }));
22620
+ const drawn2 = new Set(cards.filter(({ widget }) => specKeys.has(widget.handler)).map(({ widget }) => `${widget.handler}\0${widget.id}`));
22621
+ const claimed = /* @__PURE__ */ new Set([...pages.flatMap((page) => page.claimed ?? []), ...unanswered.map(({ widget }) => widget.id)]);
22622
+ return [...orphans, ...runtimeOrphans, ...orphanSpecRows(app, specKeys, drawn2, claimed)];
22623
+ }
22624
+ function orphanSpecRows(app, specKeys, drawn2, claimed) {
22625
+ return app.handlers.filter((handler) => specKeys.has(handler.name)).flatMap((handler) => {
22626
+ const file = handler.file || app.handlersSource;
22627
+ return [...specKeys.get(handler.name)].filter((key) => !drawn2.has(`${handler.name}\0${key}`) && !claimed.has(key)).sort().map((key) => ({
22628
+ rule: "R2.5",
22629
+ where: `${file}#SPEC.${key}`,
22630
+ message: `the SPEC declares "${key}", and no card on any page draws it (no [data-dr-component="widget"] names it, in ${SPEC_CARD} or in #dr-contract); the handler answers for a figure the dashboard never shows`
22631
+ }));
22632
+ });
22522
22633
  }
22523
22634
 
22524
22635
  // ../flows/insights-builder/kit/checks/rules/handlers.mjs
@@ -23101,7 +23212,7 @@ function parseHtml2(html) {
23101
23212
  elements.push({ tag, attrs, line: line2 });
23102
23213
  if (tag === "script" && !("src" in attrs)) {
23103
23214
  const bodyStart = match.index + whole.length;
23104
- const end = html.indexOf("</script", bodyStart);
23215
+ const end = rawTextEnd2(html, "script", bodyStart);
23105
23216
  const body = html.slice(bodyStart, end === -1 ? void 0 : end);
23106
23217
  scripts.push({ text: body, line: lineAt2(html, bodyStart) });
23107
23218
  if (end !== -1) TAG2.lastIndex = end;
@@ -23109,6 +23220,25 @@ function parseHtml2(html) {
23109
23220
  }
23110
23221
  return { elements, scripts };
23111
23222
  }
23223
+ function rawTextEnd2(html, tag, from) {
23224
+ const end = new RegExp(`</${tag}(?=[\\t\\n\\f\\r />])`, "gi");
23225
+ end.lastIndex = from;
23226
+ const found = end.exec(html);
23227
+ return found === null ? -1 : found.index;
23228
+ }
23229
+ var CONTRACT_ID2 = "dr-contract";
23230
+ var JSON_TYPE3 = "application/json";
23231
+ function contractBlockOf2(elements) {
23232
+ const first = elements.find((el) => el.attrs.id === CONTRACT_ID2);
23233
+ if (first === void 0 || first.tag !== "script" || first.attrs.src !== void 0) return null;
23234
+ return (first.attrs.type ?? "").trim().toLowerCase() === JSON_TYPE3 ? first : null;
23235
+ }
23236
+ function contractBlockText2(html) {
23237
+ const { elements, scripts } = parseHtml2(html);
23238
+ const block = contractBlockOf2(elements);
23239
+ if (block === null) return null;
23240
+ return scripts[elements.filter((el) => el.tag === "script" && !("src" in el.attrs)).indexOf(block)]?.text ?? null;
23241
+ }
23112
23242
  function parseTree2(html) {
23113
23243
  const nodes = [];
23114
23244
  const roots = [];
@@ -23147,7 +23277,7 @@ function parseTree2(html) {
23147
23277
  else nodes[parent].children.push(node.index);
23148
23278
  if (tag === "script" || tag === "style") {
23149
23279
  if (selfClosed) continue;
23150
- const end = html.indexOf(`</${tag}`, match.index + whole.length);
23280
+ const end = rawTextEnd2(html, tag, match.index + whole.length);
23151
23281
  if (end === -1) {
23152
23282
  wellFormed = false;
23153
23283
  continue;
@@ -24439,7 +24569,6 @@ function readPage2(app, page, spec, elements, problems) {
24439
24569
  pins: pinsOf2(page, tree)
24440
24570
  };
24441
24571
  }
24442
- var EMBEDDED_CONTRACT2 = /<script\b[^>]*\bid\s*=\s*["']dr-contract["'][^>]*>([\s\S]*?)<\/script>/i;
24443
24572
  function paramsOf2(page) {
24444
24573
  const entry = embeddedPageOf2(page);
24445
24574
  return Array.isArray(entry?.params) ? { params: entry.params } : {};
@@ -24455,10 +24584,10 @@ function mountOf2(page) {
24455
24584
  };
24456
24585
  }
24457
24586
  function embeddedContractOf2(page) {
24458
- const found = EMBEDDED_CONTRACT2.exec(page?.text ?? "");
24459
- if (found === null) return null;
24587
+ const text3 = contractBlockText2(page?.text ?? "");
24588
+ if (text3 === null) return null;
24460
24589
  try {
24461
- const embedded = JSON.parse((found[1] ?? "").replace(/<\\\//g, "</"));
24590
+ const embedded = JSON.parse(text3.replace(/<\\\//g, "</"));
24462
24591
  return isPlainObject12(embedded) ? embedded : null;
24463
24592
  } catch {
24464
24593
  return null;
@@ -24797,7 +24926,7 @@ function pageScripts2(app, page, tree) {
24797
24926
  for (const node of tree.nodes) {
24798
24927
  if (node.tag !== "script") continue;
24799
24928
  if (!node.attrs.src) {
24800
- const end = page.text.indexOf("</script", node.end);
24929
+ const end = rawTextEnd2(page.text, "script", node.end);
24801
24930
  inline.push({ path: page.path, text: page.text.slice(node.end, end === -1 ? void 0 : end) });
24802
24931
  continue;
24803
24932
  }
@@ -26110,7 +26239,7 @@ function payloadProducers2(contract, { moduleSource = () => void 0, pageSource =
26110
26239
  }
26111
26240
  var SCRIPT_TAG2 = /<script\b([^>]*)>([\s\S]*?)<\/script\s*>/gi;
26112
26241
  var SRC_ATTR2 = /\bsrc\s*=\s*["']([^"']+)["']/i;
26113
- var JSON_TYPE3 = /\btype\s*=\s*["']application\/json["']/i;
26242
+ var JSON_TYPE4 = /\btype\s*=\s*["']application\/json["']/i;
26114
26243
  var MOUNT_ONLY2 = /^\s*drApp\.mount\(\s*\{[^}]*\}\s*\)\s*;?\s*$/;
26115
26244
  function pageCode2(html, path3, modules, pageSource) {
26116
26245
  const out = [];
@@ -26118,7 +26247,7 @@ function pageCode2(html, path3, modules, pageSource) {
26118
26247
  const attrs = match[1] ?? "";
26119
26248
  const src = SRC_ATTR2.exec(attrs)?.[1];
26120
26249
  if (src === void 0) {
26121
- if (JSON_TYPE3.test(attrs) || MOUNT_ONLY2.test(match[2] ?? "") || (match[2] ?? "").trim() === "") continue;
26250
+ if (JSON_TYPE4.test(attrs) || MOUNT_ONLY2.test(match[2] ?? "") || (match[2] ?? "").trim() === "") continue;
26122
26251
  out.push(match[2]);
26123
26252
  continue;
26124
26253
  }
@@ -28624,11 +28753,13 @@ var REFUSALS = /* @__PURE__ */ new Set([
28624
28753
  "card_not_for_apps",
28625
28754
  // a runtime dashboard (`dashboardPage.mjs`): a card bound to no SPEC row,
28626
28755
  // a control no part draws, a control whose param is declared otherwise,
28627
- // and a page route app.routes does not serve
28756
+ // a page route app.routes does not serve, and a second piece of chrome
28757
+ // (a header, asof, refresh, theme or filters region, or a nav) on a page
28628
28758
  "card_unnamed",
28629
28759
  "control_unsupported",
28630
28760
  "control_conflict",
28631
- "route_mismatch"
28761
+ "route_mismatch",
28762
+ "chrome_duplicate"
28632
28763
  ]);
28633
28764
  var APP_REFUSALS = {
28634
28765
  app_id_shape: 'app.id of an app with a data model is not lowercase letters, digits and "-" (e.g. "arr_test_9"); the push would take it and the first table read would fail with "invalid app_id" \u2014 rename it (e.g. "arr-test-9")',
@@ -30014,7 +30145,7 @@ async function ejectHandler(dir, contract, name) {
30014
30145
  function customRole(body) {
30015
30146
  const start = body.search(/^HANDLER = \{/m);
30016
30147
  if (start < 0) return null;
30017
- const end = literalEnd(body, body.indexOf("{", start));
30148
+ const end = literalEnd2(body, body.indexOf("{", start));
30018
30149
  if (end < 0) return null;
30019
30150
  const literal = body.slice(start, end);
30020
30151
  const rewritten = literal.replace(/"role": "[^"]*"/, '"role": "custom"');
@@ -30025,7 +30156,7 @@ var EJECTED_DOCSTRING = 'Ejected with app-space eject: this file is yours now, a
30025
30156
  function authorsDocstring(body) {
30026
30157
  return body.replace(/Generated by [^\n]*compile[\s\S]*?not this file\./, EJECTED_DOCSTRING);
30027
30158
  }
30028
- function literalEnd(text3, open2) {
30159
+ function literalEnd2(text3, open2) {
30029
30160
  let depth = 0;
30030
30161
  let quote = null;
30031
30162
  for (let i = open2; i < text3.length; i++) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datarails-ai/dr-appspace-cli",
3
- "version": "0.12.27",
3
+ "version": "0.12.28",
4
4
  "description": "Datarails App Space CLI — build, try, push and publish App Space apps",
5
5
  "type": "module",
6
6
  "engines": {