@datarails-ai/dr-appspace-cli 0.12.11 → 0.12.13
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 +185 -37
- 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.
|
|
16
|
+
version: "0.12.13",
|
|
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-
|
|
60
|
+
var BUILD_TIME = "2026-10-15T06:27:08.313Z";
|
|
61
61
|
function formatBuildTime(iso) {
|
|
62
62
|
if (iso === "") return "";
|
|
63
63
|
const d = new Date(iso);
|
|
@@ -6435,7 +6435,7 @@ function endOfRegex(text3, start) {
|
|
|
6435
6435
|
// ../flows/insights-builder/kit/contract/extract.mjs
|
|
6436
6436
|
var CONTRACT_V = 1;
|
|
6437
6437
|
var GENERATED_BY = "dashboard-kit/contract@1";
|
|
6438
|
-
var KIT_REGIONS = /* @__PURE__ */ new Set(["header", "asof", "refresh", "nav", "filters", "controls", "band"]);
|
|
6438
|
+
var KIT_REGIONS = /* @__PURE__ */ new Set(["header", "asof", "refresh", "theme", "nav", "filters", "controls", "band"]);
|
|
6439
6439
|
var DASHBOARD_KIND = "dashboard";
|
|
6440
6440
|
var BAND_ROLE = "band";
|
|
6441
6441
|
var GRID_CLASSES = /* @__PURE__ */ new Set(["dr-dash", "dr-grid"]);
|
|
@@ -16373,18 +16373,31 @@ async function writePackage(dir, appId, pkg) {
|
|
|
16373
16373
|
) + "\n"
|
|
16374
16374
|
);
|
|
16375
16375
|
}
|
|
16376
|
-
async function
|
|
16376
|
+
async function readStoredContract(client, appId, codeVersion) {
|
|
16377
|
+
const app = `/apps/${encodeURIComponent(appId)}`;
|
|
16378
|
+
const path3 = typeof codeVersion === "number" ? `${app}/versions/${String(codeVersion)}/contract` : `${app}/contract`;
|
|
16377
16379
|
let stored;
|
|
16378
16380
|
try {
|
|
16379
|
-
stored = await client.request(
|
|
16380
|
-
} catch {
|
|
16381
|
-
|
|
16381
|
+
stored = await client.request(path3);
|
|
16382
|
+
} catch (err) {
|
|
16383
|
+
if (err instanceof AppSpaceHttpError) {
|
|
16384
|
+
const detail = err.body?.detail;
|
|
16385
|
+
return { missing: `${String(err.status)}${typeof detail === "string" ? ` ${detail}` : ""}` };
|
|
16386
|
+
}
|
|
16387
|
+
return { missing: err instanceof Error ? err.message.split("\n")[0] ?? "" : String(err) };
|
|
16388
|
+
}
|
|
16389
|
+
if (typeof stored !== "object" || stored === null || Array.isArray(stored)) {
|
|
16390
|
+
return { missing: "the contract read answered no document" };
|
|
16382
16391
|
}
|
|
16383
|
-
if (typeof stored !== "object" || stored === null || Array.isArray(stored)) return false;
|
|
16384
16392
|
const held = stored["contract"];
|
|
16385
16393
|
const document = typeof held === "object" && held !== null && !Array.isArray(held) ? held : stored;
|
|
16386
|
-
if (!("v" in document)) return
|
|
16387
|
-
|
|
16394
|
+
if (!("v" in document)) return { missing: "the contract read answered no document" };
|
|
16395
|
+
return { document };
|
|
16396
|
+
}
|
|
16397
|
+
async function writeStoredContract(client, appId, dir, codeVersion) {
|
|
16398
|
+
const read = await readStoredContract(client, appId, codeVersion);
|
|
16399
|
+
if (!("document" in read)) return false;
|
|
16400
|
+
await writeFile5(join10(dir, "contract.json"), JSON.stringify(read.document, null, 2) + "\n");
|
|
16388
16401
|
return true;
|
|
16389
16402
|
}
|
|
16390
16403
|
async function writeOwnedFiles(dir, document, { force = false } = {}) {
|
|
@@ -16443,20 +16456,52 @@ async function restoreOwnedFiles(dir, contractWritten, { force }) {
|
|
|
16443
16456
|
];
|
|
16444
16457
|
return writeOwnedFiles(dir, { owned }, { force });
|
|
16445
16458
|
}
|
|
16446
|
-
|
|
16447
|
-
|
|
16448
|
-
|
|
16449
|
-
|
|
16450
|
-
|
|
16451
|
-
|
|
16452
|
-
|
|
16459
|
+
var contractWait = { pulls: 7, baseMs: 250, capMs: 4e3 };
|
|
16460
|
+
function contractWaitMs(pull) {
|
|
16461
|
+
return Math.min(contractWait.capMs, contractWait.baseMs * 2 ** (pull - 1));
|
|
16462
|
+
}
|
|
16463
|
+
var LiveContractMissing = class extends Error {
|
|
16464
|
+
constructor(appId, revision, pulls, last) {
|
|
16465
|
+
super(
|
|
16466
|
+
`${appId} is live at revision ${String(revision)}, and its contract could not be read in ${String(pulls)} pulls (the last answered: ${last}). Without it this landing cannot see the cards already live: it would build the app again from the skeleton and replace every one of them. Nothing was landed. Run the same command again. If it fails the same way, the live version has no contract on record: \`app-space get ${appId} --dir <a new folder>\` and then \`app-space push <that folder>\` push the same package back with one, and the next landing builds on it.`
|
|
16467
|
+
);
|
|
16468
|
+
this.appId = appId;
|
|
16469
|
+
this.revision = revision;
|
|
16470
|
+
this.pulls = pulls;
|
|
16471
|
+
this.last = last;
|
|
16472
|
+
this.name = "LiveContractMissing";
|
|
16473
|
+
}
|
|
16474
|
+
appId;
|
|
16475
|
+
revision;
|
|
16476
|
+
pulls;
|
|
16477
|
+
last;
|
|
16478
|
+
};
|
|
16479
|
+
async function pullInto(client, appId, dir, {
|
|
16480
|
+
onWait,
|
|
16481
|
+
sleep: sleep2 = (ms) => new Promise((done) => setTimeout(done, ms))
|
|
16482
|
+
} = {}) {
|
|
16483
|
+
for (let pull = 1; ; pull += 1) {
|
|
16484
|
+
let pkg;
|
|
16485
|
+
try {
|
|
16486
|
+
pkg = await client.request(`/apps/${encodeURIComponent(appId)}/package`);
|
|
16487
|
+
} catch (err) {
|
|
16488
|
+
if (err instanceof AppSpaceHttpError && err.status === 404) return null;
|
|
16489
|
+
throw err;
|
|
16490
|
+
}
|
|
16491
|
+
await rm2(join10(dir, "contract.json"), { force: true });
|
|
16492
|
+
await writePackage(dir, appId, pkg);
|
|
16493
|
+
const read = await readStoredContract(client, appId, pkg.code_version);
|
|
16494
|
+
if ("document" in read) {
|
|
16495
|
+
await writeFile5(join10(dir, "contract.json"), JSON.stringify(read.document, null, 2) + "\n");
|
|
16496
|
+
await restoreOwnedFiles(dir, true, { force: true });
|
|
16497
|
+
await recordPulled(dir, true);
|
|
16498
|
+
return { revision: pkg.revision, stage: pkg.stage };
|
|
16499
|
+
}
|
|
16500
|
+
if (pull >= contractWait.pulls) throw new LiveContractMissing(appId, pkg.revision, pull, read.missing);
|
|
16501
|
+
const ms = contractWaitMs(pull);
|
|
16502
|
+
onWait?.(pull, ms, read.missing);
|
|
16503
|
+
await sleep2(ms);
|
|
16453
16504
|
}
|
|
16454
|
-
await rm2(join10(dir, "contract.json"), { force: true });
|
|
16455
|
-
await writePackage(dir, appId, pkg);
|
|
16456
|
-
const contract = await writeStoredContract(client, appId, dir);
|
|
16457
|
-
await restoreOwnedFiles(dir, contract, { force: true });
|
|
16458
|
-
await recordPulled(dir, contract);
|
|
16459
|
-
return { revision: pkg.revision, stage: pkg.stage };
|
|
16460
16505
|
}
|
|
16461
16506
|
async function runGet(appId, opts) {
|
|
16462
16507
|
const client = await connect(opts);
|
|
@@ -16473,7 +16518,7 @@ async function runGet(appId, opts) {
|
|
|
16473
16518
|
return 1;
|
|
16474
16519
|
}
|
|
16475
16520
|
await writePackage(dir, appId, pkg);
|
|
16476
|
-
const contract = await writeStoredContract(client, appId, dir);
|
|
16521
|
+
const contract = await writeStoredContract(client, appId, dir, pkg.code_version);
|
|
16477
16522
|
const restored = await restoreOwnedFiles(dir, contract, { force: opts.force === true });
|
|
16478
16523
|
await recordPulled(dir, contract);
|
|
16479
16524
|
if (opts.json === true) {
|
|
@@ -17261,7 +17306,7 @@ var contract_schema_default = {
|
|
|
17261
17306
|
properties: {
|
|
17262
17307
|
file: { type: "string" },
|
|
17263
17308
|
tokens: { type: "object", additionalProperties: { type: "string" } },
|
|
17264
|
-
preset: { type: "string", enum: ["default", "slate", "warm", "contrast", "presentation"], description: "OPTIONAL. A named look from the pinned ui-kit (presentation is refused: ui-kit/v4 shipped without it); absent means default. The compiler fills pages[].theme/density where a page states none from this and app.theme.density, and drops both keys from the filled contract it compares (mirrors fillHeaders)." },
|
|
17309
|
+
preset: { type: "string", enum: ["default", "slate", "warm", "contrast", "presentation"], description: "OPTIONAL. A named look from the pinned ui-kit (presentation is refused: ui-kit/v4 shipped without it); absent means default. A dashboard is built light: slate composes as default, because dark is the viewer's Dark mode switch (the theme region). The compiler fills pages[].theme/density where a page states none from this and app.theme.density, and drops both keys from the filled contract it compares (mirrors fillHeaders)." },
|
|
17265
17310
|
density: { type: "string", enum: ["comfortable", "compact"], description: "OPTIONAL. app.theme.density; absent means comfortable. Distinct from pages[].density, which is the compiled data-dr-density string." }
|
|
17266
17311
|
}
|
|
17267
17312
|
}
|
|
@@ -17277,7 +17322,7 @@ var contract_schema_default = {
|
|
|
17277
17322
|
module: { type: "string", description: "dr-widgets, dr-insights, dr-fmt, echarts, ui-kit" },
|
|
17278
17323
|
version: { type: "string", description: "1.1.1 for a vendor module, v3 for the ui-kit" },
|
|
17279
17324
|
file: { type: "string" },
|
|
17280
|
-
path: { type: "string", description: "the platform-root path, e.g. vendor/dr-widgets@1.2.
|
|
17325
|
+
path: { type: "string", description: "the platform-root path, e.g. vendor/dr-widgets@1.2.3/widgets.js" },
|
|
17281
17326
|
rel: { enum: ["script", "stylesheet"] },
|
|
17282
17327
|
pages: { type: "array", items: { type: "string" } },
|
|
17283
17328
|
order: { type: "integer", minimum: 0, description: "load order: where this module is first reached across the bundle" }
|
|
@@ -17326,7 +17371,7 @@ var contract_schema_default = {
|
|
|
17326
17371
|
}
|
|
17327
17372
|
},
|
|
17328
17373
|
scripts: { type: "array", items: { type: "string" }, description: "the bundle scripts this page loads" },
|
|
17329
|
-
theme: { type: ["string", "null"], description: "data-dr-theme: default,
|
|
17374
|
+
theme: { type: ["string", "null"], description: "data-dr-theme: default, warm, contrast. A dashboard is built light, so slate composes as default (dark is the viewer's Dark mode switch); a page read back from a dashboard built before that may still say slate." },
|
|
17330
17375
|
density: { type: ["string", "null"], description: "data-dr-density" },
|
|
17331
17376
|
mount: {
|
|
17332
17377
|
oneOf: [
|
|
@@ -17446,7 +17491,7 @@ var contract_schema_default = {
|
|
|
17446
17491
|
enum: ["region", "grid", "widget", "control", "nav", "tab", "slot", "form", "list", "detail", "queue", "action", "filter", "stat", "chart", "custom", "field", "number_input", "tabs", "tab_panel", "select", "date_picker", "data_grid", "dialog", "variance_table", "toggle", "control_group", "callout", "scenario_columns", "slider", "button", "status", "badge", "tag", "empty_state", "stepper", "alert", "checkbox", "radio_group", "file_upload", "skeleton", "progress", "accordion", "tooltip_info", "filter_bar", "filter_chip", "date_range", "fiscal_month", "search_input", "split_pane", "file_viewer", "line_grid", "document", "panel"],
|
|
17447
17492
|
description: "the dashboard vocabulary (region, grid, widget, control, nav, tab, slot) and the App Builder's component vocabulary (form, list, detail, queue, action, filter, the stat tile that reads one measure out of a summary handler, the chart that draws the rows one list, inbox or custom handler answers, the custom component a module under ui/components draws, and the field that sits inside one). widget is shared: on an app page it is a dashboard card too, drawn by dr-app-widgets in the kind data-dr-kind names, over the one widget payload the handler data-dr-source names answers"
|
|
17448
17493
|
},
|
|
17449
|
-
role: { type: ["string", "null"], description: "a region's data-dr-kit (header, asof, refresh, nav, filters, controls), a widget's kind, a control's kind, a slot's name. Null for a component: its kind IS its type, and the role it is meant for is props.role." },
|
|
17494
|
+
role: { type: ["string", "null"], description: "a region's data-dr-kit (header, asof, refresh, theme, nav, filters, controls), a widget's kind, a control's kind, a slot's name. Null for a component: its kind IS its type, and the role it is meant for is props.role." },
|
|
17450
17495
|
id: { type: ["string", "null"] },
|
|
17451
17496
|
tag: { type: ["string", "null"], description: "null for an element read out of a built bundle, which no longer says what it renders" },
|
|
17452
17497
|
classes: { type: "array", items: { type: "string" } },
|
|
@@ -19808,6 +19853,9 @@ import { fileURLToPath as fileURLToPath2 } from "url";
|
|
|
19808
19853
|
var THEME_FILE3 = "ui/theme.css";
|
|
19809
19854
|
var PRESETS2 = ["default", "slate", "warm", "contrast"];
|
|
19810
19855
|
var DEFAULT_PRESET = "default";
|
|
19856
|
+
var DARK_PRESET = "slate";
|
|
19857
|
+
var CANVAS_TOKENS = /* @__PURE__ */ new Set(["--dr-surface", "--dr-page", "--dr-border"]);
|
|
19858
|
+
var DARK_SURFACE_BELOW = 0.18;
|
|
19811
19859
|
var SPAN_BY_KIND = {
|
|
19812
19860
|
kpi: 3,
|
|
19813
19861
|
line: 6,
|
|
@@ -19890,13 +19938,21 @@ function composeLayout(contract) {
|
|
|
19890
19938
|
const app = isPlainObject7(out.app) ? out.app : out.app = {};
|
|
19891
19939
|
const pages = Array.isArray(out.pages) ? out.pages : out.pages = [];
|
|
19892
19940
|
const elements = isPlainObject7(out.elements) ? out.elements : out.elements = {};
|
|
19893
|
-
const
|
|
19941
|
+
const asked = presetOf(app, pages);
|
|
19942
|
+
const preset = asked === DARK_PRESET ? DEFAULT_PRESET : asked;
|
|
19894
19943
|
if (isPlainObject7(app.theme) && "preset" in app.theme) delete app.theme.preset;
|
|
19895
19944
|
for (const page of pages) {
|
|
19896
19945
|
const path3 = page.path ?? "ui/index.html";
|
|
19897
19946
|
if (page.theme === void 0 || page.theme === null) {
|
|
19898
19947
|
page.theme = preset;
|
|
19899
19948
|
record2("page.theme", path3, `${path3} opens on the "${preset}" theme`);
|
|
19949
|
+
} else if (page.theme === DARK_PRESET) {
|
|
19950
|
+
page.theme = DEFAULT_PRESET;
|
|
19951
|
+
record2(
|
|
19952
|
+
"page.theme",
|
|
19953
|
+
path3,
|
|
19954
|
+
`${path3} opens on the "${DEFAULT_PRESET}" theme: "${DARK_PRESET}" is the viewer's Dark mode switch, not a look a dashboard is built on`
|
|
19955
|
+
);
|
|
19900
19956
|
}
|
|
19901
19957
|
if (page.density === void 0) page.density = null;
|
|
19902
19958
|
if (page.title === void 0 || page.title === null) {
|
|
@@ -19971,6 +20027,21 @@ function composeTheme(app, preset, record2) {
|
|
|
19971
20027
|
const table = THEME_TOKENS[preset] ?? THEME_TOKENS[DEFAULT_PRESET];
|
|
19972
20028
|
const theme = isPlainObject7(app.theme) ? app.theme : {};
|
|
19973
20029
|
const tokens = isPlainObject7(theme.tokens) ? { ...theme.tokens } : {};
|
|
20030
|
+
const dark = THEME_TOKENS[DARK_PRESET];
|
|
20031
|
+
if (isDarkSurface(tokens["--dr-surface"], dark["--dr-surface"], tokens)) {
|
|
20032
|
+
let lightened = 0;
|
|
20033
|
+
for (const [token, value] of Object.entries(dark)) {
|
|
20034
|
+
if (table[token] === void 0 || tokens[token] === table[token]) continue;
|
|
20035
|
+
if (tokens[token] !== value && !CANVAS_TOKENS.has(token)) continue;
|
|
20036
|
+
tokens[token] = table[token];
|
|
20037
|
+
lightened += 1;
|
|
20038
|
+
}
|
|
20039
|
+
record2(
|
|
20040
|
+
"theme.tokens",
|
|
20041
|
+
THEME_FILE3,
|
|
20042
|
+
`${lightened} palette token(s) moved from the "${DARK_PRESET}" preset to "${preset}": a dashboard is built light, and dark is the viewer's Dark mode switch`
|
|
20043
|
+
);
|
|
20044
|
+
}
|
|
19974
20045
|
let filled = 0;
|
|
19975
20046
|
for (const [token, value] of Object.entries(table)) {
|
|
19976
20047
|
if (token in tokens) continue;
|
|
@@ -19986,6 +20057,21 @@ function composeTheme(app, preset, record2) {
|
|
|
19986
20057
|
);
|
|
19987
20058
|
}
|
|
19988
20059
|
}
|
|
20060
|
+
function isDarkSurface(value, darkSurface, tokens, seen = /* @__PURE__ */ new Set()) {
|
|
20061
|
+
if (typeof value !== "string") return false;
|
|
20062
|
+
const alias = /^var\(\s*(--[a-z0-9-]+)\s*(?:,[^)]*)?\)$/i.exec(value.trim());
|
|
20063
|
+
if (alias) {
|
|
20064
|
+
if (seen.has(alias[1])) return false;
|
|
20065
|
+
seen.add(alias[1]);
|
|
20066
|
+
return isDarkSurface(tokens[alias[1]], darkSurface, tokens, seen);
|
|
20067
|
+
}
|
|
20068
|
+
if (value === darkSurface) return true;
|
|
20069
|
+
try {
|
|
20070
|
+
return luminance(value) < DARK_SURFACE_BELOW;
|
|
20071
|
+
} catch {
|
|
20072
|
+
return false;
|
|
20073
|
+
}
|
|
20074
|
+
}
|
|
19989
20075
|
function presetOf(app, pages) {
|
|
19990
20076
|
const asked = isPlainObject7(app.theme) ? app.theme.preset : null;
|
|
19991
20077
|
if (typeof asked === "string" && PRESETS2.includes(asked)) return asked;
|
|
@@ -20371,6 +20457,9 @@ function appbar(page, elements) {
|
|
|
20371
20457
|
" </div>",
|
|
20372
20458
|
' <div class="dr-appbar__actions">',
|
|
20373
20459
|
` <button type="button" class="${classesOf2(elements, "region", "refresh", ["dr-btn", "dr-btn--secondary", "dr-btn--sm"])}" data-dr-kit="refresh">Refresh</button>`,
|
|
20460
|
+
// The viewer's light/dark switch: dr-widgets flips the body between this
|
|
20461
|
+
// page's own preset and slate, and remembers the choice per app.
|
|
20462
|
+
` <button type="button" class="${classesOf2(elements, "region", "theme", ["dr-btn", "dr-btn--secondary", "dr-btn--sm"])}" data-dr-kit="theme" aria-pressed="false">Dark mode</button>`,
|
|
20374
20463
|
" </div>",
|
|
20375
20464
|
"</header>"
|
|
20376
20465
|
];
|
|
@@ -27131,7 +27220,7 @@ var kit_default = {
|
|
|
27131
27220
|
fmt: "dr-fmt@2.0.1",
|
|
27132
27221
|
markdown: "dr-md@1.0.0",
|
|
27133
27222
|
uiKit: "ui-kit/v4",
|
|
27134
|
-
widgets: "dr-widgets@1.2.
|
|
27223
|
+
widgets: "dr-widgets@1.2.3"
|
|
27135
27224
|
};
|
|
27136
27225
|
|
|
27137
27226
|
// ../flows/appspace-builder/kit/kit.json
|
|
@@ -27348,7 +27437,7 @@ async function runCompile(dir, opts) {
|
|
|
27348
27437
|
if (plan.intruders.length > 0 || plan.strays.length > 0 || plan.edited.length > 0) {
|
|
27349
27438
|
report.failure = "would-overwrite";
|
|
27350
27439
|
await writeReport(dir, report);
|
|
27351
|
-
process.stderr.write(refusalOf(dir, plan));
|
|
27440
|
+
process.stderr.write(refusalOf(dir, plan, opts.ontoLive === true));
|
|
27352
27441
|
return 1;
|
|
27353
27442
|
}
|
|
27354
27443
|
const check5 = opts.check === true;
|
|
@@ -27687,12 +27776,13 @@ function customHandlerPaths(contract) {
|
|
|
27687
27776
|
}
|
|
27688
27777
|
return out;
|
|
27689
27778
|
}
|
|
27690
|
-
function refusalOf(dir, plan) {
|
|
27779
|
+
function refusalOf(dir, plan, ontoLive) {
|
|
27691
27780
|
const list3 = (paths) => `${paths.slice(0, 3).join(", ")}${paths.length > 3 ? "\u2026" : ""}`;
|
|
27692
27781
|
let out = "";
|
|
27693
27782
|
if (plan.intruders.length > 0) {
|
|
27694
|
-
out += `${dir} already holds ${list3(plan.intruders)},
|
|
27695
|
-
|
|
27783
|
+
out += `${dir} already holds ${list3(plan.intruders)}, ` + // `ship --onto-live` pulled these: they are the live app, and writing
|
|
27784
|
+
// over them is replacing other landings' cards. Never the way past it.
|
|
27785
|
+
(ontoLive ? "which this compiler did not write: they are the live app as pulled, and this landing could not build on them. Nothing was pushed. Run the same command again.\n" : "which this compiler did not write. Move them aside, or pass --force to write over them.\n");
|
|
27696
27786
|
}
|
|
27697
27787
|
const handlers = plan.strays.filter((path3) => path3.startsWith("handlers/"));
|
|
27698
27788
|
const modules = plan.strays.filter((path3) => !path3.startsWith("handlers/"));
|
|
@@ -27721,7 +27811,7 @@ function roundTripDiffs(input, extracted, compilerProblems = [], { order: order2
|
|
|
27721
27811
|
const stated2 = order2 === void 0 ? {} : { order: order2 };
|
|
27722
27812
|
const read = order2 === void 0 ? {} : { order: "keys" };
|
|
27723
27813
|
const left = stripPositional(normaliseApp(input), stated2);
|
|
27724
|
-
const right = stripPositional(normaliseApp(extracted), read);
|
|
27814
|
+
const right = stripPositional(normaliseApp(withoutUnstatedChrome(input, extracted)), read);
|
|
27725
27815
|
const declared = compilerProblems.some((problem5) => problem5.code === DECLARED_LOSS);
|
|
27726
27816
|
const diffs = [];
|
|
27727
27817
|
for (const section of ["app", "pages", "elements", "data"]) {
|
|
@@ -27746,6 +27836,40 @@ function roundTripDiffs(input, extracted, compilerProblems = [], { order: order2
|
|
|
27746
27836
|
}
|
|
27747
27837
|
return diffs.slice(0, 40);
|
|
27748
27838
|
}
|
|
27839
|
+
var ADDED_CHROME = /* @__PURE__ */ new Set(["theme"]);
|
|
27840
|
+
function withoutUnstatedChrome(input, extracted) {
|
|
27841
|
+
if (!isPlain(input) || !isPlain(extracted)) return extracted;
|
|
27842
|
+
const want = input["elements"];
|
|
27843
|
+
const got = extracted["elements"];
|
|
27844
|
+
if (!isPlain(want) || !isPlain(got)) return extracted;
|
|
27845
|
+
const regionOf = (element) => `${String(element["page"] ?? "")}|${String(element["role"] ?? "")}`;
|
|
27846
|
+
const statedRegions = new Set(
|
|
27847
|
+
Object.values(want).filter((element) => isPlain(element) && element["type"] === "region").map(regionOf)
|
|
27848
|
+
);
|
|
27849
|
+
const unstated = new Set(
|
|
27850
|
+
Object.entries(got).filter(([, element]) => {
|
|
27851
|
+
if (!isPlain(element) || element["type"] !== "region") return false;
|
|
27852
|
+
if (!ADDED_CHROME.has(String(element["role"] ?? ""))) return false;
|
|
27853
|
+
const children = element["children"];
|
|
27854
|
+
return !statedRegions.has(regionOf(element)) && (!Array.isArray(children) || children.length === 0);
|
|
27855
|
+
}).map(([uid]) => uid)
|
|
27856
|
+
);
|
|
27857
|
+
if (unstated.size === 0) return extracted;
|
|
27858
|
+
const out = JSON.parse(JSON.stringify(extracted));
|
|
27859
|
+
const elements = out["elements"];
|
|
27860
|
+
for (const uid of unstated) delete elements[uid];
|
|
27861
|
+
for (const element of Object.values(elements)) {
|
|
27862
|
+
if (isPlain(element) && Array.isArray(element["children"])) {
|
|
27863
|
+
element["children"] = element["children"].filter((uid) => !unstated.has(String(uid)));
|
|
27864
|
+
}
|
|
27865
|
+
}
|
|
27866
|
+
for (const page of Array.isArray(out["pages"]) ? out["pages"] : []) {
|
|
27867
|
+
if (isPlain(page) && Array.isArray(page["tree"])) {
|
|
27868
|
+
page["tree"] = page["tree"].filter((uid) => !unstated.has(String(uid)));
|
|
27869
|
+
}
|
|
27870
|
+
}
|
|
27871
|
+
return out;
|
|
27872
|
+
}
|
|
27749
27873
|
function normaliseApp(contract) {
|
|
27750
27874
|
if (typeof contract !== "object" || contract === null) return contract;
|
|
27751
27875
|
const out = JSON.parse(JSON.stringify(contract));
|
|
@@ -27971,6 +28095,12 @@ async function loadContract(dir, opts) {
|
|
|
27971
28095
|
return { contract, assemble: null, headline: resolveExpectations(opts.expect, contract, {}), overlay: {} };
|
|
27972
28096
|
}
|
|
27973
28097
|
const baseRaw = await readIfPresent(join15(dir, "contract.json"));
|
|
28098
|
+
if (opts.ontoLive === true && baseRaw === void 0 && await readIfPresent(join15(dir, "manifest.json")) !== void 0) {
|
|
28099
|
+
process.stderr.write(
|
|
28100
|
+
"ship --onto-live: the folder holds the live package but not its contract.json, so this landing cannot see the cards already live, and would replace them. Nothing was written. Run the same command again.\n"
|
|
28101
|
+
);
|
|
28102
|
+
return { exit: 1 };
|
|
28103
|
+
}
|
|
27974
28104
|
const assembled = assemble({
|
|
27975
28105
|
drafts: (await Promise.all(drafts.map(readNamed))).flatMap(pageDrafts),
|
|
27976
28106
|
...opts.skeleton === void 0 ? {} : { skeleton: await readNamed(opts.skeleton) },
|
|
@@ -28338,7 +28468,7 @@ function registerShipCommand(parent) {
|
|
|
28338
28468
|
withJsonOption(
|
|
28339
28469
|
withServerOptions(
|
|
28340
28470
|
withAssemblyOptions(
|
|
28341
|
-
parent.command("ship [dir]").description("Assemble, compile, push and prove a dashboard pass; print the update block the reply ends with.").option("--seed <file>", "Seed rows, as JSON. Forwarded to the push.").option("--force", "Write over files this compiler did not write (never a custom handler).").option(
|
|
28471
|
+
parent.command("ship [dir]").description("Assemble, compile, push and prove a dashboard pass; print the update block the reply ends with.").option("--seed <file>", "Seed rows, as JSON. Forwarded to the push.").option("--force", "Write over files this compiler did not write (never a custom handler). Refused with --onto-live.").option(
|
|
28342
28472
|
"--onto-live",
|
|
28343
28473
|
"Land this pass's cards on the app as it is live now, keeping every card already there: pull it, add them, push against its revision, and start over when another push lands first. Needs --app-id and --skeleton, and a folder of its own."
|
|
28344
28474
|
)
|
|
@@ -28373,6 +28503,11 @@ async function runShipOntoLive(dir, opts) {
|
|
|
28373
28503
|
if ((opts.expect ?? []).length > 0) {
|
|
28374
28504
|
return refuse("--expect holds the headline, which is the build's to confirm; a landing proves its own cards only.");
|
|
28375
28505
|
}
|
|
28506
|
+
if (opts.force === true) {
|
|
28507
|
+
return refuse(
|
|
28508
|
+
"does not take --force: every landing builds on the cards already live, and --force would let this one replace them. Run the same command without it. If it refuses over files it did not write, the live app could not be read whole: run it again, and report the refusal if it repeats."
|
|
28509
|
+
);
|
|
28510
|
+
}
|
|
28376
28511
|
const owned = await claimFolder(dir);
|
|
28377
28512
|
if (owned !== null) return refuse(owned);
|
|
28378
28513
|
await defaultServerFromSession(opts);
|
|
@@ -28384,7 +28519,20 @@ async function runShipOntoLive(dir, opts) {
|
|
|
28384
28519
|
const client = await connect(opts);
|
|
28385
28520
|
let pulled;
|
|
28386
28521
|
try {
|
|
28387
|
-
pulled = await pullInto(client, appId, dir
|
|
28522
|
+
pulled = await pullInto(client, appId, dir, {
|
|
28523
|
+
onWait: (pull, ms, missing) => {
|
|
28524
|
+
if (opts.json === true) return;
|
|
28525
|
+
process.stdout.write(
|
|
28526
|
+
`onto ${appId}: the live version's contract is not readable yet (${missing}) \u2014 pulling again in ${String(ms)} ms (pull ${String(pull + 1)})
|
|
28527
|
+
`
|
|
28528
|
+
);
|
|
28529
|
+
}
|
|
28530
|
+
});
|
|
28531
|
+
} catch (err) {
|
|
28532
|
+
if (!(err instanceof LiveContractMissing)) throw err;
|
|
28533
|
+
process.stderr.write(`ship --onto-live: ${err.message}
|
|
28534
|
+
`);
|
|
28535
|
+
return { code: 1, lost: null, creating: false };
|
|
28388
28536
|
} finally {
|
|
28389
28537
|
client.cleanup();
|
|
28390
28538
|
}
|