@dashai/cli 0.21.0 → 0.22.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/README.md +10 -0
- package/dist/bin.js +872 -11
- package/dist/bin.js.map +1 -1
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import pc from 'picocolors';
|
|
3
|
-
import { existsSync, readFileSync, writeFileSync, rmSync, mkdirSync, watch, readdirSync, chmodSync, renameSync, mkdtempSync, statSync } from 'fs';
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync, rmSync, mkdirSync, watch, readdirSync, chmodSync, renameSync, lstatSync, mkdtempSync, statSync } from 'fs';
|
|
4
4
|
import { hostname, tmpdir, platform, homedir } from 'os';
|
|
5
5
|
import { resolve, join, relative, dirname, basename, win32, sep } from 'path';
|
|
6
6
|
import { spawn, spawnSync } from 'child_process';
|
|
7
|
-
import { intro, log, select, isCancel, cancel, spinner, outro, text,
|
|
7
|
+
import { intro, log, select, isCancel, cancel, spinner, outro, text, confirm, multiselect } from '@clack/prompts';
|
|
8
8
|
import { Command, Option } from 'commander';
|
|
9
9
|
import open from 'open';
|
|
10
10
|
import AdmZip from 'adm-zip';
|
|
@@ -286,6 +286,15 @@ function asEnvelope(v) {
|
|
|
286
286
|
} else if (env.details && typeof env.details === "object") {
|
|
287
287
|
out2.context = env.details;
|
|
288
288
|
}
|
|
289
|
+
const folded = {};
|
|
290
|
+
for (const key of TOP_LEVEL_CONTEXT_KEYS) {
|
|
291
|
+
if (typeof env[key] === "string" && out2.context?.[key] === void 0) {
|
|
292
|
+
folded[key] = env[key];
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (Object.keys(folded).length > 0) {
|
|
296
|
+
out2.context = { ...out2.context ?? {}, ...folded };
|
|
297
|
+
}
|
|
289
298
|
return out2;
|
|
290
299
|
}
|
|
291
300
|
function codeFromStatus(status) {
|
|
@@ -587,7 +596,34 @@ async function getRuntimeLogs(installationId, opts = {}, options = {}) {
|
|
|
587
596
|
options
|
|
588
597
|
);
|
|
589
598
|
}
|
|
590
|
-
|
|
599
|
+
async function getInstallationRuntime(installationId, options = {}) {
|
|
600
|
+
return apiRequest(
|
|
601
|
+
"GET",
|
|
602
|
+
`/api/installations/${installationId}/runtime`,
|
|
603
|
+
void 0,
|
|
604
|
+
options
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
async function setInstallationRuntime(installationId, body, options = {}) {
|
|
608
|
+
return apiRequest(
|
|
609
|
+
"PATCH",
|
|
610
|
+
`/api/installations/${installationId}/runtime`,
|
|
611
|
+
body,
|
|
612
|
+
options
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
async function rebuildModule(moduleId, body, options = {}) {
|
|
616
|
+
return apiRequest("POST", `/api/modules/${moduleId}/rebuild`, body, options);
|
|
617
|
+
}
|
|
618
|
+
async function getModuleBuild(moduleId, buildId, options = {}) {
|
|
619
|
+
return apiRequest(
|
|
620
|
+
"GET",
|
|
621
|
+
`/api/modules/${moduleId}/builds/${buildId}`,
|
|
622
|
+
void 0,
|
|
623
|
+
options
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
var ApiError, CODE_SHAPE, TOP_LEVEL_CONTEXT_KEYS, slugifyFieldName;
|
|
591
627
|
var init_api_client = __esm({
|
|
592
628
|
"src/lib/api-client.ts"() {
|
|
593
629
|
init_config();
|
|
@@ -609,6 +645,7 @@ var init_api_client = __esm({
|
|
|
609
645
|
}
|
|
610
646
|
};
|
|
611
647
|
CODE_SHAPE = /^[A-Z][A-Z0-9_]*$/;
|
|
648
|
+
TOP_LEVEL_CONTEXT_KEYS = ["feature", "permission", "plane"];
|
|
612
649
|
slugifyFieldName = slugifyUsesName;
|
|
613
650
|
}
|
|
614
651
|
});
|
|
@@ -4364,6 +4401,17 @@ var init_scaffold = __esm({
|
|
|
4364
4401
|
MODULE_SLUG_REGEX = /^[a-z]([a-z0-9-]*[a-z0-9])?$/;
|
|
4365
4402
|
}
|
|
4366
4403
|
});
|
|
4404
|
+
function isFreshCloneEntry(name) {
|
|
4405
|
+
return name === ".git" || /^(readme|license|licence)(\..+)?$/i.test(name);
|
|
4406
|
+
}
|
|
4407
|
+
function entryExists(path) {
|
|
4408
|
+
try {
|
|
4409
|
+
lstatSync(path);
|
|
4410
|
+
return true;
|
|
4411
|
+
} catch {
|
|
4412
|
+
return false;
|
|
4413
|
+
}
|
|
4414
|
+
}
|
|
4367
4415
|
function resolveTargetDir(slug, dirFlag) {
|
|
4368
4416
|
if (dirFlag !== void 0) return { dir: resolve(dirFlag) };
|
|
4369
4417
|
const cwd = process.cwd();
|
|
@@ -4454,16 +4502,32 @@ async function moduleInitCommand(slugArg, options) {
|
|
|
4454
4502
|
);
|
|
4455
4503
|
return 1;
|
|
4456
4504
|
}
|
|
4505
|
+
let freshClone = false;
|
|
4506
|
+
let keptReadme = false;
|
|
4457
4507
|
if (!options.provisionOnly && existsSync(dir)) {
|
|
4458
4508
|
const entries = readdirSync(dir);
|
|
4459
4509
|
if (entries.length > 0 && !options.force && !manifestExists) {
|
|
4460
|
-
|
|
4461
|
-
|
|
4510
|
+
const linked = entries.filter(
|
|
4511
|
+
(name2) => isFreshCloneEntry(name2) && lstatSync(join(dir, name2)).isSymbolicLink()
|
|
4462
4512
|
);
|
|
4463
|
-
|
|
4513
|
+
if (linked.length > 0) {
|
|
4514
|
+
fail(
|
|
4515
|
+
`Directory ${code(dir)} holds ${linked.map((name2) => code(name2)).join(", ")} as a symbolic link. Replace it with a regular file, or pass ${code("--force")} to scaffold anyway (existing files will be overwritten).`
|
|
4516
|
+
);
|
|
4517
|
+
return 1;
|
|
4518
|
+
}
|
|
4519
|
+
if (entries.every(isFreshCloneEntry)) {
|
|
4520
|
+
freshClone = true;
|
|
4521
|
+
keptReadme = entries.some((name2) => /^readme/i.test(name2));
|
|
4522
|
+
} else {
|
|
4523
|
+
fail(
|
|
4524
|
+
`Directory ${code(dir)} is not empty. Pass ${code("--force")} to scaffold anyway (existing files will be overwritten).`
|
|
4525
|
+
);
|
|
4526
|
+
return 1;
|
|
4527
|
+
}
|
|
4464
4528
|
}
|
|
4465
4529
|
}
|
|
4466
|
-
const writeMode = options.provisionOnly ? "none" : manifestExists && !options.force ? "missing-only" : "all";
|
|
4530
|
+
const writeMode = options.provisionOnly ? "none" : (manifestExists || freshClone) && !options.force ? "missing-only" : "all";
|
|
4467
4531
|
if (options.simple && options.custom) {
|
|
4468
4532
|
fail(
|
|
4469
4533
|
`Cannot pass both ${code("--simple")} and ${code("--custom")} \u2014 they're mutually exclusive. ${code("--simple")} selects the legacy minimal scaffold; the default (no flag) is the full Next.js scaffold (formerly ${code("--custom")}).`
|
|
@@ -4578,7 +4642,7 @@ async function moduleInitCommand(slugArg, options) {
|
|
|
4578
4642
|
if (writeMode !== "none") {
|
|
4579
4643
|
for (const [relPath, content] of files) {
|
|
4580
4644
|
const absPath = join(dir, relPath);
|
|
4581
|
-
if (writeMode === "missing-only" &&
|
|
4645
|
+
if (writeMode === "missing-only" && (entryExists(absPath) || keptReadme && relPath === "README.md")) {
|
|
4582
4646
|
skipped++;
|
|
4583
4647
|
continue;
|
|
4584
4648
|
}
|
|
@@ -4594,6 +4658,10 @@ async function moduleInitCommand(slugArg, options) {
|
|
|
4594
4658
|
info(
|
|
4595
4659
|
`Existing scaffold in ${code(dir)} left untouched (provision-only) \u2014 re-provisioning the backend dev install.`
|
|
4596
4660
|
);
|
|
4661
|
+
} else if (freshClone) {
|
|
4662
|
+
success(
|
|
4663
|
+
`Scaffolded ${code(slug)} (${kind === "custom" ? "custom Next.js" : "simple module"}) into the clone in ${code(dir)}; ${code(".git")}, README and LICENSE left untouched.`
|
|
4664
|
+
);
|
|
4597
4665
|
} else if (writeMode === "missing-only") {
|
|
4598
4666
|
success(
|
|
4599
4667
|
`Existing module project in ${code(dir)} \u2014 wrote ${written} missing file(s), preserved ${skipped} existing file(s). ${dim("(Pass --force to overwrite everything.)")}`
|
|
@@ -5994,7 +6062,7 @@ async function requestDeviceCode(args) {
|
|
|
5994
6062
|
);
|
|
5995
6063
|
}
|
|
5996
6064
|
async function pollForApproval(args) {
|
|
5997
|
-
const
|
|
6065
|
+
const sleep6 = args.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
5998
6066
|
const now = args.now ?? (() => Date.now());
|
|
5999
6067
|
const pollIntervalMs = Math.max(1e3, args.intervalSeconds * 1e3);
|
|
6000
6068
|
const deadline = now() + args.expiresInSeconds * 1e3;
|
|
@@ -6013,7 +6081,7 @@ async function pollForApproval(args) {
|
|
|
6013
6081
|
switch (res.status) {
|
|
6014
6082
|
case "pending":
|
|
6015
6083
|
case "slow_down":
|
|
6016
|
-
await
|
|
6084
|
+
await sleep6(pollIntervalMs);
|
|
6017
6085
|
continue;
|
|
6018
6086
|
case "denied":
|
|
6019
6087
|
return { status: "denied" };
|
|
@@ -13828,6 +13896,738 @@ async function sleep3(ms, aborted) {
|
|
|
13828
13896
|
}
|
|
13829
13897
|
}
|
|
13830
13898
|
|
|
13899
|
+
// src/commands/module/rebuild.ts
|
|
13900
|
+
init_api_client();
|
|
13901
|
+
init_config();
|
|
13902
|
+
init_manifest_io();
|
|
13903
|
+
init_output();
|
|
13904
|
+
|
|
13905
|
+
// src/lib/permission-hint.ts
|
|
13906
|
+
init_api_client();
|
|
13907
|
+
init_output();
|
|
13908
|
+
var EXIT_NEEDS_GRANT = 3;
|
|
13909
|
+
var EXIT_TIMED_OUT = 4;
|
|
13910
|
+
var REBUILD_BUMP_OPERATOR_ONLY = "REBUILD_BUMP_OPERATOR_ONLY";
|
|
13911
|
+
var FEATURE_SUBJECTS = {
|
|
13912
|
+
module_runtime_streaming: "Streaming",
|
|
13913
|
+
module_rebuild: "Rebuild",
|
|
13914
|
+
module_building: "Building modules",
|
|
13915
|
+
module_runtime_sizing: "Memory size"
|
|
13916
|
+
};
|
|
13917
|
+
var OPTION_SUBJECTS = {
|
|
13918
|
+
streaming: "Streaming",
|
|
13919
|
+
memory_mb: "Memory size",
|
|
13920
|
+
reapply: "Re-applying the runtime",
|
|
13921
|
+
timeout: "The timeout",
|
|
13922
|
+
reserved_concurrency: "Reserved concurrency",
|
|
13923
|
+
log_retention: "Log retention"
|
|
13924
|
+
};
|
|
13925
|
+
var MODULE_RIGHT_CODES = /* @__PURE__ */ new Set([
|
|
13926
|
+
"MODULE_CAPABILITY_DENIED",
|
|
13927
|
+
"PUBLISH_REQUIRES_ADMIN",
|
|
13928
|
+
"NOT_COPY_OWNER"
|
|
13929
|
+
]);
|
|
13930
|
+
function str(v) {
|
|
13931
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
13932
|
+
}
|
|
13933
|
+
function num(v) {
|
|
13934
|
+
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
13935
|
+
}
|
|
13936
|
+
function featureFromPermission(permission) {
|
|
13937
|
+
const m = permission ? /^workspace\.feature\.([a-z0-9_]+)\.use$/.exec(permission) : null;
|
|
13938
|
+
return m?.[1];
|
|
13939
|
+
}
|
|
13940
|
+
function featureSubject(ctx) {
|
|
13941
|
+
const feature = str(ctx.feature) ?? featureFromPermission(str(ctx.permission));
|
|
13942
|
+
if (feature && FEATURE_SUBJECTS[feature]) return FEATURE_SUBJECTS[feature];
|
|
13943
|
+
return str(ctx.feature_label) ?? (feature ? `"${feature}"` : void 0);
|
|
13944
|
+
}
|
|
13945
|
+
function needsAdmin(what, module, ask) {
|
|
13946
|
+
const scope = module ? ` for ${module}` : "";
|
|
13947
|
+
return `${what}${scope} needs a Dashwise admin. Ask your Dashwise admin to ${ask}.`;
|
|
13948
|
+
}
|
|
13949
|
+
function refusalHint(code2, context, subject = {}) {
|
|
13950
|
+
const ctx = context ?? {};
|
|
13951
|
+
const module = subject.module ?? str(ctx.module_slug);
|
|
13952
|
+
const installationId = subject.installationId ?? num(ctx.installation_id);
|
|
13953
|
+
const installLabel = installationId !== void 0 ? ` (install #${installationId})` : "";
|
|
13954
|
+
const named = module ?? "this module";
|
|
13955
|
+
switch (code2) {
|
|
13956
|
+
case "FEATURE_DISABLED":
|
|
13957
|
+
case "PERMISSION_DENIED": {
|
|
13958
|
+
const what = featureSubject(ctx);
|
|
13959
|
+
if (!what) {
|
|
13960
|
+
return {
|
|
13961
|
+
message: `You don't have permission to do this${module ? ` for ${module}` : ""}. Ask your Dashwise admin for access.`,
|
|
13962
|
+
needsGrant: true
|
|
13963
|
+
};
|
|
13964
|
+
}
|
|
13965
|
+
return { message: needsAdmin(what, module, "enable it"), needsGrant: true };
|
|
13966
|
+
}
|
|
13967
|
+
case "RUNTIME_OPTION_OPERATOR_ONLY": {
|
|
13968
|
+
const option = str(ctx.option);
|
|
13969
|
+
const what = (option && OPTION_SUBJECTS[option]) ?? (option ? `"${option}"` : "This runtime option");
|
|
13970
|
+
return { message: needsAdmin(what, module, "change it"), needsGrant: true };
|
|
13971
|
+
}
|
|
13972
|
+
case REBUILD_BUMP_OPERATOR_ONLY: {
|
|
13973
|
+
const bump = str(ctx.bump) ?? "minor";
|
|
13974
|
+
return {
|
|
13975
|
+
message: needsAdmin(`A rebuild with a ${bump} version bump`, module, "run it"),
|
|
13976
|
+
needsGrant: true
|
|
13977
|
+
};
|
|
13978
|
+
}
|
|
13979
|
+
case "IMAGE_TAG_EXISTS":
|
|
13980
|
+
return {
|
|
13981
|
+
message: `This rebuild of ${named} needs a higher version bump than patch. A rebuild with a minor version bump needs a Dashwise admin. Ask your Dashwise admin to run it.`,
|
|
13982
|
+
needsGrant: true
|
|
13983
|
+
};
|
|
13984
|
+
case "RUNTIME_IMAGE_NOT_ELIGIBLE": {
|
|
13985
|
+
const version = str(ctx.version);
|
|
13986
|
+
const target = installationId !== void 0 ? ` --rollout installs:${installationId}` : "";
|
|
13987
|
+
return {
|
|
13988
|
+
message: `${named}${version ? ` v${version}` : ""} was built before streaming support. Rebuild it and upgrade this install first: dashwise module rebuild${module ? ` ${module}` : ""}${target}`,
|
|
13989
|
+
needsGrant: false
|
|
13990
|
+
};
|
|
13991
|
+
}
|
|
13992
|
+
case "RUNTIME_SWAP_PENDING": {
|
|
13993
|
+
const version = str(ctx.version);
|
|
13994
|
+
return {
|
|
13995
|
+
message: `${named}${installLabel} is still finishing its update${version ? ` to v${version}` : ""}, so streaming cannot be turned on yet. Try again in a few minutes.`,
|
|
13996
|
+
needsGrant: false
|
|
13997
|
+
};
|
|
13998
|
+
}
|
|
13999
|
+
case "RUNTIME_OPTION_UNAVAILABLE": {
|
|
14000
|
+
const option = str(ctx.option);
|
|
14001
|
+
const what = (option && OPTION_SUBJECTS[option]) ?? "This runtime option";
|
|
14002
|
+
return { message: `${what} is switched off platform-wide right now.`, needsGrant: false };
|
|
14003
|
+
}
|
|
14004
|
+
case "RUNTIME_CHANGE_IN_PROGRESS": {
|
|
14005
|
+
const op = num(ctx.operation_id);
|
|
14006
|
+
return {
|
|
14007
|
+
message: `A runtime change is already being applied to ${named}${op !== void 0 ? ` (operation #${op})` : ""}. Wait for it, then check: dashwise module runtime${module ? ` ${module}` : ""}`,
|
|
14008
|
+
needsGrant: false
|
|
14009
|
+
};
|
|
14010
|
+
}
|
|
14011
|
+
case "BUILD_IN_PROGRESS": {
|
|
14012
|
+
const buildId = num(ctx.build_id);
|
|
14013
|
+
const status = str(ctx.status);
|
|
14014
|
+
const which = buildId !== void 0 ? ` (#${buildId}${status ? `, ${status}` : ""})` : "";
|
|
14015
|
+
return {
|
|
14016
|
+
message: `A build of ${named} is already running${which}. Wait for it to finish, then run the rebuild again.`,
|
|
14017
|
+
needsGrant: false
|
|
14018
|
+
};
|
|
14019
|
+
}
|
|
14020
|
+
case "REBUILD_SOURCE_NOT_FOUND":
|
|
14021
|
+
return {
|
|
14022
|
+
message: `${named} has no build from source to rebuild. Publish it first: dashwise module publish`,
|
|
14023
|
+
needsGrant: false
|
|
14024
|
+
};
|
|
14025
|
+
case "INSTALLATION_NOT_FOUND":
|
|
14026
|
+
return {
|
|
14027
|
+
message: `${installationId !== void 0 ? `Installation #${installationId}` : "The installation"} was not found, or you cannot see it.`,
|
|
14028
|
+
needsGrant: false
|
|
14029
|
+
};
|
|
14030
|
+
case "PUBLISH_RATE_LIMITED":
|
|
14031
|
+
case "PUBLISH_USER_RATE_LIMITED":
|
|
14032
|
+
return {
|
|
14033
|
+
message: `Too many builds of ${named} in the last hour. Try again later.`,
|
|
14034
|
+
needsGrant: false
|
|
14035
|
+
};
|
|
14036
|
+
default:
|
|
14037
|
+
if (MODULE_RIGHT_CODES.has(code2)) {
|
|
14038
|
+
return {
|
|
14039
|
+
message: `You can't manage ${named}${installLabel}. Ask your Dashwise admin for access.`,
|
|
14040
|
+
needsGrant: true
|
|
14041
|
+
};
|
|
14042
|
+
}
|
|
14043
|
+
return null;
|
|
14044
|
+
}
|
|
14045
|
+
}
|
|
14046
|
+
var ANSI = /\x1b\[[0-9;]*m/g;
|
|
14047
|
+
function reportCliError(message, opts) {
|
|
14048
|
+
if (opts.json) {
|
|
14049
|
+
out(
|
|
14050
|
+
JSON.stringify({
|
|
14051
|
+
error: { code: opts.code ?? "CLI_ERROR", message: message.replace(ANSI, ""), status: null, context: null },
|
|
14052
|
+
hint: null,
|
|
14053
|
+
needs_grant: false
|
|
14054
|
+
})
|
|
14055
|
+
);
|
|
14056
|
+
} else {
|
|
14057
|
+
fail(message);
|
|
14058
|
+
}
|
|
14059
|
+
return 1;
|
|
14060
|
+
}
|
|
14061
|
+
function reportFailedOutcome(failure, hint, opts) {
|
|
14062
|
+
if (opts.json) {
|
|
14063
|
+
out(
|
|
14064
|
+
JSON.stringify({
|
|
14065
|
+
error: { code: failure.code, message: failure.message ?? null, status: null, context: null },
|
|
14066
|
+
hint: hint?.message ?? null,
|
|
14067
|
+
needs_grant: hint?.needsGrant ?? false,
|
|
14068
|
+
...opts.extra
|
|
14069
|
+
})
|
|
14070
|
+
);
|
|
14071
|
+
} else {
|
|
14072
|
+
fail(hint?.message ?? opts.fallback);
|
|
14073
|
+
}
|
|
14074
|
+
return hint?.needsGrant ? EXIT_NEEDS_GRANT : 1;
|
|
14075
|
+
}
|
|
14076
|
+
function isTransientPollError(err) {
|
|
14077
|
+
if (!(err instanceof ApiError)) return true;
|
|
14078
|
+
return err.status === 0 || err.status === 429 || err.status >= 500;
|
|
14079
|
+
}
|
|
14080
|
+
function reportRefusal(err, subject, opts) {
|
|
14081
|
+
if (!(err instanceof ApiError)) {
|
|
14082
|
+
const message = err?.message ?? String(err);
|
|
14083
|
+
if (opts.json) {
|
|
14084
|
+
out(
|
|
14085
|
+
JSON.stringify({
|
|
14086
|
+
error: { code: "CLI_ERROR", message: message.replace(ANSI, ""), status: null, context: null },
|
|
14087
|
+
hint: null,
|
|
14088
|
+
needs_grant: false
|
|
14089
|
+
})
|
|
14090
|
+
);
|
|
14091
|
+
} else {
|
|
14092
|
+
fail(`${opts.action} failed: ${message}`);
|
|
14093
|
+
}
|
|
14094
|
+
return 1;
|
|
14095
|
+
}
|
|
14096
|
+
const hint = err.status === 401 ? { message: "Your login has expired. Run: dashwise login", needsGrant: false } : refusalHint(err.code, err.context, subject);
|
|
14097
|
+
if (opts.json) {
|
|
14098
|
+
out(
|
|
14099
|
+
JSON.stringify({
|
|
14100
|
+
error: { code: err.code, message: err.message, status: err.status, context: err.context ?? null },
|
|
14101
|
+
hint: hint?.message ?? null,
|
|
14102
|
+
needs_grant: hint?.needsGrant ?? false
|
|
14103
|
+
})
|
|
14104
|
+
);
|
|
14105
|
+
} else if (hint) {
|
|
14106
|
+
fail(hint.message);
|
|
14107
|
+
} else {
|
|
14108
|
+
fail(`${opts.action} failed (${err.code}): ${err.message}`);
|
|
14109
|
+
}
|
|
14110
|
+
return hint?.needsGrant ? EXIT_NEEDS_GRANT : 1;
|
|
14111
|
+
}
|
|
14112
|
+
|
|
14113
|
+
// src/commands/module/rebuild.ts
|
|
14114
|
+
var PLATFORM_DEFAULTS = ["manifest", "on", "off"];
|
|
14115
|
+
var BUMPS = ["patch", "minor", "major"];
|
|
14116
|
+
function parseRollout(raw) {
|
|
14117
|
+
if (raw === void 0) return null;
|
|
14118
|
+
const v = raw.trim();
|
|
14119
|
+
if (v === "" || v === "none") return null;
|
|
14120
|
+
if (v === "workspace") return { scope: "workspace" };
|
|
14121
|
+
const ws = /^workspace:(\d+)$/.exec(v);
|
|
14122
|
+
if (ws) return { scope: "workspace", workspace_id: Number(ws[1]) };
|
|
14123
|
+
const inst = /^installs?:(.+)$/.exec(v);
|
|
14124
|
+
if (inst) {
|
|
14125
|
+
const ids = inst[1].split(",").map((s) => s.trim());
|
|
14126
|
+
if (ids.length === 0 || ids.some((s) => !/^\d+$/.test(s) || Number(s) <= 0)) {
|
|
14127
|
+
return `--rollout installs: expects comma-separated installation ids (got "${raw}").`;
|
|
14128
|
+
}
|
|
14129
|
+
const unique = [...new Set(ids.map(Number))];
|
|
14130
|
+
if (unique.length > 50) return "--rollout installs: takes at most 50 installation ids.";
|
|
14131
|
+
return { scope: "installations", installation_ids: unique };
|
|
14132
|
+
}
|
|
14133
|
+
return `--rollout expects none, installs:<id,id> or workspace[:<id>] (got "${raw}").`;
|
|
14134
|
+
}
|
|
14135
|
+
async function moduleRebuildCommand(slugArg, options) {
|
|
14136
|
+
let slug = slugArg;
|
|
14137
|
+
if (!slug) {
|
|
14138
|
+
try {
|
|
14139
|
+
const fromManifest = readManifest(options.dir)?.module?.slug;
|
|
14140
|
+
if (typeof fromManifest === "string" && fromManifest.length > 0) slug = fromManifest;
|
|
14141
|
+
} catch {
|
|
14142
|
+
}
|
|
14143
|
+
}
|
|
14144
|
+
const json = options.json;
|
|
14145
|
+
const usage2 = (message) => reportCliError(message, { json, code: "USAGE" });
|
|
14146
|
+
if (!slug) {
|
|
14147
|
+
return usage2(
|
|
14148
|
+
`No module slug. Pass one (${code("dashwise module rebuild <slug>")}) or run from a directory with a valid module.json.`
|
|
14149
|
+
);
|
|
14150
|
+
}
|
|
14151
|
+
const bump = (options.bump ?? "patch").trim().toLowerCase();
|
|
14152
|
+
if (!BUMPS.includes(bump)) {
|
|
14153
|
+
return usage2(`--bump expects patch, minor or major (got "${options.bump}").`);
|
|
14154
|
+
}
|
|
14155
|
+
if (bump !== "patch") {
|
|
14156
|
+
const hint = refusalHint(REBUILD_BUMP_OPERATOR_ONLY, { bump }, { module: slug });
|
|
14157
|
+
if (options.json) {
|
|
14158
|
+
out(
|
|
14159
|
+
JSON.stringify({
|
|
14160
|
+
error: { code: REBUILD_BUMP_OPERATOR_ONLY, message: hint.message, status: null, context: { bump } },
|
|
14161
|
+
hint: hint.message,
|
|
14162
|
+
needs_grant: true
|
|
14163
|
+
})
|
|
14164
|
+
);
|
|
14165
|
+
} else {
|
|
14166
|
+
fail(hint.message);
|
|
14167
|
+
publishBumpTip(bump);
|
|
14168
|
+
}
|
|
14169
|
+
return EXIT_NEEDS_GRANT;
|
|
14170
|
+
}
|
|
14171
|
+
let platformDefaults;
|
|
14172
|
+
if (options.platformDefaults !== void 0) {
|
|
14173
|
+
const v = options.platformDefaults.trim().toLowerCase();
|
|
14174
|
+
if (!PLATFORM_DEFAULTS.includes(v)) {
|
|
14175
|
+
return usage2(`--platform-defaults expects manifest, on or off (got "${options.platformDefaults}").`);
|
|
14176
|
+
}
|
|
14177
|
+
platformDefaults = v;
|
|
14178
|
+
}
|
|
14179
|
+
if (options.fromBuild !== void 0 && (!Number.isInteger(options.fromBuild) || options.fromBuild <= 0)) {
|
|
14180
|
+
return usage2(`--from-build expects a positive build id (got "${options.fromBuild}").`);
|
|
14181
|
+
}
|
|
14182
|
+
const rollout = parseRollout(options.rollout);
|
|
14183
|
+
if (typeof rollout === "string") return usage2(rollout);
|
|
14184
|
+
if (!isLoggedIn()) {
|
|
14185
|
+
return reportCliError(`Not logged in. Run ${code("dashwise login")} first.`, { json });
|
|
14186
|
+
}
|
|
14187
|
+
const cfg = readConfig();
|
|
14188
|
+
if (!cfg) {
|
|
14189
|
+
return reportCliError(`CLI config missing. Run ${code("dashwise login")}.`, { json });
|
|
14190
|
+
}
|
|
14191
|
+
const auth = { baseUrl: resolveApiUrl(), auth: { token: cfg.token } };
|
|
14192
|
+
let moduleId;
|
|
14193
|
+
try {
|
|
14194
|
+
const mod = await getModuleBySlug(slug, auth);
|
|
14195
|
+
moduleId = mod.id;
|
|
14196
|
+
slug = mod.slug;
|
|
14197
|
+
} catch (err) {
|
|
14198
|
+
if (err instanceof ApiError && err.status === 404) {
|
|
14199
|
+
return reportCliError(`Module ${code(slug)} not found on the backend.`, { json });
|
|
14200
|
+
}
|
|
14201
|
+
return reportRefusal(err, { module: slug }, { json: options.json, action: "Resolving the module" });
|
|
14202
|
+
}
|
|
14203
|
+
const buildOptions = {
|
|
14204
|
+
...platformDefaults !== void 0 ? { platform_defaults: platformDefaults } : {},
|
|
14205
|
+
...options.clean ? { clean: true } : {}
|
|
14206
|
+
};
|
|
14207
|
+
const body = {
|
|
14208
|
+
...options.fromBuild !== void 0 ? { from_build_id: options.fromBuild } : {},
|
|
14209
|
+
...Object.keys(buildOptions).length > 0 ? { options: buildOptions } : {},
|
|
14210
|
+
...rollout ? { rollout } : {},
|
|
14211
|
+
...options.reason !== void 0 ? { reason: options.reason } : {}
|
|
14212
|
+
};
|
|
14213
|
+
let build;
|
|
14214
|
+
try {
|
|
14215
|
+
build = await rebuildModule(moduleId, body, auth);
|
|
14216
|
+
} catch (err) {
|
|
14217
|
+
return reportRefusal(err, { module: slug }, { json: options.json, action: "Rebuild" });
|
|
14218
|
+
}
|
|
14219
|
+
if (options.noWait) {
|
|
14220
|
+
if (options.json) out(JSON.stringify(build));
|
|
14221
|
+
else {
|
|
14222
|
+
success(`Rebuild of ${code(slug)} queued as build ${code(`#${build.id}`)}.`);
|
|
14223
|
+
info(`Not waiting (${code("--no-wait")}). A rebuild takes about 1\u20133 minutes.`);
|
|
14224
|
+
}
|
|
14225
|
+
return 0;
|
|
14226
|
+
}
|
|
14227
|
+
if (!options.json) info(`Rebuild of ${code(slug)} queued as build ${code(`#${build.id}`)}. Building\u2026`);
|
|
14228
|
+
let waited;
|
|
14229
|
+
try {
|
|
14230
|
+
waited = await waitForBuild(moduleId, build, Boolean(rollout), options, auth);
|
|
14231
|
+
} catch (err) {
|
|
14232
|
+
return reportRefusal(err, { module: slug }, { json: options.json, action: `Checking build #${build.id}` });
|
|
14233
|
+
}
|
|
14234
|
+
const final = waited.build;
|
|
14235
|
+
if (waited.timedOut) {
|
|
14236
|
+
if (options.json) out(JSON.stringify({ ...final, timed_out: true }));
|
|
14237
|
+
else warn(`Still running after ${options.timeoutSec ?? 900}s. Build ${code(`#${build.id}`)} may still finish.`);
|
|
14238
|
+
return EXIT_TIMED_OUT;
|
|
14239
|
+
}
|
|
14240
|
+
if (final.status === "failed") {
|
|
14241
|
+
const failureCode = final.error_code ?? "BUILD_FAILED";
|
|
14242
|
+
const hint = final.error_code ? refusalHint(final.error_code, void 0, { module: slug }) : null;
|
|
14243
|
+
const exit = reportFailedOutcome({ code: failureCode, message: final.error_message }, hint, {
|
|
14244
|
+
json: options.json,
|
|
14245
|
+
fallback: `Build ${code(`#${final.id}`)} failed (${failureCode}): ${final.error_message ?? "(no detail)"}`,
|
|
14246
|
+
extra: { build: final }
|
|
14247
|
+
});
|
|
14248
|
+
if (!options.json) {
|
|
14249
|
+
if (final.log_url) info(`Logs: ${code(final.log_url)}`);
|
|
14250
|
+
if (final.error_code === "IMAGE_TAG_EXISTS") publishBumpTip("minor");
|
|
14251
|
+
}
|
|
14252
|
+
return exit;
|
|
14253
|
+
}
|
|
14254
|
+
const r = final.rollout;
|
|
14255
|
+
if (rollout && r?.status === "failed") {
|
|
14256
|
+
const failureCode = r.code ?? "ROLLOUT_FAILED";
|
|
14257
|
+
const hint = r.code ? refusalHint(r.code, void 0, { module: slug }) : null;
|
|
14258
|
+
if (!options.json) success(`Rebuilt ${code(slug)} as ${code(`v${final.version ?? "?"}`)} ${dim(`(build #${final.id})`)}.`);
|
|
14259
|
+
return reportFailedOutcome({ code: failureCode, message: null }, hint, {
|
|
14260
|
+
json: options.json,
|
|
14261
|
+
fallback: `Upgrading the installs failed (${failureCode}).`,
|
|
14262
|
+
extra: { build: final }
|
|
14263
|
+
});
|
|
14264
|
+
}
|
|
14265
|
+
if (options.json) out(JSON.stringify(final));
|
|
14266
|
+
if (!options.json) success(`Rebuilt ${code(slug)} as ${code(`v${final.version ?? "?"}`)} ${dim(`(build #${final.id})`)}.`);
|
|
14267
|
+
if (!rollout) {
|
|
14268
|
+
if (!options.json) info(`Installs keep their version until upgraded: ${code(`dashwise module install ${slug}`)}`);
|
|
14269
|
+
return 0;
|
|
14270
|
+
}
|
|
14271
|
+
if (!r || r.status !== "completed") {
|
|
14272
|
+
if (!options.json) warn(`The upgrade of installs is still ${r?.status ?? "pending"}. Check with ${code(`dashwise module status ${slug}`)}.`);
|
|
14273
|
+
return 0;
|
|
14274
|
+
}
|
|
14275
|
+
if (!options.json) {
|
|
14276
|
+
const counts = Object.entries(r.counts ?? {}).map(([k, n]) => `${n} ${k}`).join(", ");
|
|
14277
|
+
success(`Installs upgraded to v${r.to_version ?? final.version ?? "?"}: ${counts || "none"}.`);
|
|
14278
|
+
const notUpgraded = (r.counts?.failed ?? 0) + (r.counts?.skipped ?? 0);
|
|
14279
|
+
if (notUpgraded > 0) {
|
|
14280
|
+
warn(
|
|
14281
|
+
`${notUpgraded} install${notUpgraded === 1 ? " was" : "s were"} not upgraded (failed or skipped). Check with ${code(`dashwise module status ${slug}`)}.`
|
|
14282
|
+
);
|
|
14283
|
+
}
|
|
14284
|
+
if ((r.counts?.blocked ?? 0) > 0) {
|
|
14285
|
+
warn(
|
|
14286
|
+
`Blocked installs were left on their version: the upgrade would break other modules or remove data. Upgrade them one by one with ${code(`dashwise module install ${slug}`)} to see why.`
|
|
14287
|
+
);
|
|
14288
|
+
}
|
|
14289
|
+
}
|
|
14290
|
+
return 0;
|
|
14291
|
+
}
|
|
14292
|
+
async function waitForBuild(moduleId, queued, withRollout, options, auth) {
|
|
14293
|
+
const buildId = queued.id;
|
|
14294
|
+
let lastSeen = queued;
|
|
14295
|
+
const deadline = Date.now() + (options.timeoutSec ?? 900) * 1e3;
|
|
14296
|
+
const interval = options.pollIntervalMs ?? 4e3;
|
|
14297
|
+
let last = "";
|
|
14298
|
+
while (Date.now() < deadline) {
|
|
14299
|
+
await sleep4(interval);
|
|
14300
|
+
let b;
|
|
14301
|
+
try {
|
|
14302
|
+
b = await getModuleBuild(moduleId, buildId, auth);
|
|
14303
|
+
} catch (err) {
|
|
14304
|
+
if (isTransientPollError(err)) continue;
|
|
14305
|
+
throw err;
|
|
14306
|
+
}
|
|
14307
|
+
lastSeen = b;
|
|
14308
|
+
const phase = b.status === "succeeded" && withRollout ? `upgrading installs (${b.rollout?.status ?? "pending"})` : b.status;
|
|
14309
|
+
if (!options.json && phase !== last) {
|
|
14310
|
+
info(`Build ${code(`#${buildId}`)}: ${phase}`);
|
|
14311
|
+
last = phase;
|
|
14312
|
+
}
|
|
14313
|
+
if (b.status === "failed") return { build: b, timedOut: false };
|
|
14314
|
+
if (b.status === "succeeded") {
|
|
14315
|
+
if (!withRollout) return { build: b, timedOut: false };
|
|
14316
|
+
if (b.rollout && (b.rollout.status === "completed" || b.rollout.status === "failed")) {
|
|
14317
|
+
return { build: b, timedOut: false };
|
|
14318
|
+
}
|
|
14319
|
+
}
|
|
14320
|
+
}
|
|
14321
|
+
return { build: lastSeen, timedOut: true };
|
|
14322
|
+
}
|
|
14323
|
+
function publishBumpTip(bump) {
|
|
14324
|
+
info(
|
|
14325
|
+
`A rebuild you run yourself always bumps patch. To choose the bump, publish from source: ${code(`dashwise module publish --bump ${bump}`)}`
|
|
14326
|
+
);
|
|
14327
|
+
}
|
|
14328
|
+
function sleep4(ms) {
|
|
14329
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
14330
|
+
}
|
|
14331
|
+
|
|
14332
|
+
// src/commands/module/runtime.ts
|
|
14333
|
+
init_api_client();
|
|
14334
|
+
init_config();
|
|
14335
|
+
init_manifest_io();
|
|
14336
|
+
init_output();
|
|
14337
|
+
var TERMINAL_OPERATION = /* @__PURE__ */ new Set(["succeeded", "failed", "superseded"]);
|
|
14338
|
+
async function moduleRuntimeShowCommand(slugArg, options) {
|
|
14339
|
+
const auth = authOrFail(options.json);
|
|
14340
|
+
if (!auth) return 1;
|
|
14341
|
+
const target = await resolveInstall(slugArg, options, auth, "dashwise module runtime <slug>");
|
|
14342
|
+
if (typeof target === "number") return target;
|
|
14343
|
+
let view;
|
|
14344
|
+
try {
|
|
14345
|
+
view = await getInstallationRuntime(target.installationId, auth);
|
|
14346
|
+
} catch (err) {
|
|
14347
|
+
return reportRefusal(
|
|
14348
|
+
err,
|
|
14349
|
+
{ module: target.slug, installationId: target.installationId },
|
|
14350
|
+
{ json: options.json, action: "Reading the runtime settings" }
|
|
14351
|
+
);
|
|
14352
|
+
}
|
|
14353
|
+
if (options.json) {
|
|
14354
|
+
out(JSON.stringify(view));
|
|
14355
|
+
return 0;
|
|
14356
|
+
}
|
|
14357
|
+
renderRuntime(view);
|
|
14358
|
+
return 0;
|
|
14359
|
+
}
|
|
14360
|
+
function renderRuntime(view) {
|
|
14361
|
+
const slug = view.module.slug;
|
|
14362
|
+
const desired = view.desired.invoke_mode;
|
|
14363
|
+
const effective = view.effective.invoke_mode;
|
|
14364
|
+
const streamingLine = `${modeLabel(effective)}` + (desired !== null && desired !== effective ? ` ${dim(`(requested: ${modeLabel(desired)})`)}` : "") + (desired === null ? ` ${dim("(platform default)")}` : "");
|
|
14365
|
+
out("");
|
|
14366
|
+
kv("Module:", `${slug} ${dim(`(id ${view.module.id})`)}`);
|
|
14367
|
+
kv("Install:", `#${view.installation_id} ${dim(`(workspace ${view.workspace_id})`)}`);
|
|
14368
|
+
kv("Version:", view.version ?? dim("(none)"));
|
|
14369
|
+
kv("Streaming:", streamingLine);
|
|
14370
|
+
kv(
|
|
14371
|
+
"Memory:",
|
|
14372
|
+
`${view.effective.memory_mb} MB ${dim(view.effective.memory_source === "override" ? "(override)" : "(from module.json machine_size)")}`
|
|
14373
|
+
);
|
|
14374
|
+
kv("Eligible:", view.eligibility.streaming ? "yes" : `no ${dim(`(${eligibilityText(view.eligibility.reason)})`)}`);
|
|
14375
|
+
kv("Granted:", view.grant.module_runtime_streaming ? "yes" : "no");
|
|
14376
|
+
kv("Last change:", view.last_operation ? operationText(view.last_operation) : dim("(none)"));
|
|
14377
|
+
out("");
|
|
14378
|
+
if (!view.grant.module_runtime_streaming) {
|
|
14379
|
+
const hint = refusalHint("FEATURE_DISABLED", { feature: "module_runtime_streaming" }, { module: slug });
|
|
14380
|
+
if (hint) info(hint.message);
|
|
14381
|
+
} else if (!view.eligibility.streaming && view.eligibility.reason === "image_not_eligible") {
|
|
14382
|
+
const hint = refusalHint(
|
|
14383
|
+
"RUNTIME_IMAGE_NOT_ELIGIBLE",
|
|
14384
|
+
{ version: view.version ?? void 0 },
|
|
14385
|
+
{ module: slug, installationId: view.installation_id }
|
|
14386
|
+
);
|
|
14387
|
+
if (hint) info(hint.message);
|
|
14388
|
+
}
|
|
14389
|
+
}
|
|
14390
|
+
function modeLabel(mode) {
|
|
14391
|
+
if (mode === "RESPONSE_STREAM") return "on";
|
|
14392
|
+
if (mode === "BUFFERED") return "off";
|
|
14393
|
+
return "default";
|
|
14394
|
+
}
|
|
14395
|
+
function eligibilityText(reason) {
|
|
14396
|
+
switch (reason) {
|
|
14397
|
+
case "platform_disabled":
|
|
14398
|
+
return "streaming is switched off platform-wide";
|
|
14399
|
+
case "image_not_eligible":
|
|
14400
|
+
return "this version was built before streaming support; rebuild it";
|
|
14401
|
+
case "swap_pending":
|
|
14402
|
+
return "an update is still finishing";
|
|
14403
|
+
default:
|
|
14404
|
+
return reason ?? "unknown";
|
|
14405
|
+
}
|
|
14406
|
+
}
|
|
14407
|
+
function operationText(op) {
|
|
14408
|
+
const when = op.finished_at ?? op.started_at ?? op.created_at;
|
|
14409
|
+
const err = op.error_code ? ` ${op.error_code}` : "";
|
|
14410
|
+
return `${op.kind} ${op.status}${err} ${dim(`(#${op.id}, ${when})`)}`;
|
|
14411
|
+
}
|
|
14412
|
+
async function moduleRuntimeSetCommand(slugArg, options) {
|
|
14413
|
+
let streaming;
|
|
14414
|
+
if (options.streaming !== void 0) {
|
|
14415
|
+
const v = options.streaming.trim().toLowerCase();
|
|
14416
|
+
if (v === "on") streaming = "RESPONSE_STREAM";
|
|
14417
|
+
else if (v === "off") streaming = "BUFFERED";
|
|
14418
|
+
else {
|
|
14419
|
+
return usage(`--streaming expects "on" or "off" (got "${options.streaming}").`, options.json);
|
|
14420
|
+
}
|
|
14421
|
+
}
|
|
14422
|
+
if (options.memory !== void 0 && (!Number.isInteger(options.memory) || options.memory <= 0)) {
|
|
14423
|
+
return usage(`--memory expects a whole number of MB (got "${options.memory}").`, options.json);
|
|
14424
|
+
}
|
|
14425
|
+
if (streaming === void 0 && options.memory === void 0) {
|
|
14426
|
+
return usage(`Nothing to change. Pass ${code("--streaming on|off")} (or ${code("--memory <mb>")}).`, options.json);
|
|
14427
|
+
}
|
|
14428
|
+
const auth = authOrFail(options.json);
|
|
14429
|
+
if (!auth) return 1;
|
|
14430
|
+
const target = await resolveInstall(slugArg, options, auth, "dashwise module runtime set <slug>");
|
|
14431
|
+
if (typeof target === "number") return target;
|
|
14432
|
+
let before;
|
|
14433
|
+
try {
|
|
14434
|
+
before = await getInstallationRuntime(target.installationId, auth);
|
|
14435
|
+
} catch (err) {
|
|
14436
|
+
return reportRefusal(
|
|
14437
|
+
err,
|
|
14438
|
+
{ module: target.slug, installationId: target.installationId },
|
|
14439
|
+
{ json: options.json, action: "Reading the runtime settings" }
|
|
14440
|
+
);
|
|
14441
|
+
}
|
|
14442
|
+
const slug = before.module.slug;
|
|
14443
|
+
const subject = { module: slug, installationId: before.installation_id };
|
|
14444
|
+
if (streaming !== void 0 && !options.yes && !options.json && process.stdin.isTTY) {
|
|
14445
|
+
const ok = await confirm({
|
|
14446
|
+
message: `Turn streaming ${streaming === "RESPONSE_STREAM" ? "on" : "off"} for ${slug} (install #${before.installation_id})? The switch takes about a minute, and pages may come back empty while it runs.`
|
|
14447
|
+
});
|
|
14448
|
+
if (isCancel(ok) || ok !== true) {
|
|
14449
|
+
info("Cancelled. Nothing was changed.");
|
|
14450
|
+
return 1;
|
|
14451
|
+
}
|
|
14452
|
+
}
|
|
14453
|
+
const invokeMode = streaming ?? before.desired.invoke_mode ?? before.effective.invoke_mode;
|
|
14454
|
+
let accepted;
|
|
14455
|
+
try {
|
|
14456
|
+
accepted = await setInstallationRuntime(
|
|
14457
|
+
before.installation_id,
|
|
14458
|
+
{
|
|
14459
|
+
invoke_mode: invokeMode,
|
|
14460
|
+
...options.memory !== void 0 ? { memory_mb: options.memory } : {},
|
|
14461
|
+
...options.reason !== void 0 ? { reason: options.reason } : {}
|
|
14462
|
+
},
|
|
14463
|
+
auth
|
|
14464
|
+
);
|
|
14465
|
+
} catch (err) {
|
|
14466
|
+
return reportRefusal(err, subject, { json: options.json, action: "Changing the runtime settings" });
|
|
14467
|
+
}
|
|
14468
|
+
const what = describeChange(streaming, options.memory);
|
|
14469
|
+
if (accepted.noop) {
|
|
14470
|
+
if (options.json) out(JSON.stringify({ accepted, operation: before.last_operation }));
|
|
14471
|
+
else success(`${what} for ${code(slug)} is already in place. Nothing to do.`);
|
|
14472
|
+
return 0;
|
|
14473
|
+
}
|
|
14474
|
+
if (options.noWait || accepted.operation_id === null) {
|
|
14475
|
+
if (options.json) out(JSON.stringify({ accepted, operation: null }));
|
|
14476
|
+
else {
|
|
14477
|
+
success(`${what} for ${code(slug)} accepted (operation #${accepted.operation_id ?? "?"}).`);
|
|
14478
|
+
info(`It takes about a minute. Check with ${code(`dashwise module runtime ${slug}`)}.`);
|
|
14479
|
+
}
|
|
14480
|
+
return 0;
|
|
14481
|
+
}
|
|
14482
|
+
if (!options.json) info(`Applying: ${what.toLowerCase()} for ${code(slug)} (about a minute)\u2026`);
|
|
14483
|
+
let op;
|
|
14484
|
+
try {
|
|
14485
|
+
op = await waitForOperation(before.installation_id, accepted.operation_id, options, auth);
|
|
14486
|
+
} catch (err) {
|
|
14487
|
+
return reportRefusal(err, subject, { json: options.json, action: "Checking the runtime change" });
|
|
14488
|
+
}
|
|
14489
|
+
if (!op) {
|
|
14490
|
+
if (options.json) out(JSON.stringify({ accepted, operation: null, timed_out: true }));
|
|
14491
|
+
else {
|
|
14492
|
+
warn(
|
|
14493
|
+
`Still applying after ${options.timeoutSec ?? 240}s. Check with ${code(`dashwise module runtime ${slug}`)}.`
|
|
14494
|
+
);
|
|
14495
|
+
}
|
|
14496
|
+
return EXIT_TIMED_OUT;
|
|
14497
|
+
}
|
|
14498
|
+
if (op.status === "failed") {
|
|
14499
|
+
const failureCode = op.error_code ?? "UNKNOWN";
|
|
14500
|
+
const hint = op.error_code ? refusalHint(op.error_code, void 0, subject) : null;
|
|
14501
|
+
return reportFailedOutcome({ code: failureCode, message: op.error_message }, hint, {
|
|
14502
|
+
json: options.json,
|
|
14503
|
+
fallback: `The change failed (${failureCode}): ${op.error_message ?? "(no detail)"}`,
|
|
14504
|
+
extra: { accepted, operation: op }
|
|
14505
|
+
});
|
|
14506
|
+
}
|
|
14507
|
+
if (options.json) out(JSON.stringify({ accepted, operation: op }));
|
|
14508
|
+
if (op.status === "succeeded") {
|
|
14509
|
+
if (!options.json) {
|
|
14510
|
+
const outcome = typeof op.result?.outcome === "string" ? op.result.outcome : null;
|
|
14511
|
+
if (outcome === "swap_pending") {
|
|
14512
|
+
success(`${what} for ${code(slug)} is saved. Streaming starts once the module's pending update finishes.`);
|
|
14513
|
+
} else if (outcome === "no_runtime") {
|
|
14514
|
+
success(`${what} for ${code(slug)} is saved. It applies when the module is next deployed.`);
|
|
14515
|
+
} else {
|
|
14516
|
+
success(`${what} for ${code(slug)} is done.`);
|
|
14517
|
+
}
|
|
14518
|
+
}
|
|
14519
|
+
return 0;
|
|
14520
|
+
}
|
|
14521
|
+
if (!options.json) warn(`A newer change replaced this one. Check with ${code(`dashwise module runtime ${slug}`)}.`);
|
|
14522
|
+
return 0;
|
|
14523
|
+
}
|
|
14524
|
+
function describeChange(streaming, memory) {
|
|
14525
|
+
const parts = [];
|
|
14526
|
+
if (streaming !== void 0) parts.push(`Streaming ${streaming === "RESPONSE_STREAM" ? "on" : "off"}`);
|
|
14527
|
+
if (memory !== void 0) parts.push(`${parts.length ? "memory" : "Memory"} ${memory} MB`);
|
|
14528
|
+
return parts.join(", ");
|
|
14529
|
+
}
|
|
14530
|
+
async function waitForOperation(installationId, operationId, options, auth) {
|
|
14531
|
+
const deadline = Date.now() + (options.timeoutSec ?? 240) * 1e3;
|
|
14532
|
+
const interval = options.pollIntervalMs ?? 3e3;
|
|
14533
|
+
while (Date.now() < deadline) {
|
|
14534
|
+
await sleep5(interval);
|
|
14535
|
+
let view;
|
|
14536
|
+
try {
|
|
14537
|
+
view = await getInstallationRuntime(installationId, auth);
|
|
14538
|
+
} catch (err) {
|
|
14539
|
+
if (isTransientPollError(err)) continue;
|
|
14540
|
+
throw err;
|
|
14541
|
+
}
|
|
14542
|
+
const op = view.last_operation;
|
|
14543
|
+
if (!op) continue;
|
|
14544
|
+
if (op.id > operationId) {
|
|
14545
|
+
return { id: operationId, status: "superseded", superseded_by_operation_id: op.id };
|
|
14546
|
+
}
|
|
14547
|
+
if (op.id === operationId && TERMINAL_OPERATION.has(op.status)) return op;
|
|
14548
|
+
}
|
|
14549
|
+
return null;
|
|
14550
|
+
}
|
|
14551
|
+
function usage(message, json) {
|
|
14552
|
+
return reportCliError(message, { json, code: "USAGE" });
|
|
14553
|
+
}
|
|
14554
|
+
function authOrFail(json) {
|
|
14555
|
+
if (!isLoggedIn()) {
|
|
14556
|
+
reportCliError(`Not logged in. Run ${code("dashwise login")} first.`, { json });
|
|
14557
|
+
return null;
|
|
14558
|
+
}
|
|
14559
|
+
const cfg = readConfig();
|
|
14560
|
+
if (!cfg) {
|
|
14561
|
+
reportCliError(`CLI config missing. Run ${code("dashwise login")}.`, { json });
|
|
14562
|
+
return null;
|
|
14563
|
+
}
|
|
14564
|
+
return { baseUrl: resolveApiUrl(), auth: { token: cfg.token } };
|
|
14565
|
+
}
|
|
14566
|
+
async function resolveInstall(slugArg, options, auth, usageLine) {
|
|
14567
|
+
const json = options.json;
|
|
14568
|
+
if (options.install !== void 0) {
|
|
14569
|
+
if (!Number.isInteger(options.install) || options.install <= 0) {
|
|
14570
|
+
return usage(`--install expects a positive integer installation id (got "${options.install}").`, options.json);
|
|
14571
|
+
}
|
|
14572
|
+
return { installationId: options.install, ...slugArg ? { slug: slugArg } : {} };
|
|
14573
|
+
}
|
|
14574
|
+
let slug = slugArg;
|
|
14575
|
+
if (!slug) {
|
|
14576
|
+
try {
|
|
14577
|
+
const fromManifest = readManifest(options.dir)?.module?.slug;
|
|
14578
|
+
if (typeof fromManifest === "string" && fromManifest.length > 0) slug = fromManifest;
|
|
14579
|
+
} catch {
|
|
14580
|
+
}
|
|
14581
|
+
}
|
|
14582
|
+
if (!slug) {
|
|
14583
|
+
return reportCliError(
|
|
14584
|
+
`No module slug. Pass one (${code(usageLine)}), run from a directory with a valid module.json, or target an install with ${code("--install <id>")}.`,
|
|
14585
|
+
{ json: options.json, code: "USAGE" }
|
|
14586
|
+
);
|
|
14587
|
+
}
|
|
14588
|
+
let modId;
|
|
14589
|
+
try {
|
|
14590
|
+
modId = (await getModuleBySlug(slug, auth)).id;
|
|
14591
|
+
} catch (err) {
|
|
14592
|
+
if (err instanceof ApiError && err.status === 404) {
|
|
14593
|
+
return reportCliError(`Module ${code(slug)} not found on the backend.`, { json });
|
|
14594
|
+
}
|
|
14595
|
+
return reportCliError(`Could not resolve module ${code(slug)}: ${err.message}`, { json });
|
|
14596
|
+
}
|
|
14597
|
+
const cfg = readConfig();
|
|
14598
|
+
let workspace2;
|
|
14599
|
+
try {
|
|
14600
|
+
workspace2 = await resolveWorkspaceWithFallback({
|
|
14601
|
+
userFlag: options.workspace,
|
|
14602
|
+
cachedWorkspaceId: readModuleEntry(slug)?.workspaceId,
|
|
14603
|
+
configDefault: cfg?.workspaceSlug,
|
|
14604
|
+
apiUrl: resolveApiUrl(),
|
|
14605
|
+
apiToken: cfg?.token ?? ""
|
|
14606
|
+
});
|
|
14607
|
+
} catch (err) {
|
|
14608
|
+
return reportCliError(`Workspace selection failed: ${err.message}`, { json });
|
|
14609
|
+
}
|
|
14610
|
+
let row;
|
|
14611
|
+
try {
|
|
14612
|
+
const matches = (await listWorkspaceInstallations(workspace2.id, auth)).filter(
|
|
14613
|
+
(r) => r.module_id === modId
|
|
14614
|
+
);
|
|
14615
|
+
row = matches.find((r) => !isDevInstallKind(r.kind) && isRealRuntimeUrl(r.runtime_url)) ?? matches.find((r) => !isDevInstallKind(r.kind));
|
|
14616
|
+
} catch (err) {
|
|
14617
|
+
return reportCliError(`Could not read workspace installations: ${err.message}`, { json });
|
|
14618
|
+
}
|
|
14619
|
+
if (!row) {
|
|
14620
|
+
return reportCliError(
|
|
14621
|
+
`${code(slug)} has no deployed install in workspace ${code(workspace2.slug)}. Deploy it with ${code(`dashwise module install ${slug} --workspace ${workspace2.slug}`)}, or pass ${code("--install <id>")}.`,
|
|
14622
|
+
{ json }
|
|
14623
|
+
);
|
|
14624
|
+
}
|
|
14625
|
+
return { installationId: row.id, slug };
|
|
14626
|
+
}
|
|
14627
|
+
function sleep5(ms) {
|
|
14628
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
14629
|
+
}
|
|
14630
|
+
|
|
13831
14631
|
// src/program.ts
|
|
13832
14632
|
init_output();
|
|
13833
14633
|
var program = new Command();
|
|
@@ -14595,6 +15395,67 @@ moduleCmd.command("logs [slug]").description(
|
|
|
14595
15395
|
});
|
|
14596
15396
|
}
|
|
14597
15397
|
);
|
|
15398
|
+
moduleCmd.command("runtime [slug-or-set] [slug]").usage("[slug] [--install <id>] [--json] | set [slug] --streaming on|off [options]").description(
|
|
15399
|
+
'Show a deployed install\'s runtime settings: response streaming (on/off, requested vs in effect), memory, whether this version can stream, whether streaming is granted for the workspace, and the last change. `module runtime set --streaming on|off` changes streaming (needs the workspace grant "Module response streaming"; takes about a minute and waits unless --no-wait). `--memory <mb>` is set by Dashwise operators today and is refused with a hint. Exit 3 = a grant is missing (ask your Dashwise admin); exit 4 = `set` stopped waiting before the change finished.'
|
|
15400
|
+
).option("--install <id>", "Target an installation id directly (skips slug + workspace resolution).", parseIntOption).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("--streaming <on|off>", "`set` only: turn response streaming on or off.").option("--memory <mb>", "`set` only: memory in MB (operator-only today).", parseIntOption).option("--reason <text>", "`set` only: why; stored in the audit log.").option("-y, --yes", "`set` only: skip the confirmation prompt.").option("--no-wait", "`set` only: return once the change is accepted instead of waiting for it.").option("--timeout <seconds>", "`set` only: max seconds to wait for the change (default 240).", parseIntOption).option(
|
|
15401
|
+
"--json",
|
|
15402
|
+
'One JSON object on stdout: the runtime view; for `set`, { accepted, operation } (+ "timed_out": true on exit 4); on a refusal or a failed change, { error, hint, needs_grant } (plus accepted and operation when the change ran and failed; error.code USAGE for a bad invocation).'
|
|
15403
|
+
).action(
|
|
15404
|
+
async (first, second, cmdOpts) => {
|
|
15405
|
+
const target = {
|
|
15406
|
+
...cmdOpts.install !== void 0 ? { install: cmdOpts.install } : {},
|
|
15407
|
+
...cmdOpts.workspace !== void 0 ? { workspace: cmdOpts.workspace } : {},
|
|
15408
|
+
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
15409
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
15410
|
+
};
|
|
15411
|
+
if (first === "set") {
|
|
15412
|
+
process.exitCode = await moduleRuntimeSetCommand(second, {
|
|
15413
|
+
...target,
|
|
15414
|
+
...cmdOpts.streaming !== void 0 ? { streaming: cmdOpts.streaming } : {},
|
|
15415
|
+
...cmdOpts.memory !== void 0 ? { memory: cmdOpts.memory } : {},
|
|
15416
|
+
...cmdOpts.reason !== void 0 ? { reason: cmdOpts.reason } : {},
|
|
15417
|
+
...cmdOpts.yes !== void 0 ? { yes: cmdOpts.yes } : {},
|
|
15418
|
+
...cmdOpts.wait === false ? { noWait: true } : {},
|
|
15419
|
+
...cmdOpts.timeout !== void 0 ? { timeoutSec: cmdOpts.timeout } : {}
|
|
15420
|
+
});
|
|
15421
|
+
return;
|
|
15422
|
+
}
|
|
15423
|
+
const setOnly = [
|
|
15424
|
+
cmdOpts.streaming !== void 0 && "--streaming",
|
|
15425
|
+
cmdOpts.memory !== void 0 && "--memory",
|
|
15426
|
+
cmdOpts.reason !== void 0 && "--reason"
|
|
15427
|
+
].filter(Boolean);
|
|
15428
|
+
if (setOnly.length > 0 || second !== void 0) {
|
|
15429
|
+
process.exitCode = reportCliError(
|
|
15430
|
+
setOnly.length > 0 ? `${setOnly.join(", ")} only work with \`set\`, for example: dashwise module runtime set ${first ?? "<slug>"} --streaming on` : `\`dashwise module runtime\` takes one slug (got "${first}" and "${second}"). To change settings: dashwise module runtime set <slug> --streaming on|off`,
|
|
15431
|
+
{ json: cmdOpts.json, code: "USAGE" }
|
|
15432
|
+
);
|
|
15433
|
+
return;
|
|
15434
|
+
}
|
|
15435
|
+
process.exitCode = await moduleRuntimeShowCommand(first, target);
|
|
15436
|
+
}
|
|
15437
|
+
);
|
|
15438
|
+
moduleCmd.command("rebuild [slug]").description(
|
|
15439
|
+
"Rebuild the module's published source with today's platform image and defaults, as a new patch version, optionally with build options, then optionally upgrade installs. Needs the workspace grant \"Module rebuild\". Waits for the build (and the upgrades) unless `--no-wait`. Exit 3 = a grant is missing (ask your Dashwise admin); exit 4 = stopped waiting before the build (or the upgrades) finished."
|
|
15440
|
+
).option("--platform-defaults <mode>", "manifest (default: what module.json says) | on | off.").option("--clean", "Build without the Docker cache and re-pull the base image (same as a normal rebuild until build caching exists).").option("--bump <direction>", "patch (default). minor and major need a Dashwise admin.").option("--rollout <scope>", "none (default) | installs:<id,id> | workspace[:<id>] \u2014 upgrade those installs after the build. Blocked upgrades are reported, never forced.").option("--from-build <id>", "Rebuild the source of this build (default: the build behind the current version).", parseIntOption).option("--reason <text>", "Why; stored in the audit log.").option("--dir <path>", "Project root for the module.json slug lookup (default: current directory).").option("--no-wait", "Return once the build is queued.").option("--timeout <seconds>", "Max seconds to wait for the build and upgrades (default 900).", parseIntOption).option(
|
|
15441
|
+
"--json",
|
|
15442
|
+
'One JSON object on stdout: the final build (the last one seen + "timed_out": true on exit 4), or { error, hint, needs_grant } on a refusal or a failed build or rollout (plus the build when it ran; error.code USAGE for a bad invocation).'
|
|
15443
|
+
).action(
|
|
15444
|
+
async (slug, cmdOpts) => {
|
|
15445
|
+
process.exitCode = await moduleRebuildCommand(slug, {
|
|
15446
|
+
...cmdOpts.platformDefaults !== void 0 ? { platformDefaults: cmdOpts.platformDefaults } : {},
|
|
15447
|
+
...cmdOpts.clean !== void 0 ? { clean: cmdOpts.clean } : {},
|
|
15448
|
+
...cmdOpts.bump !== void 0 ? { bump: cmdOpts.bump } : {},
|
|
15449
|
+
...cmdOpts.rollout !== void 0 ? { rollout: cmdOpts.rollout } : {},
|
|
15450
|
+
...cmdOpts.fromBuild !== void 0 ? { fromBuild: cmdOpts.fromBuild } : {},
|
|
15451
|
+
...cmdOpts.reason !== void 0 ? { reason: cmdOpts.reason } : {},
|
|
15452
|
+
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
15453
|
+
...cmdOpts.wait === false ? { noWait: true } : {},
|
|
15454
|
+
...cmdOpts.timeout !== void 0 ? { timeoutSec: cmdOpts.timeout } : {},
|
|
15455
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
15456
|
+
});
|
|
15457
|
+
}
|
|
15458
|
+
);
|
|
14598
15459
|
moduleCmd.command("destroy-dev-install").description(
|
|
14599
15460
|
"Destroy the local-dev install for the current module. Tears down the install row + dashboard tile; if the module had no other installs and was never published, drops the module too. Local module.json is preserved (only the cached runtime token + install id in auth.json get cleared)."
|
|
14600
15461
|
).option("--dir <path>", "Project root (default: current directory).").option(
|
|
@@ -14717,7 +15578,7 @@ fieldCmd.command("set-type <table-slug> <field-slug> <new-type>").description(
|
|
|
14717
15578
|
}
|
|
14718
15579
|
);
|
|
14719
15580
|
function getVersion() {
|
|
14720
|
-
return "0.
|
|
15581
|
+
return "0.22.0";
|
|
14721
15582
|
}
|
|
14722
15583
|
function parseIntOption(value) {
|
|
14723
15584
|
const n = Number.parseInt(value, 10);
|