@dashai/cli 0.28.1 → 0.30.1
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 +51 -1
- package/dist/bin.js +740 -18
- package/dist/bin.js.map +1 -1
- package/package.json +2 -2
package/dist/bin.js
CHANGED
|
@@ -4,7 +4,7 @@ import { existsSync, readFileSync, writeFileSync, rmSync, mkdirSync, watch, read
|
|
|
4
4
|
import { hostname, tmpdir, platform, homedir } from 'os';
|
|
5
5
|
import { resolve, join, relative, dirname, basename, win32, sep } from 'path';
|
|
6
6
|
import { execFileSync, spawn, spawnSync } from 'child_process';
|
|
7
|
-
import { intro, log, select, isCancel, cancel, spinner, outro,
|
|
7
|
+
import { intro, log, select, isCancel, cancel, spinner, outro, confirm as confirm$1, password, text, multiselect } from '@clack/prompts';
|
|
8
8
|
import { Command, Option } from 'commander';
|
|
9
9
|
import open from 'open';
|
|
10
10
|
import AdmZip from 'adm-zip';
|
|
@@ -364,6 +364,38 @@ async function setOrigins(installationId, allowedOrigins, options = {}) {
|
|
|
364
364
|
options
|
|
365
365
|
);
|
|
366
366
|
}
|
|
367
|
+
async function getModuleBuildLog(moduleId, buildId, options = {}) {
|
|
368
|
+
return apiRequest(
|
|
369
|
+
"GET",
|
|
370
|
+
`/api/modules/${moduleId}/builds/${buildId}/log`,
|
|
371
|
+
void 0,
|
|
372
|
+
options
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
async function listInstallationSecrets(installationId, options = {}) {
|
|
376
|
+
return apiRequest(
|
|
377
|
+
"GET",
|
|
378
|
+
`/api/modules/installations/${installationId}/secrets`,
|
|
379
|
+
void 0,
|
|
380
|
+
options
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
async function setInstallationSecrets(installationId, secrets, options = {}) {
|
|
384
|
+
return apiRequest(
|
|
385
|
+
"PUT",
|
|
386
|
+
`/api/modules/installations/${installationId}/secrets`,
|
|
387
|
+
{ secrets },
|
|
388
|
+
options
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
async function clearInstallationSecrets(installationId, options = {}) {
|
|
392
|
+
await apiRequest(
|
|
393
|
+
"DELETE",
|
|
394
|
+
`/api/modules/installations/${installationId}/secrets`,
|
|
395
|
+
void 0,
|
|
396
|
+
options
|
|
397
|
+
);
|
|
398
|
+
}
|
|
367
399
|
async function createTable(body, options = {}) {
|
|
368
400
|
return apiRequest(
|
|
369
401
|
"POST",
|
|
@@ -14158,8 +14190,8 @@ function needsAdmin(what, module, ask) {
|
|
|
14158
14190
|
const scope = module ? ` for ${module}` : "";
|
|
14159
14191
|
return `${what}${scope} needs a Dashwise admin. Ask your Dashwise admin to ${ask}.`;
|
|
14160
14192
|
}
|
|
14161
|
-
function refusalHint(code2,
|
|
14162
|
-
const ctx =
|
|
14193
|
+
function refusalHint(code2, context2, subject = {}) {
|
|
14194
|
+
const ctx = context2 ?? {};
|
|
14163
14195
|
const module = subject.module ?? str(ctx.module_slug);
|
|
14164
14196
|
const installationId = subject.installationId ?? num(ctx.installation_id);
|
|
14165
14197
|
const installLabel = installationId !== void 0 ? ` (install #${installationId})` : "";
|
|
@@ -15213,9 +15245,9 @@ function surfaceSchemaPushError(err, slug, json) {
|
|
|
15213
15245
|
fail(`Schema push failed: ${err.message}`);
|
|
15214
15246
|
return 1;
|
|
15215
15247
|
}
|
|
15216
|
-
function extractIssues2(
|
|
15217
|
-
if (!
|
|
15218
|
-
const raw =
|
|
15248
|
+
function extractIssues2(context2) {
|
|
15249
|
+
if (!context2) return [];
|
|
15250
|
+
const raw = context2.issues;
|
|
15219
15251
|
if (!Array.isArray(raw)) return [];
|
|
15220
15252
|
return raw.map((i) => {
|
|
15221
15253
|
if (typeof i === "string") return i;
|
|
@@ -19193,8 +19225,16 @@ var MARKERS = [
|
|
|
19193
19225
|
["CODEX_SANDBOX_NETWORK_DISABLED", (v) => v.length > 0],
|
|
19194
19226
|
["CURSOR_AGENT", (v) => v.length > 0],
|
|
19195
19227
|
["DW_AGENT", (v) => v.length > 0],
|
|
19196
|
-
["DASHWISE_AGENT_SESSION", (v) => v === "1" || v === "true"]
|
|
19228
|
+
["DASHWISE_AGENT_SESSION", (v) => v === "1" || v === "true"],
|
|
19229
|
+
["CLAUDE_CODE_ENTRYPOINT", (v) => v.length > 0],
|
|
19230
|
+
["GEMINI_CLI", (v) => v === "1"],
|
|
19231
|
+
["COPILOT_AGENT_SESSION_ID", (v) => v.length > 0],
|
|
19232
|
+
["COPILOT_AGENT_JOB_ID", (v) => v.length > 0],
|
|
19233
|
+
["AI_AGENT", (v) => v.length > 0]
|
|
19197
19234
|
];
|
|
19235
|
+
MARKERS.map(
|
|
19236
|
+
([name]) => name
|
|
19237
|
+
);
|
|
19198
19238
|
function agentSessionMarker(env = process.env) {
|
|
19199
19239
|
for (const [name, test] of MARKERS) {
|
|
19200
19240
|
const value = env[name];
|
|
@@ -20763,7 +20803,10 @@ async function moduleReleaseCommand(slugArg, options) {
|
|
|
20763
20803
|
if (resuming && (options.commitSha !== void 0 || options.bump !== void 0 || options.noWait)) {
|
|
20764
20804
|
return usage2("--build only reads an existing release; do not combine it with --commit-sha, --bump or --no-wait.");
|
|
20765
20805
|
}
|
|
20766
|
-
if (!resuming &&
|
|
20806
|
+
if (!resuming && options.commitSha !== void 0 && !FULL_SHA.test(options.commitSha)) {
|
|
20807
|
+
return usage2("--commit-sha requires the full 40-character SHA already pushed to the linked repository.");
|
|
20808
|
+
}
|
|
20809
|
+
if (!resuming && options.commitSha === void 0 && !options.defaultBranchHead) {
|
|
20767
20810
|
return usage2("--commit-sha requires the full 40-character SHA already pushed to the linked repository.");
|
|
20768
20811
|
}
|
|
20769
20812
|
const bump = options.bump ?? "minor";
|
|
@@ -20786,7 +20829,7 @@ async function moduleReleaseCommand(slugArg, options) {
|
|
|
20786
20829
|
return usage2("Release creates a version. Pass --yes to confirm in non-interactive or --json mode.");
|
|
20787
20830
|
}
|
|
20788
20831
|
const answer = await select({
|
|
20789
|
-
message: `Release ${slug} at ${options.commitSha} with a ${bump} bump?`,
|
|
20832
|
+
message: `Release ${slug} at ${options.commitSha ?? "the head of its default branch"} with a ${bump} bump?`,
|
|
20790
20833
|
options: [{ value: "cancel", label: "Cancel" }, { value: "release", label: "Release this revision" }],
|
|
20791
20834
|
initialValue: "cancel"
|
|
20792
20835
|
});
|
|
@@ -20801,7 +20844,7 @@ async function moduleReleaseCommand(slugArg, options) {
|
|
|
20801
20844
|
if (!resuming) {
|
|
20802
20845
|
try {
|
|
20803
20846
|
initial = await apiRequest("POST", `/api/modules/${moduleId}/builds`, {
|
|
20804
|
-
commit_sha: pinnedSha,
|
|
20847
|
+
...pinnedSha !== void 0 ? { commit_sha: pinnedSha } : {},
|
|
20805
20848
|
version_bump: bump
|
|
20806
20849
|
}, auth);
|
|
20807
20850
|
} catch (err) {
|
|
@@ -20815,12 +20858,13 @@ async function moduleReleaseCommand(slugArg, options) {
|
|
|
20815
20858
|
if (!json && isTransientPollError(err)) info("Check the module build history in Dashwise before retrying; the release may already be queued.");
|
|
20816
20859
|
return reportRefusal(err, { module: slug }, { json, action: "Release" });
|
|
20817
20860
|
}
|
|
20818
|
-
if (!isRepositoryBuild(initial, moduleId) || initial.commit_sha.toLowerCase() !== pinnedSha) {
|
|
20861
|
+
if (!isRepositoryBuild(initial, moduleId) || pinnedSha !== void 0 && initial.commit_sha.toLowerCase() !== pinnedSha) {
|
|
20819
20862
|
return reportCliError("Release response did not confirm the requested repository revision. Check the module build history before retrying.", { json });
|
|
20820
20863
|
}
|
|
20821
20864
|
buildId = initial.id;
|
|
20822
20865
|
}
|
|
20823
20866
|
const id = buildId;
|
|
20867
|
+
options.onBuildId?.(id);
|
|
20824
20868
|
const resume = `dashwise module release ${slug} --build ${id}`;
|
|
20825
20869
|
if (!json) info(`Build #${id}. Resume checking: ${code(resume)}`);
|
|
20826
20870
|
if (options.noWait && isRepositoryBuild(initial, moduleId) && (initial.status === "queued" || initial.status === "building")) {
|
|
@@ -20859,6 +20903,13 @@ async function moduleReleaseCommand(slugArg, options) {
|
|
|
20859
20903
|
}
|
|
20860
20904
|
if (json) out(JSON.stringify(value));
|
|
20861
20905
|
else success(`Released ${slug} v${value.version} (build #${id}, revision ${pinnedSha}).`);
|
|
20906
|
+
options.onReleased?.({
|
|
20907
|
+
id,
|
|
20908
|
+
module_id: moduleId,
|
|
20909
|
+
commit_sha: pinnedSha,
|
|
20910
|
+
version: value.version,
|
|
20911
|
+
module_version_id: value.module_version_id
|
|
20912
|
+
});
|
|
20862
20913
|
return 0;
|
|
20863
20914
|
}
|
|
20864
20915
|
if (value.status === "failed") {
|
|
@@ -20900,6 +20951,248 @@ async function moduleReleaseCommand(slugArg, options) {
|
|
|
20900
20951
|
}
|
|
20901
20952
|
}
|
|
20902
20953
|
|
|
20954
|
+
// src/commands/module/build-log.ts
|
|
20955
|
+
init_api_client();
|
|
20956
|
+
init_config();
|
|
20957
|
+
init_manifest_io();
|
|
20958
|
+
init_output();
|
|
20959
|
+
init_permission_hint();
|
|
20960
|
+
var RUNNING = /* @__PURE__ */ new Set(["queued", "building"]);
|
|
20961
|
+
var FOLLOW_BASE_MS = 5e3;
|
|
20962
|
+
var FOLLOW_MAX_MS = 1e4;
|
|
20963
|
+
var MIN_OVERLAP = 64;
|
|
20964
|
+
function newLogText(printed, next) {
|
|
20965
|
+
if (printed === "") return { text: next, moved: false };
|
|
20966
|
+
if (next.startsWith(printed)) return { text: next.slice(printed.length), moved: false };
|
|
20967
|
+
const min = Math.min(MIN_OVERLAP, printed.length);
|
|
20968
|
+
const head = next.slice(0, min);
|
|
20969
|
+
for (let j = printed.indexOf(head); j !== -1; j = printed.indexOf(head, j + 1)) {
|
|
20970
|
+
const tail = printed.slice(j);
|
|
20971
|
+
if (tail.length >= min && next.startsWith(tail)) {
|
|
20972
|
+
return { text: next.slice(tail.length), moved: false };
|
|
20973
|
+
}
|
|
20974
|
+
}
|
|
20975
|
+
return { text: next, moved: true };
|
|
20976
|
+
}
|
|
20977
|
+
function sanitizeForTerminal(text4, keepColour = false) {
|
|
20978
|
+
const ESCAPE = /\u001b(?:\[[0-?]*[ -/]*[@-~]|\][\s\S]*?(?:\u0007|\u001b\\|$)|[PX^_][\s\S]*?(?:\u001b\\|$)|[ -/]*[0-~]|$)/g;
|
|
20979
|
+
const SGR = /^\u001b\[[0-9;]*m$/;
|
|
20980
|
+
return text4.replace(/\r\n/g, "\n").replace(ESCAPE, (seq) => keepColour && SGR.test(seq) ? seq : "").replace(
|
|
20981
|
+
/[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g,
|
|
20982
|
+
(ch, at, all) => (
|
|
20983
|
+
// Keep the ESC that opens a kept SGR sequence; drop every other control.
|
|
20984
|
+
ch === "\x1B" && keepColour && SGR.test((/^\u001b\[[0-9;]*m/.exec(all.slice(at)) ?? [""])[0]) ? ch : ""
|
|
20985
|
+
)
|
|
20986
|
+
);
|
|
20987
|
+
}
|
|
20988
|
+
async function moduleBuildLogCommand(slugArg, buildIdArg, options) {
|
|
20989
|
+
const json = options.json;
|
|
20990
|
+
const usage2 = (message) => reportCliError(message, { json, code: "USAGE" });
|
|
20991
|
+
let slug = slugArg;
|
|
20992
|
+
let rawId = buildIdArg;
|
|
20993
|
+
if (rawId === void 0 && slug !== void 0 && /^\d+$/.test(slug)) {
|
|
20994
|
+
rawId = slug;
|
|
20995
|
+
slug = void 0;
|
|
20996
|
+
}
|
|
20997
|
+
const buildId = rawId === void 0 ? Number.NaN : Number(rawId);
|
|
20998
|
+
if (!Number.isSafeInteger(buildId) || buildId <= 0) {
|
|
20999
|
+
return usage2("Pass the build id: `dashwise module build-log [slug] <buildId>` (ids are in `dashwise module status`).");
|
|
21000
|
+
}
|
|
21001
|
+
if (json && options.follow) return usage2("--follow streams text; drop --json.");
|
|
21002
|
+
if (!slug) {
|
|
21003
|
+
try {
|
|
21004
|
+
const local = readManifest(options.dir)?.module?.slug;
|
|
21005
|
+
if (typeof local === "string" && local) slug = local;
|
|
21006
|
+
} catch {
|
|
21007
|
+
}
|
|
21008
|
+
}
|
|
21009
|
+
if (!slug) return usage2("Pass a module slug or run from a directory with a valid module.json.");
|
|
21010
|
+
const timeoutMs = (options.timeoutSec ?? 900) * 1e3;
|
|
21011
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return usage2("--timeout expects a positive number of seconds.");
|
|
21012
|
+
const cfg = readConfig();
|
|
21013
|
+
if (!isLoggedIn() || !cfg) return reportCliError("Not logged in. Run `dashwise login` first.", { json });
|
|
21014
|
+
const auth = { baseUrl: resolveApiUrl(), auth: { token: cfg.token } };
|
|
21015
|
+
let moduleId;
|
|
21016
|
+
try {
|
|
21017
|
+
const mod = await getModuleBySlug(slug, auth);
|
|
21018
|
+
moduleId = mod.id;
|
|
21019
|
+
slug = mod.slug;
|
|
21020
|
+
} catch (err) {
|
|
21021
|
+
return reportRefusal(err, { module: slug }, { json, action: "Resolving the module" });
|
|
21022
|
+
}
|
|
21023
|
+
const read = async () => {
|
|
21024
|
+
try {
|
|
21025
|
+
return await getModuleBuildLog(moduleId, buildId, auth);
|
|
21026
|
+
} catch (err) {
|
|
21027
|
+
if (err instanceof ApiError && err.code === "FEATURE_DISABLED") {
|
|
21028
|
+
return reportCliError(
|
|
21029
|
+
"Build logs are not available on this Dashwise yet (the backend serves them once its build-log scrubber is switched on).",
|
|
21030
|
+
{ json }
|
|
21031
|
+
);
|
|
21032
|
+
}
|
|
21033
|
+
return reportRefusal(err, { module: slug }, { json, action: `Reading the log of build #${buildId}` });
|
|
21034
|
+
}
|
|
21035
|
+
};
|
|
21036
|
+
const first = await read();
|
|
21037
|
+
if (typeof first === "number") return first;
|
|
21038
|
+
if (json) {
|
|
21039
|
+
out(JSON.stringify(first));
|
|
21040
|
+
return 0;
|
|
21041
|
+
}
|
|
21042
|
+
const printLog = (view, printed2) => {
|
|
21043
|
+
if (!view.available || view.text === null) return printed2;
|
|
21044
|
+
const delta = newLogText(printed2, view.text);
|
|
21045
|
+
if (delta.moved) info(dim("\u2026 (earlier lines scrolled out of the window the server keeps)"));
|
|
21046
|
+
if (delta.text) {
|
|
21047
|
+
const safe = sanitizeForTerminal(delta.text, Boolean(process.stdout.isTTY));
|
|
21048
|
+
process.stdout.write(safe.endsWith("\n") ? safe : `${safe}
|
|
21049
|
+
`);
|
|
21050
|
+
}
|
|
21051
|
+
return view.text;
|
|
21052
|
+
};
|
|
21053
|
+
if (!first.available && !(options.follow && RUNNING.has(first.status))) {
|
|
21054
|
+
info(`Build #${buildId} (${first.status}) has no log: it did not run on the build service, or the log expired.`);
|
|
21055
|
+
return 0;
|
|
21056
|
+
}
|
|
21057
|
+
if (first.truncated) info(dim("(showing the newest part of the log)"));
|
|
21058
|
+
let printed = printLog(first, "");
|
|
21059
|
+
const footer = (view) => {
|
|
21060
|
+
if (view.redactions > 0) info(dim(`${view.redactions} value(s) were hidden by the server's scrubber.`));
|
|
21061
|
+
};
|
|
21062
|
+
if (!options.follow || !RUNNING.has(first.status)) {
|
|
21063
|
+
footer(first);
|
|
21064
|
+
return 0;
|
|
21065
|
+
}
|
|
21066
|
+
const deadline = Date.now() + timeoutMs;
|
|
21067
|
+
let last = first;
|
|
21068
|
+
const base = options.pollIntervalMs ?? FOLLOW_BASE_MS;
|
|
21069
|
+
const cap = options.pollIntervalMs !== void 0 ? options.pollIntervalMs * 2 : FOLLOW_MAX_MS;
|
|
21070
|
+
let interval = base;
|
|
21071
|
+
while (Date.now() < deadline) {
|
|
21072
|
+
await new Promise(
|
|
21073
|
+
(resolve16) => setTimeout(resolve16, Math.min(interval, Math.max(0, deadline - Date.now())))
|
|
21074
|
+
);
|
|
21075
|
+
let view;
|
|
21076
|
+
try {
|
|
21077
|
+
view = await getModuleBuildLog(moduleId, buildId, auth);
|
|
21078
|
+
} catch (err) {
|
|
21079
|
+
if (isTransientPollError(err)) continue;
|
|
21080
|
+
return reportRefusal(err, { module: slug }, { json, action: `Reading the log of build #${buildId}` });
|
|
21081
|
+
}
|
|
21082
|
+
const grew = view.text !== null && view.text !== last.text;
|
|
21083
|
+
interval = grew ? base : Math.min(cap, Math.round(interval * 1.5));
|
|
21084
|
+
printed = printLog(view, printed);
|
|
21085
|
+
last = view;
|
|
21086
|
+
if (!RUNNING.has(view.status)) {
|
|
21087
|
+
footer(view);
|
|
21088
|
+
if (view.status === "failed") {
|
|
21089
|
+
fail(`Build #${buildId} failed.`);
|
|
21090
|
+
return 1;
|
|
21091
|
+
}
|
|
21092
|
+
info(`Build #${buildId}: ${view.status}.`);
|
|
21093
|
+
return 0;
|
|
21094
|
+
}
|
|
21095
|
+
}
|
|
21096
|
+
warn(`Stopped following build #${buildId} (still ${last.status}). Resume: ${code(`dashwise module build-log ${slug} ${buildId} --follow`)}`);
|
|
21097
|
+
return EXIT_TIMED_OUT;
|
|
21098
|
+
}
|
|
21099
|
+
|
|
21100
|
+
// src/commands/module/ship.ts
|
|
21101
|
+
init_api_client();
|
|
21102
|
+
init_config();
|
|
21103
|
+
init_manifest_io();
|
|
21104
|
+
init_output();
|
|
21105
|
+
init_permission_hint();
|
|
21106
|
+
async function moduleShipCommand(slugArg, options) {
|
|
21107
|
+
const marker = agentSessionMarker();
|
|
21108
|
+
if (marker !== null) {
|
|
21109
|
+
fail(
|
|
21110
|
+
`\`dashwise module ship\` takes a module live, which a person does (detected an AI agent session: ${marker}). An agent may release (${code("dashwise module release --commit-sha <sha> --yes")}); a person then installs it.`
|
|
21111
|
+
);
|
|
21112
|
+
return 2;
|
|
21113
|
+
}
|
|
21114
|
+
if (options.build !== void 0 && options.commitSha !== void 0) {
|
|
21115
|
+
fail("--build resumes an existing release; do not combine it with --commit-sha.");
|
|
21116
|
+
return 1;
|
|
21117
|
+
}
|
|
21118
|
+
const commitSha = options.build !== void 0 ? void 0 : options.commitSha ?? await defaultBranchHead(slugArg, options.dir);
|
|
21119
|
+
if (options.commitSha !== void 0) {
|
|
21120
|
+
info(`Releasing ${options.commitSha.slice(0, 12)} (--commit-sha).`);
|
|
21121
|
+
}
|
|
21122
|
+
let released = null;
|
|
21123
|
+
let buildId = options.build;
|
|
21124
|
+
const releaseExit = await moduleReleaseCommand(slugArg, {
|
|
21125
|
+
...options.dir !== void 0 ? { dir: options.dir } : {},
|
|
21126
|
+
...commitSha !== void 0 ? { commitSha } : {},
|
|
21127
|
+
...options.build !== void 0 ? { build: options.build } : {},
|
|
21128
|
+
...options.bump !== void 0 && options.build === void 0 ? { bump: options.bump } : {},
|
|
21129
|
+
...options.timeoutSec !== void 0 ? { timeoutSec: options.timeoutSec } : {},
|
|
21130
|
+
...options.pollIntervalMs !== void 0 ? { pollIntervalMs: options.pollIntervalMs } : {},
|
|
21131
|
+
// `ship` is the person's go for the release; the install below asks.
|
|
21132
|
+
yes: true,
|
|
21133
|
+
defaultBranchHead: commitSha === void 0 && options.build === void 0,
|
|
21134
|
+
onBuildId: (id) => {
|
|
21135
|
+
buildId = id;
|
|
21136
|
+
},
|
|
21137
|
+
onReleased: (build2) => {
|
|
21138
|
+
released = build2;
|
|
21139
|
+
}
|
|
21140
|
+
});
|
|
21141
|
+
if (releaseExit === EXIT_TIMED_OUT && buildId !== void 0) {
|
|
21142
|
+
info(
|
|
21143
|
+
`Resume the ship (wait for build #${buildId}, then install it): ${code(["dashwise module ship", slugArg, `--build ${buildId}`].filter(Boolean).join(" "))}`
|
|
21144
|
+
);
|
|
21145
|
+
}
|
|
21146
|
+
if (releaseExit !== 0) return releaseExit;
|
|
21147
|
+
const build = released;
|
|
21148
|
+
if (!build) {
|
|
21149
|
+
fail("The release finished without naming its version; nothing was installed.");
|
|
21150
|
+
return 1;
|
|
21151
|
+
}
|
|
21152
|
+
info(`Installing v${build.version} (build #${build.id}, revision ${build.commit_sha.slice(0, 12)}).`);
|
|
21153
|
+
return moduleInstallCommand(slugArg, {
|
|
21154
|
+
...options.dir !== void 0 ? { dir: options.dir } : {},
|
|
21155
|
+
...options.workspace !== void 0 ? { workspace: options.workspace } : {},
|
|
21156
|
+
...options.installation !== void 0 ? { installation: options.installation } : {},
|
|
21157
|
+
...options.keepDevInstall !== void 0 ? { keepDevInstall: options.keepDevInstall } : {},
|
|
21158
|
+
...options.replaceDevInstall !== void 0 ? { replaceDevInstall: options.replaceDevInstall } : {},
|
|
21159
|
+
...options.yes !== void 0 ? { yes: options.yes } : {},
|
|
21160
|
+
version: build.version
|
|
21161
|
+
});
|
|
21162
|
+
}
|
|
21163
|
+
async function defaultBranchHead(slugArg, dir) {
|
|
21164
|
+
const cfg = readConfig();
|
|
21165
|
+
let slug = slugArg;
|
|
21166
|
+
if (!slug) {
|
|
21167
|
+
try {
|
|
21168
|
+
slug = readManifest(dir)?.module?.slug;
|
|
21169
|
+
} catch {
|
|
21170
|
+
slug = void 0;
|
|
21171
|
+
}
|
|
21172
|
+
}
|
|
21173
|
+
if (!slug || !cfg || !isLoggedIn()) return void 0;
|
|
21174
|
+
const auth = { baseUrl: resolveApiUrl(), auth: { token: cfg.token } };
|
|
21175
|
+
try {
|
|
21176
|
+
const mod = await getModuleBySlug(slug, auth);
|
|
21177
|
+
const plan = await apiRequest("GET", `/api/modules/${mod.id}/builds/release-plan`, void 0, auth);
|
|
21178
|
+
const sha = plan.default_branch_sha ?? void 0;
|
|
21179
|
+
if (!sha || !/^[0-9a-f]{40}$/i.test(sha)) {
|
|
21180
|
+
info("Releasing the head of the default branch.");
|
|
21181
|
+
return void 0;
|
|
21182
|
+
}
|
|
21183
|
+
info(`Releasing ${sha.slice(0, 12)} from ${plan.default_branch ?? "the default branch"}.`);
|
|
21184
|
+
if (plan.pending_branch) {
|
|
21185
|
+
warn(
|
|
21186
|
+
`Newer work on ${plan.pending_branch.name}${plan.pending_branch.commits_ahead ? ` (${plan.pending_branch.commits_ahead} commit(s) ahead)` : ""} is not in this release: merge it to ${plan.default_branch ?? "the default branch"} first to ship it.`
|
|
21187
|
+
);
|
|
21188
|
+
}
|
|
21189
|
+
return sha.toLowerCase();
|
|
21190
|
+
} catch {
|
|
21191
|
+
info("Releasing the head of the default branch.");
|
|
21192
|
+
return void 0;
|
|
21193
|
+
}
|
|
21194
|
+
}
|
|
21195
|
+
|
|
20903
21196
|
// src/commands/module/status.ts
|
|
20904
21197
|
init_api_client();
|
|
20905
21198
|
init_config();
|
|
@@ -21760,9 +22053,9 @@ async function moduleValidateCommand(options) {
|
|
|
21760
22053
|
return 1;
|
|
21761
22054
|
}
|
|
21762
22055
|
}
|
|
21763
|
-
function readIssues(
|
|
21764
|
-
if (!
|
|
21765
|
-
const v =
|
|
22056
|
+
function readIssues(context2) {
|
|
22057
|
+
if (!context2) return [];
|
|
22058
|
+
const v = context2["issues"];
|
|
21766
22059
|
if (!Array.isArray(v)) return [];
|
|
21767
22060
|
return v.map((entry) => {
|
|
21768
22061
|
if (!entry || typeof entry !== "object") return null;
|
|
@@ -23344,9 +23637,9 @@ async function pushManifest(ctx) {
|
|
|
23344
23637
|
return { ok: false, rejected: false };
|
|
23345
23638
|
}
|
|
23346
23639
|
}
|
|
23347
|
-
function extractIssues(
|
|
23348
|
-
if (!
|
|
23349
|
-
const raw =
|
|
23640
|
+
function extractIssues(context2) {
|
|
23641
|
+
if (!context2) return [];
|
|
23642
|
+
const raw = context2.issues;
|
|
23350
23643
|
if (!Array.isArray(raw)) return [];
|
|
23351
23644
|
return raw.map((i) => {
|
|
23352
23645
|
if (typeof i === "string") return i;
|
|
@@ -24130,6 +24423,356 @@ function surfaceOriginsError(err) {
|
|
|
24130
24423
|
return 1;
|
|
24131
24424
|
}
|
|
24132
24425
|
|
|
24426
|
+
// src/commands/secrets.ts
|
|
24427
|
+
init_api_client();
|
|
24428
|
+
init_output();
|
|
24429
|
+
var KEY_SHAPE = /^[A-Z][A-Z0-9_]{0,63}$/;
|
|
24430
|
+
function context(opts) {
|
|
24431
|
+
try {
|
|
24432
|
+
return loadContext5(opts);
|
|
24433
|
+
} catch (err) {
|
|
24434
|
+
fail(err.message);
|
|
24435
|
+
return null;
|
|
24436
|
+
}
|
|
24437
|
+
}
|
|
24438
|
+
function interactive() {
|
|
24439
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
24440
|
+
}
|
|
24441
|
+
async function secretsListCommand(options) {
|
|
24442
|
+
const ctx = context(options);
|
|
24443
|
+
if (!ctx) return 1;
|
|
24444
|
+
let resp;
|
|
24445
|
+
try {
|
|
24446
|
+
resp = await listInstallationSecrets(ctx.installationId, ctx.requestOptions);
|
|
24447
|
+
} catch (err) {
|
|
24448
|
+
return surfaceSecretsError(err);
|
|
24449
|
+
}
|
|
24450
|
+
const set = new Set(resp.set);
|
|
24451
|
+
const declared = new Set(resp.declared.map((d) => d.key));
|
|
24452
|
+
if (options.json) {
|
|
24453
|
+
process.stdout.write(
|
|
24454
|
+
`${JSON.stringify(
|
|
24455
|
+
{
|
|
24456
|
+
installation_id: ctx.installationId,
|
|
24457
|
+
declared: resp.declared.map((d) => ({
|
|
24458
|
+
key: d.key,
|
|
24459
|
+
description: d.description,
|
|
24460
|
+
set: set.has(d.key)
|
|
24461
|
+
})),
|
|
24462
|
+
// Stored but no longer declared by the installed version: not
|
|
24463
|
+
// delivered to the app; `set` removes them.
|
|
24464
|
+
undeclared_set: resp.set.filter((k) => !declared.has(k))
|
|
24465
|
+
},
|
|
24466
|
+
null,
|
|
24467
|
+
2
|
|
24468
|
+
)}
|
|
24469
|
+
`
|
|
24470
|
+
);
|
|
24471
|
+
return 0;
|
|
24472
|
+
}
|
|
24473
|
+
info(`Secrets of ${code(ctx.slug)} ${dim("(names only; values are never shown)")}:`);
|
|
24474
|
+
if (resp.declared.length === 0) {
|
|
24475
|
+
info(` ${dim("(the module declares none in module.json#runtime.secrets)")}`);
|
|
24476
|
+
}
|
|
24477
|
+
for (const d of resp.declared) {
|
|
24478
|
+
const state = set.has(d.key) ? "set" : dim("not set");
|
|
24479
|
+
process.stdout.write(
|
|
24480
|
+
` ${d.key} ${state}${d.description ? ` ${dim(d.description)}` : ""}
|
|
24481
|
+
`
|
|
24482
|
+
);
|
|
24483
|
+
}
|
|
24484
|
+
const stray = resp.set.filter((k) => !declared.has(k));
|
|
24485
|
+
if (stray.length > 0) {
|
|
24486
|
+
warn(
|
|
24487
|
+
`Stored but no longer declared (not delivered): ${stray.join(", ")}. The next ${code("dashwise secrets set")} removes them.`
|
|
24488
|
+
);
|
|
24489
|
+
}
|
|
24490
|
+
return 0;
|
|
24491
|
+
}
|
|
24492
|
+
function parseEnvFile(text4) {
|
|
24493
|
+
const out2 = {};
|
|
24494
|
+
const lines = text4.split(/\r?\n/);
|
|
24495
|
+
for (let i = 0; i < lines.length; i++) {
|
|
24496
|
+
const n = i + 1;
|
|
24497
|
+
const line = lines[i].replace(/^\s+/, "");
|
|
24498
|
+
if (line === "" || line.startsWith("#")) continue;
|
|
24499
|
+
const m = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)$/.exec(line);
|
|
24500
|
+
if (!m) {
|
|
24501
|
+
throw new Error(`line ${n} is not KEY=value (no spaces around "=")`);
|
|
24502
|
+
}
|
|
24503
|
+
const [, key, raw] = m;
|
|
24504
|
+
out2[key] = parseValue(raw, n);
|
|
24505
|
+
}
|
|
24506
|
+
return out2;
|
|
24507
|
+
}
|
|
24508
|
+
function parseValue(raw, n) {
|
|
24509
|
+
const quote2 = raw[0];
|
|
24510
|
+
if (quote2 === '"' || quote2 === "'") {
|
|
24511
|
+
let value = "";
|
|
24512
|
+
let j = 1;
|
|
24513
|
+
for (; j < raw.length; j++) {
|
|
24514
|
+
const ch = raw[j];
|
|
24515
|
+
if (ch === quote2) break;
|
|
24516
|
+
if (quote2 === '"' && ch === "\\" && j + 1 < raw.length) {
|
|
24517
|
+
const next = raw[++j];
|
|
24518
|
+
value += next === "n" ? "\n" : next === '"' || next === "\\" ? next : `\\${next}`;
|
|
24519
|
+
continue;
|
|
24520
|
+
}
|
|
24521
|
+
value += ch;
|
|
24522
|
+
}
|
|
24523
|
+
if (j >= raw.length) throw new Error(`line ${n}: the quoted value is not closed`);
|
|
24524
|
+
const rest = raw.slice(j + 1);
|
|
24525
|
+
if (rest.trim() !== "" && !/^\s+#/.test(rest)) {
|
|
24526
|
+
throw new Error(`line ${n}: only a " # comment" may follow the closing quote`);
|
|
24527
|
+
}
|
|
24528
|
+
return value;
|
|
24529
|
+
}
|
|
24530
|
+
if (/^\s|\s$/.test(raw)) {
|
|
24531
|
+
throw new Error(
|
|
24532
|
+
`line ${n}: an unquoted value may not start or end with a space; quote it`
|
|
24533
|
+
);
|
|
24534
|
+
}
|
|
24535
|
+
if (/\s#/.test(raw)) {
|
|
24536
|
+
throw new Error(
|
|
24537
|
+
`line ${n}: an unquoted value may not contain " #" (comment or value?); quote it`
|
|
24538
|
+
);
|
|
24539
|
+
}
|
|
24540
|
+
return raw;
|
|
24541
|
+
}
|
|
24542
|
+
async function readStdin() {
|
|
24543
|
+
const chunks = [];
|
|
24544
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
24545
|
+
return Buffer.concat(chunks).toString("utf8").replace(/\r?\n$/, "");
|
|
24546
|
+
}
|
|
24547
|
+
async function secretsSetCommand(keyArgs, options) {
|
|
24548
|
+
const inline = keyArgs.findIndex((k) => k.includes("=") || !KEY_SHAPE.test(k));
|
|
24549
|
+
if (inline !== -1) {
|
|
24550
|
+
fail(
|
|
24551
|
+
`Argument ${inline + 1} is not a secret key name; values must not be passed as arguments (shell history). Enter values at the prompt, or use ${code("--from-stdin")} or ${code("--from-env-file <path>")}.`
|
|
24552
|
+
);
|
|
24553
|
+
return 1;
|
|
24554
|
+
}
|
|
24555
|
+
if (options.fromStdin && options.fromEnvFile) {
|
|
24556
|
+
fail(`Use one of ${code("--from-stdin")} or ${code("--from-env-file")}, not both.`);
|
|
24557
|
+
return 1;
|
|
24558
|
+
}
|
|
24559
|
+
if (options.keys && !options.fromEnvFile) {
|
|
24560
|
+
fail(`${code("--keys")} goes with ${code("--from-env-file")}.`);
|
|
24561
|
+
return 1;
|
|
24562
|
+
}
|
|
24563
|
+
const ctx = context(options);
|
|
24564
|
+
if (!ctx) return 1;
|
|
24565
|
+
let current;
|
|
24566
|
+
try {
|
|
24567
|
+
current = await listInstallationSecrets(ctx.installationId, ctx.requestOptions);
|
|
24568
|
+
} catch (err) {
|
|
24569
|
+
return surfaceSecretsError(err);
|
|
24570
|
+
}
|
|
24571
|
+
const declared = current.declared.map((d) => d.key);
|
|
24572
|
+
let keys = [...new Set(keyArgs)];
|
|
24573
|
+
const values = {};
|
|
24574
|
+
if (options.fromEnvFile) {
|
|
24575
|
+
let fileValues;
|
|
24576
|
+
try {
|
|
24577
|
+
fileValues = parseEnvFile(readFileSync(options.fromEnvFile, "utf8"));
|
|
24578
|
+
} catch (err) {
|
|
24579
|
+
fail(`Could not read ${code(options.fromEnvFile)}: ${err.message}`);
|
|
24580
|
+
return 1;
|
|
24581
|
+
}
|
|
24582
|
+
const wanted = options.keys ? options.keys.split(",").map((k) => k.trim()).filter(Boolean) : keys.length > 0 ? keys : declared.filter((k) => k in fileValues);
|
|
24583
|
+
const undeclaredAt2 = wanted.findIndex(
|
|
24584
|
+
(k) => !KEY_SHAPE.test(k) || !declared.includes(k)
|
|
24585
|
+
);
|
|
24586
|
+
if (undeclaredAt2 !== -1) {
|
|
24587
|
+
fail(
|
|
24588
|
+
`${options.keys ? `Item ${undeclaredAt2 + 1} of --keys` : `Argument ${undeclaredAt2 + 1}`} is not a declared key; values must not be passed as arguments. Declared: ${declared.length > 0 ? declared.join(", ") : "none"}.`
|
|
24589
|
+
);
|
|
24590
|
+
return 1;
|
|
24591
|
+
}
|
|
24592
|
+
const missing = wanted.filter((k) => !(k in fileValues));
|
|
24593
|
+
if (missing.length > 0) {
|
|
24594
|
+
fail(`Not in ${code(options.fromEnvFile)}: ${missing.join(", ")}.`);
|
|
24595
|
+
return 1;
|
|
24596
|
+
}
|
|
24597
|
+
for (const k of wanted) values[k] = fileValues[k];
|
|
24598
|
+
keys = wanted;
|
|
24599
|
+
} else if (options.fromStdin) {
|
|
24600
|
+
if (keys.length !== 1) {
|
|
24601
|
+
fail(`${code("--from-stdin")} reads the value of exactly one key: ${code("dashwise secrets set KEY --from-stdin")}.`);
|
|
24602
|
+
return 1;
|
|
24603
|
+
}
|
|
24604
|
+
if (process.stdin.isTTY) {
|
|
24605
|
+
info(`Nothing is piped into stdin; asking at a hidden prompt instead.`);
|
|
24606
|
+
} else {
|
|
24607
|
+
values[keys[0]] = await readStdin();
|
|
24608
|
+
}
|
|
24609
|
+
} else {
|
|
24610
|
+
if (keys.length === 0) {
|
|
24611
|
+
fail(
|
|
24612
|
+
`Name the keys to set (${declared.length > 0 ? declared.join(", ") : "none declared"}), or use ${code("--from-env-file <path>")}.`
|
|
24613
|
+
);
|
|
24614
|
+
return 1;
|
|
24615
|
+
}
|
|
24616
|
+
if (!interactive()) {
|
|
24617
|
+
fail(`No terminal to prompt in. Use ${code("--from-stdin")} or ${code("--from-env-file <path>")}.`);
|
|
24618
|
+
return 1;
|
|
24619
|
+
}
|
|
24620
|
+
}
|
|
24621
|
+
if (keys.length === 0) {
|
|
24622
|
+
fail(`Nothing to set: no declared key was found.`);
|
|
24623
|
+
return 1;
|
|
24624
|
+
}
|
|
24625
|
+
const undeclaredAt = keys.findIndex(
|
|
24626
|
+
(k) => !KEY_SHAPE.test(k) || !declared.includes(k)
|
|
24627
|
+
);
|
|
24628
|
+
if (undeclaredAt !== -1) {
|
|
24629
|
+
const where = options.keys ? `Item ${undeclaredAt + 1} of --keys` : keyArgs.length > 0 ? `Argument ${keyArgs.indexOf(keys[undeclaredAt]) + 1}` : `Key ${undeclaredAt + 1}`;
|
|
24630
|
+
fail(
|
|
24631
|
+
`${where} is not a declared key; values must not be passed as arguments. Declared: ${declared.length > 0 ? declared.join(", ") : "none"}.`
|
|
24632
|
+
);
|
|
24633
|
+
return 1;
|
|
24634
|
+
}
|
|
24635
|
+
const removed = current.set.filter((k) => !keys.includes(k));
|
|
24636
|
+
if (removed.length > 0 && !options.yes) {
|
|
24637
|
+
if (!interactive()) {
|
|
24638
|
+
fail(
|
|
24639
|
+
`This replaces the whole map and would remove ${removed.join(", ")}. Include them, or pass ${code("--yes")} to remove them.`
|
|
24640
|
+
);
|
|
24641
|
+
return 1;
|
|
24642
|
+
}
|
|
24643
|
+
const ok = await confirm$1({
|
|
24644
|
+
message: `This replaces the whole map: ${removed.join(", ")} will be removed. Continue?`,
|
|
24645
|
+
initialValue: false
|
|
24646
|
+
});
|
|
24647
|
+
if (isCancel(ok) || ok !== true) {
|
|
24648
|
+
info("Nothing changed.");
|
|
24649
|
+
return 1;
|
|
24650
|
+
}
|
|
24651
|
+
}
|
|
24652
|
+
for (const k of keys) {
|
|
24653
|
+
if (k in values) continue;
|
|
24654
|
+
const entered = await password({ message: `Value for ${k}` });
|
|
24655
|
+
if (isCancel(entered)) {
|
|
24656
|
+
info("Nothing changed.");
|
|
24657
|
+
return 1;
|
|
24658
|
+
}
|
|
24659
|
+
values[k] = entered;
|
|
24660
|
+
}
|
|
24661
|
+
const empty = keys.filter((k) => values[k] === "");
|
|
24662
|
+
if (empty.length > 0) {
|
|
24663
|
+
fail(`Empty value for ${empty.join(", ")}; nothing changed. (To remove every value: ${code("dashwise secrets clear")}.)`);
|
|
24664
|
+
return 1;
|
|
24665
|
+
}
|
|
24666
|
+
try {
|
|
24667
|
+
const now = await listInstallationSecrets(ctx.installationId, ctx.requestOptions);
|
|
24668
|
+
const before = [...current.set].sort().join(",");
|
|
24669
|
+
if ([...now.set].sort().join(",") !== before) {
|
|
24670
|
+
fail(
|
|
24671
|
+
`The set keys changed while you were entering values (now: ${now.set.length > 0 ? [...now.set].sort().join(", ") : "none"}). Nothing changed; run ${code("dashwise secrets set")} again.`
|
|
24672
|
+
);
|
|
24673
|
+
return 1;
|
|
24674
|
+
}
|
|
24675
|
+
} catch (err) {
|
|
24676
|
+
return surfaceSecretsError(err);
|
|
24677
|
+
}
|
|
24678
|
+
let resp;
|
|
24679
|
+
try {
|
|
24680
|
+
resp = await setInstallationSecrets(ctx.installationId, values, ctx.requestOptions);
|
|
24681
|
+
} catch (err) {
|
|
24682
|
+
return surfaceSecretsError(err);
|
|
24683
|
+
}
|
|
24684
|
+
if (options.json) {
|
|
24685
|
+
process.stdout.write(
|
|
24686
|
+
`${JSON.stringify({ installation_id: ctx.installationId, keys_set: resp.keys_set, removed, runtime: resp.runtime ?? null }, null, 2)}
|
|
24687
|
+
`
|
|
24688
|
+
);
|
|
24689
|
+
return 0;
|
|
24690
|
+
}
|
|
24691
|
+
success(`Saved ${resp.keys_set.join(", ")} for ${code(ctx.slug)}.`);
|
|
24692
|
+
if (removed.length > 0) info(` ${dim(`Removed: ${removed.join(", ")}.`)}`);
|
|
24693
|
+
info(` ${dim(runtimeLine(resp.runtime))}`);
|
|
24694
|
+
return 0;
|
|
24695
|
+
}
|
|
24696
|
+
function runtimeLine(runtime) {
|
|
24697
|
+
const status = runtime?.status;
|
|
24698
|
+
switch (status) {
|
|
24699
|
+
case "queued":
|
|
24700
|
+
return "The running app picks the new values up on its next request (about a minute).";
|
|
24701
|
+
case "deferred":
|
|
24702
|
+
return "Another change of this app is in progress; the values are applied right after it.";
|
|
24703
|
+
case "no_runtime":
|
|
24704
|
+
return "Nothing is deployed yet; the next deploy carries the values.";
|
|
24705
|
+
case "frozen":
|
|
24706
|
+
return "This installation is held; the values apply once it is released.";
|
|
24707
|
+
case void 0:
|
|
24708
|
+
return "The values reach the hosted app at its next deploy (or are read at runtime, where the backend delivers them).";
|
|
24709
|
+
default:
|
|
24710
|
+
return `Runtime: ${status}.`;
|
|
24711
|
+
}
|
|
24712
|
+
}
|
|
24713
|
+
async function secretsClearCommand(options) {
|
|
24714
|
+
const ctx = context(options);
|
|
24715
|
+
if (!ctx) return 1;
|
|
24716
|
+
if (!options.yes) {
|
|
24717
|
+
if (!interactive()) {
|
|
24718
|
+
fail(`This removes every value. Pass ${code("--yes")} to confirm.`);
|
|
24719
|
+
return 1;
|
|
24720
|
+
}
|
|
24721
|
+
const ok = await confirm$1({
|
|
24722
|
+
message: `Remove every secret value of ${ctx.slug}?`,
|
|
24723
|
+
initialValue: false
|
|
24724
|
+
});
|
|
24725
|
+
if (isCancel(ok) || ok !== true) {
|
|
24726
|
+
info("Nothing changed.");
|
|
24727
|
+
return 1;
|
|
24728
|
+
}
|
|
24729
|
+
}
|
|
24730
|
+
try {
|
|
24731
|
+
await clearInstallationSecrets(ctx.installationId, ctx.requestOptions);
|
|
24732
|
+
} catch (err) {
|
|
24733
|
+
return surfaceSecretsError(err);
|
|
24734
|
+
}
|
|
24735
|
+
success(`Removed every secret value of ${code(ctx.slug)}.`);
|
|
24736
|
+
return 0;
|
|
24737
|
+
}
|
|
24738
|
+
function surfaceSecretsError(err) {
|
|
24739
|
+
if (err instanceof ApiError) {
|
|
24740
|
+
const ctx = err.context ?? {};
|
|
24741
|
+
switch (err.code) {
|
|
24742
|
+
case "UNAUTHENTICATED":
|
|
24743
|
+
fail(`Auth rejected by backend. Your CLI token may be stale \u2014 run ${code("dashwise login")} then retry.`);
|
|
24744
|
+
return 1;
|
|
24745
|
+
case "SECRETS_ENV_TOO_LARGE": {
|
|
24746
|
+
const largest = Array.isArray(ctx.largest_keys) ? ctx.largest_keys.map(String).join(", ") : "";
|
|
24747
|
+
fail(
|
|
24748
|
+
`These values take ${String(ctx.bytes ?? "?")} bytes; an installation may hold at most ${String(ctx.budget ?? 3072)}. Shorten or remove a value${largest ? ` (largest: ${largest})` : ""}. Nothing changed.`
|
|
24749
|
+
);
|
|
24750
|
+
return 1;
|
|
24751
|
+
}
|
|
24752
|
+
case "UNKNOWN_SECRET_KEY":
|
|
24753
|
+
fail(`The backend does not know these keys: ${Array.isArray(ctx.unknown) ? ctx.unknown.join(", ") : err.message}.`);
|
|
24754
|
+
return 1;
|
|
24755
|
+
case "SECRETS_NOT_CONFIGURED":
|
|
24756
|
+
fail("Secrets are not configured on this backend (its master key is unset).");
|
|
24757
|
+
return 1;
|
|
24758
|
+
case "FORBIDDEN":
|
|
24759
|
+
case "PERMISSION_DENIED":
|
|
24760
|
+
case "NOT_WORKSPACE_ADMIN":
|
|
24761
|
+
fail("Not authorized. Setting secrets needs a company admin or an installation manager.");
|
|
24762
|
+
return 1;
|
|
24763
|
+
case "NOT_FOUND":
|
|
24764
|
+
case "INSTALLATION_NOT_FOUND":
|
|
24765
|
+
fail(`Installation not found (or not visible to you). Check the id with ${code("dashwise module status <slug>")}.`);
|
|
24766
|
+
return 1;
|
|
24767
|
+
default:
|
|
24768
|
+
fail(`Backend rejected: ${err.code} ${err.status} \u2014 ${err.message}`);
|
|
24769
|
+
return 1;
|
|
24770
|
+
}
|
|
24771
|
+
}
|
|
24772
|
+
fail(`Secrets operation failed: ${err.message}`);
|
|
24773
|
+
return 1;
|
|
24774
|
+
}
|
|
24775
|
+
|
|
24133
24776
|
// src/commands/env-export.ts
|
|
24134
24777
|
init_api_client();
|
|
24135
24778
|
init_config();
|
|
@@ -26418,6 +27061,55 @@ originsCmd.command("remove <url>").description("Remove an SSO callback origin fr
|
|
|
26418
27061
|
...cmdOpts.install !== void 0 ? { install: cmdOpts.install } : {}
|
|
26419
27062
|
});
|
|
26420
27063
|
});
|
|
27064
|
+
var secretsCmd = program.command("secrets").description(
|
|
27065
|
+
"Manage the current module install's runtime secrets (write-only: names are shown, values never are)."
|
|
27066
|
+
);
|
|
27067
|
+
secretsCmd.command("list").description("Show the declared secret keys and which have a value (names only).").option("--dir <path>", "Project root (default: current directory).").option(
|
|
27068
|
+
"--install <id>",
|
|
27069
|
+
"Target a specific installation id instead of the cached dev install (production installs included). Find ids via `dashwise module status <slug>`.",
|
|
27070
|
+
parseIntOption
|
|
27071
|
+
).option("--json", "Emit JSON instead of the human-friendly list.").action(async (cmdOpts) => {
|
|
27072
|
+
process.exitCode = await secretsListCommand({
|
|
27073
|
+
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
27074
|
+
...cmdOpts.install !== void 0 ? { install: cmdOpts.install } : {},
|
|
27075
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
27076
|
+
});
|
|
27077
|
+
});
|
|
27078
|
+
secretsCmd.command("set [keys...]").description(
|
|
27079
|
+
"Set secret values, REPLACING the whole map (keys set now but not given are removed; you are asked first). Values come from a hidden prompt, --from-stdin or --from-env-file, never from the command line."
|
|
27080
|
+
).option("--dir <path>", "Project root (default: current directory).").option(
|
|
27081
|
+
"--install <id>",
|
|
27082
|
+
"Target a specific installation id instead of the cached dev install (production installs included). Find ids via `dashwise module status <slug>`.",
|
|
27083
|
+
parseIntOption
|
|
27084
|
+
).option("--from-stdin", "Read the value of exactly one key from stdin.").option("--from-env-file <path>", "Read values from a dotenv-style file (all declared keys found in it, or the keys named).").option("--keys <list>", "With --from-env-file: only these keys (comma-separated).").option("-y, --yes", "Remove keys that are set now but not in the new map without asking.").option("--json", "Emit a JSON summary (key names only).").addHelpText(
|
|
27085
|
+
"before",
|
|
27086
|
+
pc.bold(
|
|
27087
|
+
"set REPLACES THE WHOLE MAP: every key you do not give is removed (you are asked first; --yes skips the question)."
|
|
27088
|
+
)
|
|
27089
|
+
).action(
|
|
27090
|
+
async (keys, cmdOpts) => {
|
|
27091
|
+
process.exitCode = await secretsSetCommand(keys ?? [], {
|
|
27092
|
+
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
27093
|
+
...cmdOpts.install !== void 0 ? { install: cmdOpts.install } : {},
|
|
27094
|
+
...cmdOpts.fromStdin !== void 0 ? { fromStdin: cmdOpts.fromStdin } : {},
|
|
27095
|
+
...cmdOpts.fromEnvFile !== void 0 ? { fromEnvFile: cmdOpts.fromEnvFile } : {},
|
|
27096
|
+
...cmdOpts.keys !== void 0 ? { keys: cmdOpts.keys } : {},
|
|
27097
|
+
...cmdOpts.yes !== void 0 ? { yes: cmdOpts.yes } : {},
|
|
27098
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
27099
|
+
});
|
|
27100
|
+
}
|
|
27101
|
+
);
|
|
27102
|
+
secretsCmd.command("clear").description("Remove every secret value of the install.").option("--dir <path>", "Project root (default: current directory).").option(
|
|
27103
|
+
"--install <id>",
|
|
27104
|
+
"Target a specific installation id instead of the cached dev install (production installs included). Find ids via `dashwise module status <slug>`.",
|
|
27105
|
+
parseIntOption
|
|
27106
|
+
).option("-y, --yes", "Do not ask for confirmation.").action(async (cmdOpts) => {
|
|
27107
|
+
process.exitCode = await secretsClearCommand({
|
|
27108
|
+
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
27109
|
+
...cmdOpts.install !== void 0 ? { install: cmdOpts.install } : {},
|
|
27110
|
+
...cmdOpts.yes !== void 0 ? { yes: cmdOpts.yes } : {}
|
|
27111
|
+
});
|
|
27112
|
+
});
|
|
26421
27113
|
var envCmd = program.command("env").description("Environment-variable helpers for deploying a module.");
|
|
26422
27114
|
envCmd.command("export").description(
|
|
26423
27115
|
"Emit the env vars a deployed module needs to authenticate against Dashwise. By default emits .env-style KEY=value lines; --format = env | json | railway | vercel | fly to wrap in platform-native syntax. Use --new-key <name> to mint + inline an API key in one step."
|
|
@@ -26451,6 +27143,36 @@ moduleCmd.command("release [slug]").description("Release an exact pushed reposit
|
|
|
26451
27143
|
timeoutSec: opts.timeout
|
|
26452
27144
|
});
|
|
26453
27145
|
});
|
|
27146
|
+
moduleCmd.command("build-log [slug] [buildId]").description(
|
|
27147
|
+
"Print a build's log, scrubbed by the server (credentials and internal hosts hidden) and bounded to its newest part. One positional is the build id (module from module.json). --follow prints new lines until the build ends."
|
|
27148
|
+
).option("--dir <path>", "Project root for module.json slug lookup (default: current directory).").option("--follow", "Keep printing new lines while the build is queued or building.").option("--timeout <seconds>", "Max seconds to follow (default 900); exit 4 means still building.", Number).option("--json", "Emit the log response as JSON (not with --follow).").action(
|
|
27149
|
+
async (slug, buildId, opts) => {
|
|
27150
|
+
process.exitCode = await moduleBuildLogCommand(slug, buildId, {
|
|
27151
|
+
...opts.dir !== void 0 ? { dir: opts.dir } : {},
|
|
27152
|
+
...opts.follow !== void 0 ? { follow: opts.follow } : {},
|
|
27153
|
+
...opts.timeout !== void 0 ? { timeoutSec: opts.timeout } : {},
|
|
27154
|
+
...opts.json !== void 0 ? { json: opts.json } : {}
|
|
27155
|
+
});
|
|
27156
|
+
}
|
|
27157
|
+
);
|
|
27158
|
+
moduleCmd.command("ship [slug]").description(
|
|
27159
|
+
"Release and install in one command, for a person: releases the latest push to the default branch (or --commit-sha), waits for the build, then installs that version, showing what the update does to the data and asking once. Refused inside an AI agent session (an agent releases; a person takes it live)."
|
|
27160
|
+
).option("--dir <path>", "Project root for module.json slug lookup (default: current directory).").option("--commit-sha <sha>", "Release this pushed revision (full 40-character SHA) instead of the default-branch head.").option("--build <id>", "Resume a ship whose release was still building: wait for that build, then install it. Never queues a release.", parsePositiveIdOption).option("--bump <direction>", "Semver bump: patch | minor | major (default minor).").option("--workspace <slug-or-id>", "Target workspace (as for module install).").option("--installation <id>", "Update exactly this production installation.", parsePositiveIdOption).option("--keep-dev-install", "Install alongside the workspace's dev install (as for module install).").option("--replace-dev-install", "Replace the workspace's dev install (as for module install).").option("-y, --yes", "Skip the install question when nothing is lost. Never accepts data loss.").option("--timeout <seconds>", "Max seconds to wait for the build (default 360); exit 4 means still building.", Number).action(
|
|
27161
|
+
async (slug, opts) => {
|
|
27162
|
+
process.exitCode = await moduleShipCommand(slug, {
|
|
27163
|
+
...opts.dir !== void 0 ? { dir: opts.dir } : {},
|
|
27164
|
+
...opts.commitSha !== void 0 ? { commitSha: opts.commitSha } : {},
|
|
27165
|
+
...opts.build !== void 0 ? { build: opts.build } : {},
|
|
27166
|
+
...opts.bump !== void 0 ? { bump: opts.bump } : {},
|
|
27167
|
+
...opts.workspace !== void 0 ? { workspace: opts.workspace } : {},
|
|
27168
|
+
...opts.installation !== void 0 ? { installation: opts.installation } : {},
|
|
27169
|
+
...opts.keepDevInstall !== void 0 ? { keepDevInstall: opts.keepDevInstall } : {},
|
|
27170
|
+
...opts.replaceDevInstall !== void 0 ? { replaceDevInstall: opts.replaceDevInstall } : {},
|
|
27171
|
+
...opts.yes !== void 0 ? { yes: opts.yes } : {},
|
|
27172
|
+
...opts.timeout !== void 0 ? { timeoutSec: opts.timeout } : {}
|
|
27173
|
+
});
|
|
27174
|
+
}
|
|
27175
|
+
);
|
|
26454
27176
|
moduleCmd.command("publish").description("Pack the current directory + upload it as a new module version.").option("--dir <path>", "Project root (default: current directory).").option("--bump <direction>", "Semver bump: patch | minor | major.").option("--dry-run", "Validate + upload but do not persist the version row.").option("-y, --yes", "Skip the confirmation prompt (for CI / scripted use).").option(
|
|
26455
27177
|
"--from-local",
|
|
26456
27178
|
"Publish local files to a module that has a linked repository (the next repository build replaces them)."
|
|
@@ -26874,7 +27596,7 @@ fieldCmd.command("set-type <table-slug> <field-slug> <new-type>").description(
|
|
|
26874
27596
|
}
|
|
26875
27597
|
);
|
|
26876
27598
|
function getVersion() {
|
|
26877
|
-
return "0.
|
|
27599
|
+
return "0.30.1";
|
|
26878
27600
|
}
|
|
26879
27601
|
function parsePositiveIdOption(value) {
|
|
26880
27602
|
if (!/^[1-9][0-9]*$/.test(value.trim())) {
|