@datarails-ai/dr-appspace-cli 0.12.12 → 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 +99 -29
- 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);
|
|
@@ -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) {
|
|
@@ -27392,7 +27437,7 @@ async function runCompile(dir, opts) {
|
|
|
27392
27437
|
if (plan.intruders.length > 0 || plan.strays.length > 0 || plan.edited.length > 0) {
|
|
27393
27438
|
report.failure = "would-overwrite";
|
|
27394
27439
|
await writeReport(dir, report);
|
|
27395
|
-
process.stderr.write(refusalOf(dir, plan));
|
|
27440
|
+
process.stderr.write(refusalOf(dir, plan, opts.ontoLive === true));
|
|
27396
27441
|
return 1;
|
|
27397
27442
|
}
|
|
27398
27443
|
const check5 = opts.check === true;
|
|
@@ -27731,12 +27776,13 @@ function customHandlerPaths(contract) {
|
|
|
27731
27776
|
}
|
|
27732
27777
|
return out;
|
|
27733
27778
|
}
|
|
27734
|
-
function refusalOf(dir, plan) {
|
|
27779
|
+
function refusalOf(dir, plan, ontoLive) {
|
|
27735
27780
|
const list3 = (paths) => `${paths.slice(0, 3).join(", ")}${paths.length > 3 ? "\u2026" : ""}`;
|
|
27736
27781
|
let out = "";
|
|
27737
27782
|
if (plan.intruders.length > 0) {
|
|
27738
|
-
out += `${dir} already holds ${list3(plan.intruders)},
|
|
27739
|
-
|
|
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");
|
|
27740
27786
|
}
|
|
27741
27787
|
const handlers = plan.strays.filter((path3) => path3.startsWith("handlers/"));
|
|
27742
27788
|
const modules = plan.strays.filter((path3) => !path3.startsWith("handlers/"));
|
|
@@ -28049,6 +28095,12 @@ async function loadContract(dir, opts) {
|
|
|
28049
28095
|
return { contract, assemble: null, headline: resolveExpectations(opts.expect, contract, {}), overlay: {} };
|
|
28050
28096
|
}
|
|
28051
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
|
+
}
|
|
28052
28104
|
const assembled = assemble({
|
|
28053
28105
|
drafts: (await Promise.all(drafts.map(readNamed))).flatMap(pageDrafts),
|
|
28054
28106
|
...opts.skeleton === void 0 ? {} : { skeleton: await readNamed(opts.skeleton) },
|
|
@@ -28416,7 +28468,7 @@ function registerShipCommand(parent) {
|
|
|
28416
28468
|
withJsonOption(
|
|
28417
28469
|
withServerOptions(
|
|
28418
28470
|
withAssemblyOptions(
|
|
28419
|
-
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(
|
|
28420
28472
|
"--onto-live",
|
|
28421
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."
|
|
28422
28474
|
)
|
|
@@ -28451,6 +28503,11 @@ async function runShipOntoLive(dir, opts) {
|
|
|
28451
28503
|
if ((opts.expect ?? []).length > 0) {
|
|
28452
28504
|
return refuse("--expect holds the headline, which is the build's to confirm; a landing proves its own cards only.");
|
|
28453
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
|
+
}
|
|
28454
28511
|
const owned = await claimFolder(dir);
|
|
28455
28512
|
if (owned !== null) return refuse(owned);
|
|
28456
28513
|
await defaultServerFromSession(opts);
|
|
@@ -28462,7 +28519,20 @@ async function runShipOntoLive(dir, opts) {
|
|
|
28462
28519
|
const client = await connect(opts);
|
|
28463
28520
|
let pulled;
|
|
28464
28521
|
try {
|
|
28465
|
-
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 };
|
|
28466
28536
|
} finally {
|
|
28467
28537
|
client.cleanup();
|
|
28468
28538
|
}
|