@datarails-ai/dr-appspace-cli 0.10.6 → 0.10.8
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 +164 -11
- 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.8",
|
|
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-30T19:41:32.269Z";
|
|
61
61
|
function formatBuildTime(iso) {
|
|
62
62
|
if (iso === "") return "";
|
|
63
63
|
const d = new Date(iso);
|
|
@@ -2793,10 +2793,53 @@ function conflictTables(body) {
|
|
|
2793
2793
|
const entries = Object.entries(raw).filter((e) => Array.isArray(e[1]) && e[1].length > 0).map(([table, owners]) => [table, owners.map(String)]);
|
|
2794
2794
|
return entries.length === 0 ? void 0 : entries;
|
|
2795
2795
|
}
|
|
2796
|
+
function conflictOwners(body, table) {
|
|
2797
|
+
const owners = (body?.conflicts?.[table] ?? {}).owners;
|
|
2798
|
+
if (!Array.isArray(owners)) return [];
|
|
2799
|
+
return owners.flatMap((o) => {
|
|
2800
|
+
if (typeof o !== "object" || o === null) return [];
|
|
2801
|
+
const r = o;
|
|
2802
|
+
if (typeof r["app_id"] !== "string") return [];
|
|
2803
|
+
const cols = r["columns"];
|
|
2804
|
+
return [
|
|
2805
|
+
{
|
|
2806
|
+
appId: r["app_id"],
|
|
2807
|
+
columns: typeof cols === "object" && cols !== null && !Array.isArray(cols) ? Object.fromEntries(Object.entries(cols).map(([k, v]) => [k, String(v)])) : null,
|
|
2808
|
+
connectProblems: Array.isArray(r["connect_problems"]) ? r["connect_problems"].map(String) : []
|
|
2809
|
+
}
|
|
2810
|
+
];
|
|
2811
|
+
});
|
|
2812
|
+
}
|
|
2813
|
+
function columnsLine(columns) {
|
|
2814
|
+
return Object.entries(columns).map(([n, t]) => `${n} ${t}`).join(", ");
|
|
2815
|
+
}
|
|
2816
|
+
function conflictGuidance(body) {
|
|
2817
|
+
const lines = [];
|
|
2818
|
+
for (const [table, ids] of conflictTables(body) ?? []) {
|
|
2819
|
+
const owners = conflictOwners(body, table);
|
|
2820
|
+
for (const owner of owners.length > 0 ? owners : ids.map((appId) => ({ appId, columns: null, connectProblems: [] }))) {
|
|
2821
|
+
lines.push(
|
|
2822
|
+
`table "${table}" is owned by ${owner.appId}` + (owner.columns === null ? " (its structure could not be read)" : ` with columns: ${columnsLine(owner.columns)}`)
|
|
2823
|
+
);
|
|
2824
|
+
if (owner.connectProblems.length > 0) {
|
|
2825
|
+
lines.push(
|
|
2826
|
+
` as declared, connecting would be refused: ${owner.connectProblems.join("; ")}`,
|
|
2827
|
+
" Match the owner's columns and types exactly (tables share ONE structure: the owner's), or rename yours."
|
|
2828
|
+
);
|
|
2829
|
+
} else if (owners.length > 0) {
|
|
2830
|
+
lines.push(
|
|
2831
|
+
` to share it, add to manifest.json: "connect_tables": ${JSON.stringify({ [table]: owner.appId })}`
|
|
2832
|
+
);
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
return lines;
|
|
2837
|
+
}
|
|
2796
2838
|
function describeFailure(status, method, path3, body) {
|
|
2797
2839
|
const conflicts = conflictTables(body);
|
|
2798
2840
|
if (status === 409 && conflicts !== void 0) {
|
|
2799
|
-
return `${method} ${path3} failed (409) \u2014 ${conflicts.map(([table, owners]) => `table "${table}" already belongs to ${owners.join(", ")}`).join("; ")}. Rename the table in data_model.json, or
|
|
2841
|
+
return `${method} ${path3} failed (409) \u2014 ${conflicts.map(([table, owners]) => `table "${table}" already belongs to ${owners.join(", ")}`).join("; ")}. Rename the table in data_model.json, or share the existing one with \`connect_tables\` in manifest.json.` + conflictGuidance(body).map((l) => `
|
|
2842
|
+
${l}`).join("");
|
|
2800
2843
|
}
|
|
2801
2844
|
const code = body?.detail?.error;
|
|
2802
2845
|
if (status === 403 && code === "studio_not_enabled") {
|
|
@@ -6941,6 +6984,10 @@ async function pushBody(dir, opts) {
|
|
|
6941
6984
|
if (ui?.field === "frontend_bundle_gz") {
|
|
6942
6985
|
body["frontend_bundle_gz"] = ui.files;
|
|
6943
6986
|
}
|
|
6987
|
+
const connectTables = manifest["connect_tables"];
|
|
6988
|
+
if (typeof connectTables === "object" && connectTables !== null && !Array.isArray(connectTables)) {
|
|
6989
|
+
if (Object.keys(connectTables).length > 0) body["connect_tables"] = connectTables;
|
|
6990
|
+
}
|
|
6944
6991
|
if (contract !== null) {
|
|
6945
6992
|
body["contract"] = contract;
|
|
6946
6993
|
}
|
|
@@ -7010,6 +7057,10 @@ async function pushFolder(dir, opts) {
|
|
|
7010
7057
|
}
|
|
7011
7058
|
if (result.stage !== "published") {
|
|
7012
7059
|
process.stdout.write("Only you can see it until an admin publishes it.\n");
|
|
7060
|
+
} else if (opts.publish !== true) {
|
|
7061
|
+
process.stdout.write(
|
|
7062
|
+
"The app was already published: this push changed its working version only; the published one is unchanged until it is published again.\n"
|
|
7063
|
+
);
|
|
7013
7064
|
}
|
|
7014
7065
|
return outcome;
|
|
7015
7066
|
} finally {
|
|
@@ -7146,9 +7197,11 @@ async function writePackage(dir, appId, pkg) {
|
|
|
7146
7197
|
rm2(join10(dir, "handlers"), { recursive: true, force: true }),
|
|
7147
7198
|
rm2(join10(dir, "ui"), { recursive: true, force: true })
|
|
7148
7199
|
]);
|
|
7200
|
+
const connected = pkg.connect_tables ?? {};
|
|
7201
|
+
const manifestOut = Object.keys(connected).length > 0 ? { ...pkg.manifest, connect_tables: connected } : pkg.manifest;
|
|
7149
7202
|
await writeFile5(
|
|
7150
7203
|
join10(dir, "manifest.json"),
|
|
7151
|
-
JSON.stringify(
|
|
7204
|
+
JSON.stringify(manifestOut, null, 2) + "\n"
|
|
7152
7205
|
);
|
|
7153
7206
|
if (pkg.data_model !== null && pkg.data_model !== void 0) {
|
|
7154
7207
|
await writeFile5(
|
|
@@ -10763,12 +10816,19 @@ function fromRefusal(status, detail, contract) {
|
|
|
10763
10816
|
const tables = tablesOf(contract);
|
|
10764
10817
|
const conflicts = conflictTables(detail);
|
|
10765
10818
|
if (conflicts !== void 0) {
|
|
10766
|
-
return conflicts.map(([table, owners]) =>
|
|
10767
|
-
|
|
10768
|
-
|
|
10769
|
-
|
|
10770
|
-
|
|
10771
|
-
|
|
10819
|
+
return conflicts.map(([table, owners]) => {
|
|
10820
|
+
const owned = conflictOwners(detail, table);
|
|
10821
|
+
const detailLines = conflictGuidance({
|
|
10822
|
+
conflict_tables: { [table]: owners },
|
|
10823
|
+
conflicts: detail.conflicts
|
|
10824
|
+
});
|
|
10825
|
+
return {
|
|
10826
|
+
code: "server:table-conflict",
|
|
10827
|
+
pointer: tables.has(table) ? pointer("app", "data_model", "tables", table) : "/",
|
|
10828
|
+
message: `${refused}: table "${table}" already belongs to ${owners.join(", ")}.` + (owned.length > 0 ? ` ${detailLines.join(" ")}` : ""),
|
|
10829
|
+
fix: `Rename the table, or share the existing one: add "connect_tables" to manifest.json (the entry is in the message) with your columns matching the owner's exactly.`
|
|
10830
|
+
};
|
|
10831
|
+
});
|
|
10772
10832
|
}
|
|
10773
10833
|
if (isObj3(detail) && detail["error"] === "stale_revision") {
|
|
10774
10834
|
return [
|
|
@@ -25094,6 +25154,29 @@ function findNode(nodes, interaction) {
|
|
|
25094
25154
|
}
|
|
25095
25155
|
return void 0;
|
|
25096
25156
|
}
|
|
25157
|
+
function closedDialogOf(node) {
|
|
25158
|
+
const hidden = node.closest?.("[hidden]") ?? null;
|
|
25159
|
+
if (hidden === null) return void 0;
|
|
25160
|
+
const dialog = hidden.closest?.('[data-dr-component="dialog"]') ?? null;
|
|
25161
|
+
if (dialog === null || dialog.getAttribute("data-dr-open") === "true") return void 0;
|
|
25162
|
+
return dialog;
|
|
25163
|
+
}
|
|
25164
|
+
function dialogTriggerOf(dialog) {
|
|
25165
|
+
const marked2 = Array.from(dialog.querySelectorAll('[data-test="dialogTrigger"]'))[0];
|
|
25166
|
+
if (marked2 !== void 0) return marked2;
|
|
25167
|
+
return Array.from(dialog.querySelectorAll("button")).find((button) => (button.closest?.("[hidden]") ?? null) === null);
|
|
25168
|
+
}
|
|
25169
|
+
function openDialogNodeFor(document, interaction) {
|
|
25170
|
+
for (const box of Array.from(document.querySelectorAll('[role="dialog"]'))) {
|
|
25171
|
+
const node = findNode(box.querySelectorAll(INTERACTION_SELECTOR), { ...interaction, occurrence: 0 });
|
|
25172
|
+
if (node !== void 0) return node;
|
|
25173
|
+
}
|
|
25174
|
+
return void 0;
|
|
25175
|
+
}
|
|
25176
|
+
function press(window, node) {
|
|
25177
|
+
if (typeof node.click === "function") node.click();
|
|
25178
|
+
else node.dispatchEvent(new window.Event("click", { bubbles: true, cancelable: true }));
|
|
25179
|
+
}
|
|
25097
25180
|
function fire(window, node, interaction) {
|
|
25098
25181
|
const notes = [];
|
|
25099
25182
|
if (interaction.kind === "form") {
|
|
@@ -26497,7 +26580,7 @@ async function runUiSmoke(opts) {
|
|
|
26497
26580
|
const nodes = dom.window.document.querySelectorAll(
|
|
26498
26581
|
INTERACTION_SELECTOR
|
|
26499
26582
|
);
|
|
26500
|
-
|
|
26583
|
+
let node = findNode(nodes, interaction);
|
|
26501
26584
|
if (node === void 0) {
|
|
26502
26585
|
notExercised.push({
|
|
26503
26586
|
handler: interaction.handler,
|
|
@@ -26506,6 +26589,36 @@ async function runUiSmoke(opts) {
|
|
|
26506
26589
|
});
|
|
26507
26590
|
continue;
|
|
26508
26591
|
}
|
|
26592
|
+
const closed = closedDialogOf(node);
|
|
26593
|
+
let opened = false;
|
|
26594
|
+
if (closed !== void 0) {
|
|
26595
|
+
const trigger = dialogTriggerOf(closed);
|
|
26596
|
+
if (trigger === void 0 || trigger.disabled === true) {
|
|
26597
|
+
notExercised.push({
|
|
26598
|
+
handler: interaction.handler,
|
|
26599
|
+
kind: interaction.kind,
|
|
26600
|
+
why: `it sits in a closed dialog whose trigger the page drew ${trigger === void 0 ? "no button for" : "disabled"} (${interaction.label})`
|
|
26601
|
+
});
|
|
26602
|
+
continue;
|
|
26603
|
+
}
|
|
26604
|
+
press(dom.window, trigger);
|
|
26605
|
+
const openBy = Math.min(Date.now() + Math.min(perInteraction, 5e3), budgetUntil);
|
|
26606
|
+
let live = openDialogNodeFor(dom.window.document, interaction);
|
|
26607
|
+
while (live === void 0 && Date.now() < openBy) {
|
|
26608
|
+
await sleep(25);
|
|
26609
|
+
live = openDialogNodeFor(dom.window.document, interaction);
|
|
26610
|
+
}
|
|
26611
|
+
if (live === void 0) {
|
|
26612
|
+
notExercised.push({
|
|
26613
|
+
handler: interaction.handler,
|
|
26614
|
+
kind: interaction.kind,
|
|
26615
|
+
why: `it sits in a closed dialog, and pressing the dialog's trigger drew no ${interaction.kind} for it (${interaction.label})`
|
|
26616
|
+
});
|
|
26617
|
+
continue;
|
|
26618
|
+
}
|
|
26619
|
+
node = live;
|
|
26620
|
+
opened = true;
|
|
26621
|
+
}
|
|
26509
26622
|
if (node.disabled === true) {
|
|
26510
26623
|
notExercised.push({
|
|
26511
26624
|
handler: interaction.handler,
|
|
@@ -26545,6 +26658,13 @@ async function runUiSmoke(opts) {
|
|
|
26545
26658
|
);
|
|
26546
26659
|
outcome.problems.push(...notes);
|
|
26547
26660
|
outcomes2.push(outcome);
|
|
26661
|
+
if (opened) {
|
|
26662
|
+
for (const box of Array.from(dom.window.document.querySelectorAll('[role="dialog"]'))) {
|
|
26663
|
+
const cancel = box.querySelector('[data-test="dialogCancel"]');
|
|
26664
|
+
if (cancel instanceof dom.window.HTMLButtonElement) cancel.click();
|
|
26665
|
+
}
|
|
26666
|
+
await settle(Math.min(Date.now() + graceMs, budgetUntil), calls.length);
|
|
26667
|
+
}
|
|
26548
26668
|
}
|
|
26549
26669
|
}
|
|
26550
26670
|
if (canvasContexts.size > 0) caveats.push(canvasCaveat([...canvasContexts]));
|
|
@@ -29989,6 +30109,38 @@ ${METRIC_ADVICE}
|
|
|
29989
30109
|
);
|
|
29990
30110
|
}
|
|
29991
30111
|
|
|
30112
|
+
// src/commands/appspace/tables.ts
|
|
30113
|
+
function renderTables(rows2) {
|
|
30114
|
+
if (rows2.length === 0) return "No app owns a table you can see.\n";
|
|
30115
|
+
return rows2.map(
|
|
30116
|
+
(r) => `${r.table} (owner ${r.owner_app}${r.stage ? `, ${r.stage}` : ""})
|
|
30117
|
+
columns: ${columnsLine(r.columns)}
|
|
30118
|
+
to share: "connect_tables": ${JSON.stringify(r.connect_tables)}
|
|
30119
|
+
`
|
|
30120
|
+
).join("");
|
|
30121
|
+
}
|
|
30122
|
+
function registerTablesCommand(parent) {
|
|
30123
|
+
withJsonOption(
|
|
30124
|
+
withServerOptions(
|
|
30125
|
+
parent.command("tables").description(
|
|
30126
|
+
"List the tables this tenant's apps own, with their columns and owner app (to share one, connect to it)."
|
|
30127
|
+
)
|
|
30128
|
+
)
|
|
30129
|
+
).action(async (opts) => {
|
|
30130
|
+
const client = await connect(opts);
|
|
30131
|
+
try {
|
|
30132
|
+
const rows2 = await client.request("/apps/tables");
|
|
30133
|
+
if (opts.json === true) {
|
|
30134
|
+
printJson(rows2);
|
|
30135
|
+
return;
|
|
30136
|
+
}
|
|
30137
|
+
process.stdout.write(renderTables(rows2));
|
|
30138
|
+
} finally {
|
|
30139
|
+
client.cleanup();
|
|
30140
|
+
}
|
|
30141
|
+
});
|
|
30142
|
+
}
|
|
30143
|
+
|
|
29992
30144
|
// src/commands/appspace/whoami.ts
|
|
29993
30145
|
function registerWhoamiCommand2(parent) {
|
|
29994
30146
|
withJsonOption(
|
|
@@ -30027,6 +30179,7 @@ function registerAppSpaceCommand(root) {
|
|
|
30027
30179
|
const cmd = root.command("app-space").description("Build and deploy App Space apps.");
|
|
30028
30180
|
registerWhoamiCommand2(cmd);
|
|
30029
30181
|
registerListCommand(cmd);
|
|
30182
|
+
registerTablesCommand(cmd);
|
|
30030
30183
|
registerScaffoldCommand(cmd);
|
|
30031
30184
|
registerGetCommand(cmd);
|
|
30032
30185
|
registerLogsCommand(cmd);
|