@dashai/cli 0.12.1 → 0.13.0
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/bin.js +565 -3
- package/dist/bin.js.map +1 -1
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -428,6 +428,25 @@ async function getModuleBySlug(slug, options = {}) {
|
|
|
428
428
|
options
|
|
429
429
|
);
|
|
430
430
|
}
|
|
431
|
+
function isInstallGated(r) {
|
|
432
|
+
return r.install_gated === true;
|
|
433
|
+
}
|
|
434
|
+
async function installModule(moduleId, body, options = {}) {
|
|
435
|
+
return apiRequest(
|
|
436
|
+
"POST",
|
|
437
|
+
`/api/modules/${moduleId}/install`,
|
|
438
|
+
body,
|
|
439
|
+
options
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
async function listWorkspaceInstallations(workspaceId, options = {}) {
|
|
443
|
+
return apiRequest(
|
|
444
|
+
"GET",
|
|
445
|
+
`/api/modules/workspaces/${workspaceId}/installations`,
|
|
446
|
+
void 0,
|
|
447
|
+
options
|
|
448
|
+
);
|
|
449
|
+
}
|
|
431
450
|
async function applyDraftManifest(moduleId, manifest, options = {}) {
|
|
432
451
|
return apiRequest(
|
|
433
452
|
"PUT",
|
|
@@ -5200,6 +5219,386 @@ async function moduleDiffCommand(options) {
|
|
|
5200
5219
|
// src/bin.ts
|
|
5201
5220
|
init_init();
|
|
5202
5221
|
|
|
5222
|
+
// src/commands/module/install.ts
|
|
5223
|
+
init_api_client();
|
|
5224
|
+
init_config();
|
|
5225
|
+
init_manifest_io();
|
|
5226
|
+
|
|
5227
|
+
// src/lib/module-lifecycle.ts
|
|
5228
|
+
init_api_client();
|
|
5229
|
+
init_output();
|
|
5230
|
+
init_workspaces();
|
|
5231
|
+
function isDevInstallKind(kind) {
|
|
5232
|
+
return kind === "local" || kind === "sandbox";
|
|
5233
|
+
}
|
|
5234
|
+
function isRealRuntimeUrl(url) {
|
|
5235
|
+
return !!url && /^https:\/\//i.test(url);
|
|
5236
|
+
}
|
|
5237
|
+
function isDraftVersion(v) {
|
|
5238
|
+
return !v || v.includes("draft");
|
|
5239
|
+
}
|
|
5240
|
+
function compareSemver(a, b) {
|
|
5241
|
+
const parse = (v) => v.split("-")[0].split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
5242
|
+
const pa = parse(a);
|
|
5243
|
+
const pb = parse(b);
|
|
5244
|
+
for (let i = 0; i < 3; i++) {
|
|
5245
|
+
const d = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
5246
|
+
if (d !== 0) return d > 0 ? 1 : -1;
|
|
5247
|
+
}
|
|
5248
|
+
return 0;
|
|
5249
|
+
}
|
|
5250
|
+
function isUpdateAvailable(installedVersion, latestVersion) {
|
|
5251
|
+
return !!installedVersion && !!latestVersion && !isDraftVersion(installedVersion) && !isDraftVersion(latestVersion) && compareSemver(latestVersion, installedVersion) > 0;
|
|
5252
|
+
}
|
|
5253
|
+
async function resolveWorkspaceWithFallback(opts) {
|
|
5254
|
+
const fromCache = opts.userFlag === void 0 && opts.cachedWorkspaceId !== void 0;
|
|
5255
|
+
const flag = opts.userFlag ?? (fromCache ? String(opts.cachedWorkspaceId) : opts.configDefault);
|
|
5256
|
+
try {
|
|
5257
|
+
return await selectWorkspace({
|
|
5258
|
+
apiUrl: opts.apiUrl,
|
|
5259
|
+
apiToken: opts.apiToken,
|
|
5260
|
+
...flag !== void 0 ? { flag } : {},
|
|
5261
|
+
interactive: true
|
|
5262
|
+
});
|
|
5263
|
+
} catch (err) {
|
|
5264
|
+
if (!fromCache || err instanceof ApiError) throw err;
|
|
5265
|
+
info(
|
|
5266
|
+
`Cached dev-install workspace ${code(String(opts.cachedWorkspaceId))} is no longer accessible \u2014 pick a workspace.`
|
|
5267
|
+
);
|
|
5268
|
+
return await selectWorkspace({
|
|
5269
|
+
apiUrl: opts.apiUrl,
|
|
5270
|
+
apiToken: opts.apiToken,
|
|
5271
|
+
...opts.configDefault !== void 0 ? { flag: opts.configDefault } : {},
|
|
5272
|
+
interactive: true
|
|
5273
|
+
});
|
|
5274
|
+
}
|
|
5275
|
+
}
|
|
5276
|
+
|
|
5277
|
+
// src/commands/module/install.ts
|
|
5278
|
+
init_output();
|
|
5279
|
+
async function moduleInstallCommand(slugArg, options) {
|
|
5280
|
+
const slug = resolveSlug(slugArg, options.dir);
|
|
5281
|
+
if (!slug) {
|
|
5282
|
+
fail(
|
|
5283
|
+
`No module slug \u2014 pass one (${code("dashwise module install <slug>")}) or run from a directory with a valid module.json.`
|
|
5284
|
+
);
|
|
5285
|
+
return 1;
|
|
5286
|
+
}
|
|
5287
|
+
if (!isLoggedIn()) {
|
|
5288
|
+
fail(`Not logged in. Run ${code("dashwise login")} first.`);
|
|
5289
|
+
return 1;
|
|
5290
|
+
}
|
|
5291
|
+
const cfg = readConfig();
|
|
5292
|
+
if (!cfg) {
|
|
5293
|
+
fail(`CLI config missing. Run ${code("dashwise login")}.`);
|
|
5294
|
+
return 1;
|
|
5295
|
+
}
|
|
5296
|
+
const auth = { baseUrl: resolveApiUrl(), auth: { token: cfg.token } };
|
|
5297
|
+
let mod;
|
|
5298
|
+
try {
|
|
5299
|
+
mod = await getModuleBySlug(slug, auth);
|
|
5300
|
+
} catch (err) {
|
|
5301
|
+
if (err instanceof ApiError && err.status === 404) {
|
|
5302
|
+
fail(
|
|
5303
|
+
`Module ${code(slug)} not found on the backend. Run ${code("dashwise module register")} + ${code("dashwise module publish")} first.`
|
|
5304
|
+
);
|
|
5305
|
+
return 1;
|
|
5306
|
+
}
|
|
5307
|
+
fail(`Could not resolve module ${code(slug)}: ${err.message}`);
|
|
5308
|
+
return 1;
|
|
5309
|
+
}
|
|
5310
|
+
if (!mod.current_version) {
|
|
5311
|
+
fail(
|
|
5312
|
+
`Module ${code(slug)} has no published version yet \u2014 nothing to install. Run ${code("dashwise module publish")} first.`
|
|
5313
|
+
);
|
|
5314
|
+
return 1;
|
|
5315
|
+
}
|
|
5316
|
+
const targetVersion = options.version ?? mod.current_version;
|
|
5317
|
+
let workspace2;
|
|
5318
|
+
try {
|
|
5319
|
+
workspace2 = await resolveWorkspaceWithFallback({
|
|
5320
|
+
userFlag: options.workspace,
|
|
5321
|
+
cachedWorkspaceId: readModuleEntry(slug)?.workspaceId,
|
|
5322
|
+
configDefault: cfg.workspaceSlug,
|
|
5323
|
+
apiUrl: resolveApiUrl(),
|
|
5324
|
+
apiToken: cfg.token
|
|
5325
|
+
});
|
|
5326
|
+
} catch (err) {
|
|
5327
|
+
fail(`Workspace selection failed: ${err.message}`);
|
|
5328
|
+
return 1;
|
|
5329
|
+
}
|
|
5330
|
+
let existing;
|
|
5331
|
+
try {
|
|
5332
|
+
const installs = await listWorkspaceInstallations(workspace2.id, auth);
|
|
5333
|
+
existing = installs.find((row2) => row2.module_id === mod.id);
|
|
5334
|
+
} catch (err) {
|
|
5335
|
+
fail(
|
|
5336
|
+
`Could not read workspace installations: ${err.message}`
|
|
5337
|
+
);
|
|
5338
|
+
return 1;
|
|
5339
|
+
}
|
|
5340
|
+
let isUpgrade = false;
|
|
5341
|
+
let devInstallToReplace;
|
|
5342
|
+
let confirmedViaPicker = false;
|
|
5343
|
+
if (existing && isDevInstallKind(existing.kind)) {
|
|
5344
|
+
const resolution = await resolveDevInstallCollision(
|
|
5345
|
+
existing,
|
|
5346
|
+
slug,
|
|
5347
|
+
workspace2.slug,
|
|
5348
|
+
options
|
|
5349
|
+
);
|
|
5350
|
+
if (typeof resolution === "number") return resolution;
|
|
5351
|
+
devInstallToReplace = existing;
|
|
5352
|
+
confirmedViaPicker = resolution === "replace-confirmed";
|
|
5353
|
+
existing = void 0;
|
|
5354
|
+
} else if (existing) {
|
|
5355
|
+
isUpgrade = true;
|
|
5356
|
+
const installedVersion2 = existing.module_version?.version;
|
|
5357
|
+
if (installedVersion2 && installedVersion2 === targetVersion) {
|
|
5358
|
+
info(
|
|
5359
|
+
`Already deployed at ${code(`v${installedVersion2}`)} \u2014 re-running is idempotent and repairs a failed provision.`
|
|
5360
|
+
);
|
|
5361
|
+
} else {
|
|
5362
|
+
info(
|
|
5363
|
+
`Existing production install #${existing.id}${installedVersion2 ? ` at ${code(`v${installedVersion2}`)}` : ""} \u2014 this will upgrade it to ${code(`v${targetVersion}`)}.`
|
|
5364
|
+
);
|
|
5365
|
+
}
|
|
5366
|
+
}
|
|
5367
|
+
if (!options.yes && process.stdout.isTTY && !confirmedViaPicker) {
|
|
5368
|
+
const message = devInstallToReplace ? `Replace the dev install (revokes local-dev credentials) and install ${code(mod.slug)} ${code(`v${targetVersion}`)} into ${code(workspace2.slug)} as production?` : `${isUpgrade ? "Upgrade" : "Install"} ${code(mod.slug)} ${code(`v${targetVersion}`)} into workspace ${code(workspace2.slug)}?`;
|
|
5369
|
+
const answer = await select({
|
|
5370
|
+
message,
|
|
5371
|
+
options: [
|
|
5372
|
+
{
|
|
5373
|
+
value: "go",
|
|
5374
|
+
label: devInstallToReplace ? "Yes, replace the dev install + production-install" : isUpgrade ? "Yes, upgrade now" : "Yes, install now"
|
|
5375
|
+
},
|
|
5376
|
+
{ value: "cancel", label: "No, abort" }
|
|
5377
|
+
],
|
|
5378
|
+
initialValue: devInstallToReplace ? "cancel" : "go"
|
|
5379
|
+
});
|
|
5380
|
+
if (isCancel(answer) || answer === "cancel") {
|
|
5381
|
+
fail("Aborted. Nothing was changed.");
|
|
5382
|
+
return 130;
|
|
5383
|
+
}
|
|
5384
|
+
}
|
|
5385
|
+
let devInstallReplaced = false;
|
|
5386
|
+
if (devInstallToReplace) {
|
|
5387
|
+
const destroyed = await replaceDevInstall(devInstallToReplace, slug, auth);
|
|
5388
|
+
if (destroyed !== 0) return destroyed;
|
|
5389
|
+
devInstallReplaced = true;
|
|
5390
|
+
}
|
|
5391
|
+
info("Provisioning the runtime \u2014 this can take a minute\u2026");
|
|
5392
|
+
let result;
|
|
5393
|
+
try {
|
|
5394
|
+
result = await installModule(
|
|
5395
|
+
mod.id,
|
|
5396
|
+
{
|
|
5397
|
+
workspaceId: workspace2.id,
|
|
5398
|
+
...options.version !== void 0 ? { version: options.version } : {},
|
|
5399
|
+
...options.force !== void 0 ? { force: options.force } : {}
|
|
5400
|
+
},
|
|
5401
|
+
auth
|
|
5402
|
+
);
|
|
5403
|
+
} catch (err) {
|
|
5404
|
+
return surfaceInstallError(err, mod.slug, workspace2.slug, devInstallReplaced);
|
|
5405
|
+
}
|
|
5406
|
+
if (isInstallGated(result)) {
|
|
5407
|
+
if (options.json) {
|
|
5408
|
+
out(JSON.stringify(result));
|
|
5409
|
+
return 0;
|
|
5410
|
+
}
|
|
5411
|
+
success(
|
|
5412
|
+
`Install request ${code(`#${result.install_request.id}`)} queued (status: ${result.install_request.status}).`
|
|
5413
|
+
);
|
|
5414
|
+
info(
|
|
5415
|
+
`This module requires author approval before it installs. Once approved, check with ${code(`dashwise module status ${mod.slug} --workspace ${workspace2.slug}`)}.`
|
|
5416
|
+
);
|
|
5417
|
+
return 0;
|
|
5418
|
+
}
|
|
5419
|
+
let row;
|
|
5420
|
+
let verified = false;
|
|
5421
|
+
try {
|
|
5422
|
+
const after = await listWorkspaceInstallations(workspace2.id, auth);
|
|
5423
|
+
row = after.find((r) => r.module_id === mod.id);
|
|
5424
|
+
verified = row !== void 0;
|
|
5425
|
+
} catch {
|
|
5426
|
+
}
|
|
5427
|
+
const publicUrl = row?.public_url ?? null;
|
|
5428
|
+
const installedVersion = row?.module_version?.version ?? targetVersion;
|
|
5429
|
+
const runtimeProvisioned = isRealRuntimeUrl(row?.runtime_url);
|
|
5430
|
+
if (options.json) {
|
|
5431
|
+
out(
|
|
5432
|
+
JSON.stringify({
|
|
5433
|
+
installation_id: result.installation.id,
|
|
5434
|
+
kind: row?.kind ?? null,
|
|
5435
|
+
version: installedVersion,
|
|
5436
|
+
was_upgrade: result.was_upgrade,
|
|
5437
|
+
public_url: publicUrl,
|
|
5438
|
+
verified
|
|
5439
|
+
})
|
|
5440
|
+
);
|
|
5441
|
+
return 0;
|
|
5442
|
+
}
|
|
5443
|
+
if (!verified) {
|
|
5444
|
+
success(
|
|
5445
|
+
`Installed ${code(mod.slug)} ${code(`v${targetVersion}`)} (install ${code(`#${result.installation.id}`)}).`
|
|
5446
|
+
);
|
|
5447
|
+
info(
|
|
5448
|
+
`The verification read failed \u2014 run ${code(`dashwise module status ${mod.slug} --workspace ${workspace2.slug}`)} to see the deploy state and public URL.`
|
|
5449
|
+
);
|
|
5450
|
+
return 0;
|
|
5451
|
+
}
|
|
5452
|
+
if (runtimeProvisioned && publicUrl) {
|
|
5453
|
+
success(
|
|
5454
|
+
`Deployed ${code(mod.slug)} ${code(`v${installedVersion}`)} \u2014 live at ${code(publicUrl)}`
|
|
5455
|
+
);
|
|
5456
|
+
info(
|
|
5457
|
+
`Install ${code(`#${result.installation.id}`)} (kind=${row?.kind ?? "unknown"}) in workspace ${code(workspace2.slug)}${result.was_upgrade ? " \u2014 upgraded in place." : "."}`
|
|
5458
|
+
);
|
|
5459
|
+
} else if (runtimeProvisioned) {
|
|
5460
|
+
success(
|
|
5461
|
+
`Installed ${code(mod.slug)} ${code(`v${installedVersion}`)} (install ${code(`#${result.installation.id}`)}, kind=${row?.kind ?? "unknown"}).`
|
|
5462
|
+
);
|
|
5463
|
+
warn(
|
|
5464
|
+
`The runtime is provisioned, but this platform exposes no public host \u2014 there is no public URL to print. Ask the operator to configure the public module host; re-running install won't change this.`
|
|
5465
|
+
);
|
|
5466
|
+
} else {
|
|
5467
|
+
success(
|
|
5468
|
+
`Installed ${code(mod.slug)} ${code(`v${installedVersion}`)} (install ${code(`#${result.installation.id}`)}, kind=${row?.kind ?? "unknown"}).`
|
|
5469
|
+
);
|
|
5470
|
+
warn(
|
|
5471
|
+
`No public URL yet \u2014 runtime provisioning didn't complete. Re-running ${code("dashwise module install")} is idempotent and repairs a failed provision.`
|
|
5472
|
+
);
|
|
5473
|
+
}
|
|
5474
|
+
return 0;
|
|
5475
|
+
}
|
|
5476
|
+
function resolveSlug(slugArg, dir) {
|
|
5477
|
+
if (slugArg) return slugArg;
|
|
5478
|
+
try {
|
|
5479
|
+
const manifest = readManifest(dir);
|
|
5480
|
+
const slug = manifest?.module?.slug;
|
|
5481
|
+
return typeof slug === "string" && slug.length > 0 ? slug : void 0;
|
|
5482
|
+
} catch {
|
|
5483
|
+
return void 0;
|
|
5484
|
+
}
|
|
5485
|
+
}
|
|
5486
|
+
async function resolveDevInstallCollision(existing, slug, workspaceSlug, options) {
|
|
5487
|
+
if (existing.kind === "sandbox") {
|
|
5488
|
+
fail(
|
|
5489
|
+
`Workspace ${code(workspaceSlug)} already has a ${code("kind=sandbox")} install of ${code(slug)} (id=${existing.id}), and only ONE install per (workspace, module) can exist.
|
|
5490
|
+
A plain install would upgrade that row in place \u2014 but ${code("kind")} is immutable, so it would stay sandbox and hidden from the app sidebar.
|
|
5491
|
+
The CLI can't destroy a sandbox install. Uninstall it from the dashboard first, or deploy with ${code("--workspace <other>")} to a separate workspace.`
|
|
5492
|
+
);
|
|
5493
|
+
return 1;
|
|
5494
|
+
}
|
|
5495
|
+
const explanation = `Workspace ${code(workspaceSlug)} already has the DEV install of ${code(slug)} (kind=local, id=${existing.id}) created by ${code("dashwise init")}.
|
|
5496
|
+
Only ONE install per (workspace, module) can exist, and ${code("kind")} is immutable: a plain install would upgrade that row in place \u2014 the runtime would provision, but it would STAY a dev install, hidden from the app sidebar.
|
|
5497
|
+
Options:
|
|
5498
|
+
- ${code("--replace-dev-install")} \u2014 destroy the dev install, then fresh production install. Recommended when development is done. Note: this revokes the module's cached local-dev credentials, so local ${code("dashwise dev")} against this workspace needs a fresh ${code("dashwise init --force")} afterwards (which in turn cannot coexist with the production install here).
|
|
5499
|
+
- ${code("--workspace <other>")} \u2014 deploy to a separate test/client workspace and keep this dev install. Recommended for ongoing development.`;
|
|
5500
|
+
if (options.replaceDevInstall) return "replace";
|
|
5501
|
+
if (process.stdout.isTTY && !options.yes) {
|
|
5502
|
+
info(explanation);
|
|
5503
|
+
const answer = await select({
|
|
5504
|
+
message: `Replace the dev install of ${code(slug)} in ${code(workspaceSlug)} with a production install? (revokes local-dev credentials)`,
|
|
5505
|
+
options: [
|
|
5506
|
+
{
|
|
5507
|
+
value: "replace",
|
|
5508
|
+
label: "Replace \u2014 destroy the dev install, then production-install",
|
|
5509
|
+
hint: "local `dashwise dev` here will need `dashwise init --force` afterwards"
|
|
5510
|
+
},
|
|
5511
|
+
{ value: "cancel", label: "Cancel \u2014 I will deploy to another workspace" }
|
|
5512
|
+
],
|
|
5513
|
+
initialValue: "cancel"
|
|
5514
|
+
});
|
|
5515
|
+
if (isCancel(answer) || answer === "cancel") {
|
|
5516
|
+
fail("Aborted. Nothing was changed.");
|
|
5517
|
+
return 130;
|
|
5518
|
+
}
|
|
5519
|
+
return "replace-confirmed";
|
|
5520
|
+
}
|
|
5521
|
+
fail(explanation);
|
|
5522
|
+
return 1;
|
|
5523
|
+
}
|
|
5524
|
+
async function replaceDevInstall(existing, slug, auth) {
|
|
5525
|
+
info(`Destroying dev install ${code(`#${existing.id}`)} (kind=local)\u2026`);
|
|
5526
|
+
try {
|
|
5527
|
+
await destroyDevInstallation(existing.id, auth);
|
|
5528
|
+
} catch (err) {
|
|
5529
|
+
if (err instanceof ApiError && err.code === "INSTALLATION_NOT_FOUND") {
|
|
5530
|
+
warn("Dev install already gone server-side \u2014 continuing.");
|
|
5531
|
+
} else if (err instanceof ApiError) {
|
|
5532
|
+
fail(
|
|
5533
|
+
`Could not destroy the dev install (${err.code} ${err.status}): ${err.message}`
|
|
5534
|
+
);
|
|
5535
|
+
return 1;
|
|
5536
|
+
} else {
|
|
5537
|
+
fail(`Could not destroy the dev install: ${err.message}`);
|
|
5538
|
+
return 1;
|
|
5539
|
+
}
|
|
5540
|
+
}
|
|
5541
|
+
const entry = readModuleEntry(slug);
|
|
5542
|
+
if (entry && entry.installationId === existing.id) {
|
|
5543
|
+
try {
|
|
5544
|
+
removeModuleEntry(slug);
|
|
5545
|
+
info(
|
|
5546
|
+
`Removed ${code(slug)} from auth.json \u2014 its local-dev credentials are revoked. Local ${code("dashwise dev")} needs ${code("dashwise init --force")} (ideally against a different workspace).`
|
|
5547
|
+
);
|
|
5548
|
+
} catch (err) {
|
|
5549
|
+
warn(
|
|
5550
|
+
`Dev install destroyed but auth.json cleanup failed: ${err.message}. Remove the ${code(`modules["${slug}"]`)} entry from ${code("~/.config/dashwise/auth.json")} manually.`
|
|
5551
|
+
);
|
|
5552
|
+
}
|
|
5553
|
+
}
|
|
5554
|
+
return 0;
|
|
5555
|
+
}
|
|
5556
|
+
function surfaceInstallError(err, slug, workspaceSlug, devInstallReplaced) {
|
|
5557
|
+
if (err instanceof ApiError) {
|
|
5558
|
+
switch (err.code) {
|
|
5559
|
+
case "INSTALL_NOT_INVITED":
|
|
5560
|
+
fail(
|
|
5561
|
+
`Module ${code(slug)} is invite-only and workspace ${code(workspaceSlug)} hasn't been invited.
|
|
5562
|
+
Ask the module's author to invite this workspace (or open the install gate), then retry.`
|
|
5563
|
+
);
|
|
5564
|
+
break;
|
|
5565
|
+
case "UPGRADE_BREAKS_CONSUMERS":
|
|
5566
|
+
fail(
|
|
5567
|
+
`Upgrading ${code(slug)} would break modules that consume its current schema.
|
|
5568
|
+
Review the breaking changes, then re-run with ${code("--force")} to upgrade anyway.`
|
|
5569
|
+
);
|
|
5570
|
+
break;
|
|
5571
|
+
case "FORBIDDEN":
|
|
5572
|
+
fail(
|
|
5573
|
+
`You must be an ADMIN of workspace ${code(workspaceSlug)} to install modules into it.`
|
|
5574
|
+
);
|
|
5575
|
+
break;
|
|
5576
|
+
case "UNAUTHENTICATED":
|
|
5577
|
+
fail(
|
|
5578
|
+
`Auth rejected by the backend \u2014 your CLI token may be stale. Re-run ${code("dashwise login")} and try again.`
|
|
5579
|
+
);
|
|
5580
|
+
break;
|
|
5581
|
+
default:
|
|
5582
|
+
if (err.status === 400 && /publish/i.test(err.message)) {
|
|
5583
|
+
fail(
|
|
5584
|
+
`${err.message}
|
|
5585
|
+
Run ${code("dashwise module publish")} first, then retry.`
|
|
5586
|
+
);
|
|
5587
|
+
} else {
|
|
5588
|
+
fail(`Install failed (${err.code || err.status}): ${err.message}`);
|
|
5589
|
+
}
|
|
5590
|
+
}
|
|
5591
|
+
} else {
|
|
5592
|
+
fail(`Install failed: ${err.message}`);
|
|
5593
|
+
}
|
|
5594
|
+
if (devInstallReplaced) {
|
|
5595
|
+
warn(
|
|
5596
|
+
`Note: the dev install was already removed (that is not rolled back). Re-run ${code("dashwise module install")} to complete the production install, or ${code("dashwise init --force")} to restore local dev.`
|
|
5597
|
+
);
|
|
5598
|
+
}
|
|
5599
|
+
return 1;
|
|
5600
|
+
}
|
|
5601
|
+
|
|
5203
5602
|
// src/commands/module/list.ts
|
|
5204
5603
|
init_api_client();
|
|
5205
5604
|
init_config();
|
|
@@ -5668,6 +6067,140 @@ function formatBytes2(n) {
|
|
|
5668
6067
|
return `${(n / 1024 / 1024).toFixed(1)}MB`;
|
|
5669
6068
|
}
|
|
5670
6069
|
|
|
6070
|
+
// src/commands/module/status.ts
|
|
6071
|
+
init_api_client();
|
|
6072
|
+
init_config();
|
|
6073
|
+
init_manifest_io();
|
|
6074
|
+
init_output();
|
|
6075
|
+
async function moduleStatusCommand(slugArg, options) {
|
|
6076
|
+
let slug = slugArg;
|
|
6077
|
+
if (!slug) {
|
|
6078
|
+
try {
|
|
6079
|
+
const manifest = readManifest(options.dir);
|
|
6080
|
+
const fromManifest = manifest?.module?.slug;
|
|
6081
|
+
if (typeof fromManifest === "string" && fromManifest.length > 0) {
|
|
6082
|
+
slug = fromManifest;
|
|
6083
|
+
}
|
|
6084
|
+
} catch {
|
|
6085
|
+
}
|
|
6086
|
+
}
|
|
6087
|
+
if (!slug) {
|
|
6088
|
+
fail(
|
|
6089
|
+
`No module slug \u2014 pass one (${code("dashwise module status <slug>")}) or run from a directory with a valid module.json.`
|
|
6090
|
+
);
|
|
6091
|
+
return 1;
|
|
6092
|
+
}
|
|
6093
|
+
if (!isLoggedIn()) {
|
|
6094
|
+
fail(`Not logged in. Run ${code("dashwise login")} first.`);
|
|
6095
|
+
return 1;
|
|
6096
|
+
}
|
|
6097
|
+
const cfg = readConfig();
|
|
6098
|
+
if (!cfg) {
|
|
6099
|
+
fail(`CLI config missing. Run ${code("dashwise login")}.`);
|
|
6100
|
+
return 1;
|
|
6101
|
+
}
|
|
6102
|
+
const auth = { baseUrl: resolveApiUrl(), auth: { token: cfg.token } };
|
|
6103
|
+
let mod;
|
|
6104
|
+
try {
|
|
6105
|
+
mod = await getModuleBySlug(slug, auth);
|
|
6106
|
+
} catch (err) {
|
|
6107
|
+
if (err instanceof ApiError && err.status === 404) {
|
|
6108
|
+
fail(`Module ${code(slug)} not found on the backend.`);
|
|
6109
|
+
return 1;
|
|
6110
|
+
}
|
|
6111
|
+
fail(`Could not resolve module ${code(slug)}: ${err.message}`);
|
|
6112
|
+
return 1;
|
|
6113
|
+
}
|
|
6114
|
+
let workspace2;
|
|
6115
|
+
try {
|
|
6116
|
+
workspace2 = await resolveWorkspaceWithFallback({
|
|
6117
|
+
userFlag: options.workspace,
|
|
6118
|
+
cachedWorkspaceId: readModuleEntry(slug)?.workspaceId,
|
|
6119
|
+
configDefault: cfg.workspaceSlug,
|
|
6120
|
+
apiUrl: resolveApiUrl(),
|
|
6121
|
+
apiToken: cfg.token
|
|
6122
|
+
});
|
|
6123
|
+
} catch (err) {
|
|
6124
|
+
fail(`Workspace selection failed: ${err.message}`);
|
|
6125
|
+
return 1;
|
|
6126
|
+
}
|
|
6127
|
+
let row;
|
|
6128
|
+
try {
|
|
6129
|
+
const installs = await listWorkspaceInstallations(workspace2.id, auth);
|
|
6130
|
+
row = installs.find((r) => r.module_id === mod.id);
|
|
6131
|
+
} catch (err) {
|
|
6132
|
+
fail(`Could not read workspace installations: ${err.message}`);
|
|
6133
|
+
return 1;
|
|
6134
|
+
}
|
|
6135
|
+
const latestVersion = row?.module?.current_version ?? mod.current_version;
|
|
6136
|
+
if (!row) {
|
|
6137
|
+
if (options.json) {
|
|
6138
|
+
out(
|
|
6139
|
+
JSON.stringify({
|
|
6140
|
+
installed: false,
|
|
6141
|
+
module: mod.slug,
|
|
6142
|
+
workspace: { id: workspace2.id, slug: workspace2.slug },
|
|
6143
|
+
latest_version: latestVersion
|
|
6144
|
+
})
|
|
6145
|
+
);
|
|
6146
|
+
return 0;
|
|
6147
|
+
}
|
|
6148
|
+
info(
|
|
6149
|
+
`${code(mod.slug)} is not installed in workspace ${code(workspace2.slug)}.` + (latestVersion ? ` Deploy it with ${code(`dashwise module install ${mod.slug} --workspace ${workspace2.slug}`)}.` : ` It has no published version yet \u2014 run ${code("dashwise module publish")} first.`)
|
|
6150
|
+
);
|
|
6151
|
+
return 0;
|
|
6152
|
+
}
|
|
6153
|
+
const runtimeProvisioned = isRealRuntimeUrl(row.runtime_url);
|
|
6154
|
+
const state = runtimeProvisioned && row.public_url ? "live" : runtimeProvisioned ? "deployed" : "not_deployed";
|
|
6155
|
+
const installedVersion = row.module_version?.version ?? null;
|
|
6156
|
+
const updateAvailable = isUpdateAvailable(installedVersion, latestVersion);
|
|
6157
|
+
if (options.json) {
|
|
6158
|
+
out(
|
|
6159
|
+
JSON.stringify({
|
|
6160
|
+
installed: true,
|
|
6161
|
+
module: mod.slug,
|
|
6162
|
+
workspace: { id: workspace2.id, slug: workspace2.slug },
|
|
6163
|
+
installation_id: row.id,
|
|
6164
|
+
kind: row.kind ?? null,
|
|
6165
|
+
version: installedVersion,
|
|
6166
|
+
latest_version: latestVersion,
|
|
6167
|
+
update_available: updateAvailable,
|
|
6168
|
+
runtime_provisioned: runtimeProvisioned,
|
|
6169
|
+
runtime_url: row.runtime_url,
|
|
6170
|
+
public_url: row.public_url,
|
|
6171
|
+
state
|
|
6172
|
+
})
|
|
6173
|
+
);
|
|
6174
|
+
return 0;
|
|
6175
|
+
}
|
|
6176
|
+
const stateLabel = state === "live" ? "Live" : state === "deployed" ? "Deployed" : "Not deployed \u2014 runtime not provisioned";
|
|
6177
|
+
out("");
|
|
6178
|
+
kv("Module:", `${mod.slug} ${dim(`(id ${mod.id})`)}`);
|
|
6179
|
+
kv("Workspace:", `${workspace2.slug} ${dim(`(id ${workspace2.id})`)}`);
|
|
6180
|
+
kv("Install:", `#${row.id} ${dim(`(kind=${row.kind ?? "unknown"})`)}`);
|
|
6181
|
+
kv(
|
|
6182
|
+
"Version:",
|
|
6183
|
+
`${installedVersion ?? dim("(unknown)")}${updateAvailable ? ` ${code(`update available: v${latestVersion} \u2014 run \`dashwise module install ${mod.slug}\``)}` : latestVersion ? ` ${dim(`(latest published: v${latestVersion})`)}` : ""}`
|
|
6184
|
+
);
|
|
6185
|
+
kv(
|
|
6186
|
+
"Runtime:",
|
|
6187
|
+
runtimeProvisioned ? `provisioned ${dim(`(${row.runtime_url})`)}` : "not provisioned"
|
|
6188
|
+
);
|
|
6189
|
+
kv("Public URL:", row.public_url ?? dim("(none)"));
|
|
6190
|
+
kv("State:", stateLabel);
|
|
6191
|
+
out("");
|
|
6192
|
+
if (row.kind === "local") {
|
|
6193
|
+
info(
|
|
6194
|
+
`This is the ${code("kind=local")} dev install \u2014 hidden from the app sidebar. A production deploy needs ${code("dashwise module install --replace-dev-install")} (or ${code("--workspace <other>")}).`
|
|
6195
|
+
);
|
|
6196
|
+
} else if (state === "not_deployed") {
|
|
6197
|
+
info(
|
|
6198
|
+
`Re-run ${code(`dashwise module install ${mod.slug} --workspace ${workspace2.slug}`)} to (re-)provision the runtime \u2014 the install POST is idempotent.`
|
|
6199
|
+
);
|
|
6200
|
+
}
|
|
6201
|
+
return 0;
|
|
6202
|
+
}
|
|
6203
|
+
|
|
5671
6204
|
// src/commands/module/pull-data.ts
|
|
5672
6205
|
init_api_client();
|
|
5673
6206
|
init_output();
|
|
@@ -6289,7 +6822,7 @@ function rangeSatisfied(version, range) {
|
|
|
6289
6822
|
const op = opMatch[1] ?? "=";
|
|
6290
6823
|
const target = parseSemver(opMatch[2]);
|
|
6291
6824
|
if (!target) return false;
|
|
6292
|
-
const cmp =
|
|
6825
|
+
const cmp = compareSemver2(vParts, target);
|
|
6293
6826
|
switch (op) {
|
|
6294
6827
|
case "=":
|
|
6295
6828
|
return cmp === 0;
|
|
@@ -6318,7 +6851,7 @@ function parseSemver(s) {
|
|
|
6318
6851
|
if (!m) return null;
|
|
6319
6852
|
return [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
6320
6853
|
}
|
|
6321
|
-
function
|
|
6854
|
+
function compareSemver2(a, b) {
|
|
6322
6855
|
for (let i = 0; i < 3; i++) {
|
|
6323
6856
|
if (a[i] !== b[i]) return a[i] - b[i];
|
|
6324
6857
|
}
|
|
@@ -9204,6 +9737,35 @@ moduleCmd.command("publish").description("Pack the current directory + upload it
|
|
|
9204
9737
|
});
|
|
9205
9738
|
}
|
|
9206
9739
|
);
|
|
9740
|
+
moduleCmd.command("install [slug]").alias("deploy").description(
|
|
9741
|
+
"Install (deploy) a published module into a workspace \u2014 provisions the runtime and prints the public URL. Upgrades/repairs an existing production install idempotently. Refuses to silently land on a dev install: use --replace-dev-install or --workspace <other>."
|
|
9742
|
+
).option("--workspace <slug-or-id>", "Target workspace (default: cached module workspace, config default, or interactive picker).").option("--version <semver>", "Install a specific published version (default: latest).").option("--force", "Proceed even when the upgrade breaks consumer modules (UPGRADE_BREAKS_CONSUMERS).").option(
|
|
9743
|
+
"--replace-dev-install",
|
|
9744
|
+
"Destroy the workspace's kind=local dev install first, then fresh production install. Revokes the module's cached local-dev credentials (re-run `dashwise init --force` for local dev afterwards)."
|
|
9745
|
+
).option("--dir <path>", "Project root for the module.json slug lookup (default: current directory).").option("-y, --yes", "Skip the confirmation prompt (for CI / scripted use).").option("--json", "Emit the final result as machine-readable JSON.").action(
|
|
9746
|
+
async (slug, cmdOpts) => {
|
|
9747
|
+
process.exitCode = await moduleInstallCommand(slug, {
|
|
9748
|
+
...cmdOpts.workspace !== void 0 ? { workspace: cmdOpts.workspace } : {},
|
|
9749
|
+
...cmdOpts.version !== void 0 ? { version: cmdOpts.version } : {},
|
|
9750
|
+
...cmdOpts.force !== void 0 ? { force: cmdOpts.force } : {},
|
|
9751
|
+
...cmdOpts.replaceDevInstall !== void 0 ? { replaceDevInstall: cmdOpts.replaceDevInstall } : {},
|
|
9752
|
+
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
9753
|
+
...cmdOpts.yes !== void 0 ? { yes: cmdOpts.yes } : {},
|
|
9754
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
9755
|
+
});
|
|
9756
|
+
}
|
|
9757
|
+
);
|
|
9758
|
+
moduleCmd.command("status [slug]").description(
|
|
9759
|
+
"Show a module's deploy state in a workspace: install kind, installed vs latest published version, runtime provisioning, and the public URL."
|
|
9760
|
+
).option("--workspace <slug-or-id>", "Target workspace (default: cached module workspace, config default, or interactive picker).").option("--dir <path>", "Project root for the module.json slug lookup (default: current directory).").option("--json", "Emit machine-readable JSON.").action(
|
|
9761
|
+
async (slug, cmdOpts) => {
|
|
9762
|
+
process.exitCode = await moduleStatusCommand(slug, {
|
|
9763
|
+
...cmdOpts.workspace !== void 0 ? { workspace: cmdOpts.workspace } : {},
|
|
9764
|
+
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
9765
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
9766
|
+
});
|
|
9767
|
+
}
|
|
9768
|
+
);
|
|
9207
9769
|
moduleCmd.command("query-log").description(
|
|
9208
9770
|
"Read the per-module SDK query log. Default: 50 most recent entries. `--slow` filters to duration_ms > 500ms; `--since 1h` narrows by time; `--tail` streams new entries every 5s."
|
|
9209
9771
|
).option(
|
|
@@ -9381,7 +9943,7 @@ fieldCmd.command("set-type <table-slug> <field-slug> <new-type>").description(
|
|
|
9381
9943
|
}
|
|
9382
9944
|
})();
|
|
9383
9945
|
function getVersion() {
|
|
9384
|
-
return "0.
|
|
9946
|
+
return "0.13.0";
|
|
9385
9947
|
}
|
|
9386
9948
|
function parseIntOption(value) {
|
|
9387
9949
|
const n = Number.parseInt(value, 10);
|