@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/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, multiselect, confirm } from '@clack/prompts';
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
- var ApiError, CODE_SHAPE, slugifyFieldName;
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
- fail(
4461
- `Directory ${code(dir)} is not empty. Pass ${code("--force")} to scaffold anyway (existing files will be overwritten).`
4510
+ const linked = entries.filter(
4511
+ (name2) => isFreshCloneEntry(name2) && lstatSync(join(dir, name2)).isSymbolicLink()
4462
4512
  );
4463
- return 1;
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" && existsSync(absPath)) {
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 sleep4 = args.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
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 sleep4(pollIntervalMs);
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.21.0";
15581
+ return "0.22.0";
14721
15582
  }
14722
15583
  function parseIntOption(value) {
14723
15584
  const n = Number.parseInt(value, 10);