@dashai/cli 0.28.1 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -1
- package/dist/bin.js +436 -13
- package/dist/bin.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ The CLI defaults to `https://api.dashwise.net`. `DASHWISE_API_URL` takes precede
|
|
|
8
8
|
|
|
9
9
|
## Status
|
|
10
10
|
|
|
11
|
-
`0.5.x` — full author workflow + **Query Plane v1 author tooling** + **P7 cross-module-action observability** (shipped 2026-05-20) + **BaaS Phase 1 deploy loop** (`api-keys`, `env export`, `--deploy-target`; shipped 2026-05-24). Commands: `login`, `logout`, `profile`, `workspace`, `init` / `module init` (incl. `--custom`, `--deploy-target railway|vercel|fly`) / `connect-git` / `generate` / `dev` / `validate` / `list` / `publish` / `diff` / `pull-data` / `clone` / `query-log` / `action-log`, plus `query` (run AST queries against the local module), `pull` (sync canonical manifest from backend), `api-keys list|create|revoke`, `origins list|add|remove` (SSO callback allowlist), `env export`, and `deps` (live connect status) / `add` / `list` / `remove` / `check` / `bind` / `unbind` (cross-module dependency connect lifecycle).
|
|
11
|
+
`0.5.x` — full author workflow + **Query Plane v1 author tooling** + **P7 cross-module-action observability** (shipped 2026-05-20) + **BaaS Phase 1 deploy loop** (`api-keys`, `env export`, `--deploy-target`; shipped 2026-05-24). Commands: `login`, `logout`, `profile`, `workspace`, `init` / `module init` (incl. `--custom`, `--deploy-target railway|vercel|fly`) / `connect-git` / `generate` / `dev` / `validate` / `list` / `publish` / `diff` / `pull-data` / `clone` / `query-log` / `action-log`, plus `query` (run AST queries against the local module), `pull` (sync canonical manifest from backend), `api-keys list|create|revoke`, `origins list|add|remove` (SSO callback allowlist), `secrets list|set|clear` (write-only runtime secrets), `env export`, and `deps` (live connect status) / `add` / `list` / `remove` / `check` / `bind` / `unbind` (cross-module dependency connect lifecycle).
|
|
12
12
|
|
|
13
13
|
## Install
|
|
14
14
|
|
|
@@ -563,6 +563,32 @@ Flags:
|
|
|
563
563
|
- `--dir <path>` — Project root.
|
|
564
564
|
- `--json` (list only) — Emit a JSON array of origins.
|
|
565
565
|
|
|
566
|
+
### `dashwise secrets list|set|clear`
|
|
567
|
+
|
|
568
|
+
Manage the install's runtime secrets: the values your module declares in `module.json#runtime.secrets[]` (API tokens, webhook signing secrets). They are **write-only**: the backend never returns a value, and the CLI never prints one or takes one from the command line (it would land in shell history).
|
|
569
|
+
|
|
570
|
+
```sh
|
|
571
|
+
dashwise secrets list # declared keys, which are set (names only)
|
|
572
|
+
dashwise secrets set GITHUB_TOKEN SLACK_BOT_TOKEN # hidden prompt for each value
|
|
573
|
+
printf %s "$TOKEN" | dashwise secrets set GITHUB_TOKEN --from-stdin
|
|
574
|
+
dashwise secrets set --from-env-file .env.secrets # every declared key found in the file
|
|
575
|
+
dashwise secrets set --from-env-file .env.secrets --keys GITHUB_TOKEN
|
|
576
|
+
dashwise secrets clear --yes # remove every value
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
Env-file format (read strictly, so a value is never guessed at): `KEY=value` lines with no spaces around `=`, `#` comment lines, an optional `export ` prefix. An unquoted value runs to the end of the line and may not start or end with a space or contain ` #`; quote such a value. `"double quotes"` understand `\\`, `\"` and `\n`; `'single quotes'` are literal; only a ` # comment` may follow a closing quote. Errors name the line number, never its content. `--from-stdin` with nothing piped in asks at the hidden prompt instead.
|
|
580
|
+
|
|
581
|
+
`set` **replaces the whole map** (the backend has no per-key route yet): a key that is set now but not given is removed, and `set` names those keys and asks first (`--yes` in scripts; without a terminal it refuses). Right before writing it reads the set keys again and stops if they changed meanwhile. An argument that is not a declared key is named by its position only (it may be a mistyped value). Where the backend delivers secrets to the hosted app, `set` says whether the running app picks the values up on its next request. An oversize map is refused with the byte count and the largest key names.
|
|
582
|
+
|
|
583
|
+
Install resolution as for `origins`: the cached dev install of the project's module, or any install (production included) with `--install <id>`. Setting values needs a company admin or an installation manager.
|
|
584
|
+
|
|
585
|
+
Flags:
|
|
586
|
+
- `--dir <path>` — Project root.
|
|
587
|
+
- `--install <id>` — Target a specific installation.
|
|
588
|
+
- `--from-stdin` / `--from-env-file <path>` / `--keys <list>` (set) — Where values come from.
|
|
589
|
+
- `-y, --yes` (set, clear) — Do not ask.
|
|
590
|
+
- `--json` (list, set) — JSON output (names only).
|
|
591
|
+
|
|
566
592
|
### `dashwise module query-log`
|
|
567
593
|
|
|
568
594
|
Read the per-module SDK query log (each cross-module / `.execute()` / `.stream()` / `.explain()` dispatch writes a row to `module_query_log` server-side). Workspace-member-scoped on the backend.
|
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,30 @@ async function setOrigins(installationId, allowedOrigins, options = {}) {
|
|
|
364
364
|
options
|
|
365
365
|
);
|
|
366
366
|
}
|
|
367
|
+
async function listInstallationSecrets(installationId, options = {}) {
|
|
368
|
+
return apiRequest(
|
|
369
|
+
"GET",
|
|
370
|
+
`/api/modules/installations/${installationId}/secrets`,
|
|
371
|
+
void 0,
|
|
372
|
+
options
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
async function setInstallationSecrets(installationId, secrets, options = {}) {
|
|
376
|
+
return apiRequest(
|
|
377
|
+
"PUT",
|
|
378
|
+
`/api/modules/installations/${installationId}/secrets`,
|
|
379
|
+
{ secrets },
|
|
380
|
+
options
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
async function clearInstallationSecrets(installationId, options = {}) {
|
|
384
|
+
await apiRequest(
|
|
385
|
+
"DELETE",
|
|
386
|
+
`/api/modules/installations/${installationId}/secrets`,
|
|
387
|
+
void 0,
|
|
388
|
+
options
|
|
389
|
+
);
|
|
390
|
+
}
|
|
367
391
|
async function createTable(body, options = {}) {
|
|
368
392
|
return apiRequest(
|
|
369
393
|
"POST",
|
|
@@ -14158,8 +14182,8 @@ function needsAdmin(what, module, ask) {
|
|
|
14158
14182
|
const scope = module ? ` for ${module}` : "";
|
|
14159
14183
|
return `${what}${scope} needs a Dashwise admin. Ask your Dashwise admin to ${ask}.`;
|
|
14160
14184
|
}
|
|
14161
|
-
function refusalHint(code2,
|
|
14162
|
-
const ctx =
|
|
14185
|
+
function refusalHint(code2, context2, subject = {}) {
|
|
14186
|
+
const ctx = context2 ?? {};
|
|
14163
14187
|
const module = subject.module ?? str(ctx.module_slug);
|
|
14164
14188
|
const installationId = subject.installationId ?? num(ctx.installation_id);
|
|
14165
14189
|
const installLabel = installationId !== void 0 ? ` (install #${installationId})` : "";
|
|
@@ -15213,9 +15237,9 @@ function surfaceSchemaPushError(err, slug, json) {
|
|
|
15213
15237
|
fail(`Schema push failed: ${err.message}`);
|
|
15214
15238
|
return 1;
|
|
15215
15239
|
}
|
|
15216
|
-
function extractIssues2(
|
|
15217
|
-
if (!
|
|
15218
|
-
const raw =
|
|
15240
|
+
function extractIssues2(context2) {
|
|
15241
|
+
if (!context2) return [];
|
|
15242
|
+
const raw = context2.issues;
|
|
15219
15243
|
if (!Array.isArray(raw)) return [];
|
|
15220
15244
|
return raw.map((i) => {
|
|
15221
15245
|
if (typeof i === "string") return i;
|
|
@@ -21760,9 +21784,9 @@ async function moduleValidateCommand(options) {
|
|
|
21760
21784
|
return 1;
|
|
21761
21785
|
}
|
|
21762
21786
|
}
|
|
21763
|
-
function readIssues(
|
|
21764
|
-
if (!
|
|
21765
|
-
const v =
|
|
21787
|
+
function readIssues(context2) {
|
|
21788
|
+
if (!context2) return [];
|
|
21789
|
+
const v = context2["issues"];
|
|
21766
21790
|
if (!Array.isArray(v)) return [];
|
|
21767
21791
|
return v.map((entry) => {
|
|
21768
21792
|
if (!entry || typeof entry !== "object") return null;
|
|
@@ -23344,9 +23368,9 @@ async function pushManifest(ctx) {
|
|
|
23344
23368
|
return { ok: false, rejected: false };
|
|
23345
23369
|
}
|
|
23346
23370
|
}
|
|
23347
|
-
function extractIssues(
|
|
23348
|
-
if (!
|
|
23349
|
-
const raw =
|
|
23371
|
+
function extractIssues(context2) {
|
|
23372
|
+
if (!context2) return [];
|
|
23373
|
+
const raw = context2.issues;
|
|
23350
23374
|
if (!Array.isArray(raw)) return [];
|
|
23351
23375
|
return raw.map((i) => {
|
|
23352
23376
|
if (typeof i === "string") return i;
|
|
@@ -24130,6 +24154,356 @@ function surfaceOriginsError(err) {
|
|
|
24130
24154
|
return 1;
|
|
24131
24155
|
}
|
|
24132
24156
|
|
|
24157
|
+
// src/commands/secrets.ts
|
|
24158
|
+
init_api_client();
|
|
24159
|
+
init_output();
|
|
24160
|
+
var KEY_SHAPE = /^[A-Z][A-Z0-9_]{0,63}$/;
|
|
24161
|
+
function context(opts) {
|
|
24162
|
+
try {
|
|
24163
|
+
return loadContext5(opts);
|
|
24164
|
+
} catch (err) {
|
|
24165
|
+
fail(err.message);
|
|
24166
|
+
return null;
|
|
24167
|
+
}
|
|
24168
|
+
}
|
|
24169
|
+
function interactive() {
|
|
24170
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
24171
|
+
}
|
|
24172
|
+
async function secretsListCommand(options) {
|
|
24173
|
+
const ctx = context(options);
|
|
24174
|
+
if (!ctx) return 1;
|
|
24175
|
+
let resp;
|
|
24176
|
+
try {
|
|
24177
|
+
resp = await listInstallationSecrets(ctx.installationId, ctx.requestOptions);
|
|
24178
|
+
} catch (err) {
|
|
24179
|
+
return surfaceSecretsError(err);
|
|
24180
|
+
}
|
|
24181
|
+
const set = new Set(resp.set);
|
|
24182
|
+
const declared = new Set(resp.declared.map((d) => d.key));
|
|
24183
|
+
if (options.json) {
|
|
24184
|
+
process.stdout.write(
|
|
24185
|
+
`${JSON.stringify(
|
|
24186
|
+
{
|
|
24187
|
+
installation_id: ctx.installationId,
|
|
24188
|
+
declared: resp.declared.map((d) => ({
|
|
24189
|
+
key: d.key,
|
|
24190
|
+
description: d.description,
|
|
24191
|
+
set: set.has(d.key)
|
|
24192
|
+
})),
|
|
24193
|
+
// Stored but no longer declared by the installed version: not
|
|
24194
|
+
// delivered to the app; `set` removes them.
|
|
24195
|
+
undeclared_set: resp.set.filter((k) => !declared.has(k))
|
|
24196
|
+
},
|
|
24197
|
+
null,
|
|
24198
|
+
2
|
|
24199
|
+
)}
|
|
24200
|
+
`
|
|
24201
|
+
);
|
|
24202
|
+
return 0;
|
|
24203
|
+
}
|
|
24204
|
+
info(`Secrets of ${code(ctx.slug)} ${dim("(names only; values are never shown)")}:`);
|
|
24205
|
+
if (resp.declared.length === 0) {
|
|
24206
|
+
info(` ${dim("(the module declares none in module.json#runtime.secrets)")}`);
|
|
24207
|
+
}
|
|
24208
|
+
for (const d of resp.declared) {
|
|
24209
|
+
const state = set.has(d.key) ? "set" : dim("not set");
|
|
24210
|
+
process.stdout.write(
|
|
24211
|
+
` ${d.key} ${state}${d.description ? ` ${dim(d.description)}` : ""}
|
|
24212
|
+
`
|
|
24213
|
+
);
|
|
24214
|
+
}
|
|
24215
|
+
const stray = resp.set.filter((k) => !declared.has(k));
|
|
24216
|
+
if (stray.length > 0) {
|
|
24217
|
+
warn(
|
|
24218
|
+
`Stored but no longer declared (not delivered): ${stray.join(", ")}. The next ${code("dashwise secrets set")} removes them.`
|
|
24219
|
+
);
|
|
24220
|
+
}
|
|
24221
|
+
return 0;
|
|
24222
|
+
}
|
|
24223
|
+
function parseEnvFile(text4) {
|
|
24224
|
+
const out2 = {};
|
|
24225
|
+
const lines = text4.split(/\r?\n/);
|
|
24226
|
+
for (let i = 0; i < lines.length; i++) {
|
|
24227
|
+
const n = i + 1;
|
|
24228
|
+
const line = lines[i].replace(/^\s+/, "");
|
|
24229
|
+
if (line === "" || line.startsWith("#")) continue;
|
|
24230
|
+
const m = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)$/.exec(line);
|
|
24231
|
+
if (!m) {
|
|
24232
|
+
throw new Error(`line ${n} is not KEY=value (no spaces around "=")`);
|
|
24233
|
+
}
|
|
24234
|
+
const [, key, raw] = m;
|
|
24235
|
+
out2[key] = parseValue(raw, n);
|
|
24236
|
+
}
|
|
24237
|
+
return out2;
|
|
24238
|
+
}
|
|
24239
|
+
function parseValue(raw, n) {
|
|
24240
|
+
const quote2 = raw[0];
|
|
24241
|
+
if (quote2 === '"' || quote2 === "'") {
|
|
24242
|
+
let value = "";
|
|
24243
|
+
let j = 1;
|
|
24244
|
+
for (; j < raw.length; j++) {
|
|
24245
|
+
const ch = raw[j];
|
|
24246
|
+
if (ch === quote2) break;
|
|
24247
|
+
if (quote2 === '"' && ch === "\\" && j + 1 < raw.length) {
|
|
24248
|
+
const next = raw[++j];
|
|
24249
|
+
value += next === "n" ? "\n" : next === '"' || next === "\\" ? next : `\\${next}`;
|
|
24250
|
+
continue;
|
|
24251
|
+
}
|
|
24252
|
+
value += ch;
|
|
24253
|
+
}
|
|
24254
|
+
if (j >= raw.length) throw new Error(`line ${n}: the quoted value is not closed`);
|
|
24255
|
+
const rest = raw.slice(j + 1);
|
|
24256
|
+
if (rest.trim() !== "" && !/^\s+#/.test(rest)) {
|
|
24257
|
+
throw new Error(`line ${n}: only a " # comment" may follow the closing quote`);
|
|
24258
|
+
}
|
|
24259
|
+
return value;
|
|
24260
|
+
}
|
|
24261
|
+
if (/^\s|\s$/.test(raw)) {
|
|
24262
|
+
throw new Error(
|
|
24263
|
+
`line ${n}: an unquoted value may not start or end with a space; quote it`
|
|
24264
|
+
);
|
|
24265
|
+
}
|
|
24266
|
+
if (/\s#/.test(raw)) {
|
|
24267
|
+
throw new Error(
|
|
24268
|
+
`line ${n}: an unquoted value may not contain " #" (comment or value?); quote it`
|
|
24269
|
+
);
|
|
24270
|
+
}
|
|
24271
|
+
return raw;
|
|
24272
|
+
}
|
|
24273
|
+
async function readStdin() {
|
|
24274
|
+
const chunks = [];
|
|
24275
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
24276
|
+
return Buffer.concat(chunks).toString("utf8").replace(/\r?\n$/, "");
|
|
24277
|
+
}
|
|
24278
|
+
async function secretsSetCommand(keyArgs, options) {
|
|
24279
|
+
const inline = keyArgs.findIndex((k) => k.includes("=") || !KEY_SHAPE.test(k));
|
|
24280
|
+
if (inline !== -1) {
|
|
24281
|
+
fail(
|
|
24282
|
+
`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>")}.`
|
|
24283
|
+
);
|
|
24284
|
+
return 1;
|
|
24285
|
+
}
|
|
24286
|
+
if (options.fromStdin && options.fromEnvFile) {
|
|
24287
|
+
fail(`Use one of ${code("--from-stdin")} or ${code("--from-env-file")}, not both.`);
|
|
24288
|
+
return 1;
|
|
24289
|
+
}
|
|
24290
|
+
if (options.keys && !options.fromEnvFile) {
|
|
24291
|
+
fail(`${code("--keys")} goes with ${code("--from-env-file")}.`);
|
|
24292
|
+
return 1;
|
|
24293
|
+
}
|
|
24294
|
+
const ctx = context(options);
|
|
24295
|
+
if (!ctx) return 1;
|
|
24296
|
+
let current;
|
|
24297
|
+
try {
|
|
24298
|
+
current = await listInstallationSecrets(ctx.installationId, ctx.requestOptions);
|
|
24299
|
+
} catch (err) {
|
|
24300
|
+
return surfaceSecretsError(err);
|
|
24301
|
+
}
|
|
24302
|
+
const declared = current.declared.map((d) => d.key);
|
|
24303
|
+
let keys = [...new Set(keyArgs)];
|
|
24304
|
+
const values = {};
|
|
24305
|
+
if (options.fromEnvFile) {
|
|
24306
|
+
let fileValues;
|
|
24307
|
+
try {
|
|
24308
|
+
fileValues = parseEnvFile(readFileSync(options.fromEnvFile, "utf8"));
|
|
24309
|
+
} catch (err) {
|
|
24310
|
+
fail(`Could not read ${code(options.fromEnvFile)}: ${err.message}`);
|
|
24311
|
+
return 1;
|
|
24312
|
+
}
|
|
24313
|
+
const wanted = options.keys ? options.keys.split(",").map((k) => k.trim()).filter(Boolean) : keys.length > 0 ? keys : declared.filter((k) => k in fileValues);
|
|
24314
|
+
const undeclaredAt2 = wanted.findIndex(
|
|
24315
|
+
(k) => !KEY_SHAPE.test(k) || !declared.includes(k)
|
|
24316
|
+
);
|
|
24317
|
+
if (undeclaredAt2 !== -1) {
|
|
24318
|
+
fail(
|
|
24319
|
+
`${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"}.`
|
|
24320
|
+
);
|
|
24321
|
+
return 1;
|
|
24322
|
+
}
|
|
24323
|
+
const missing = wanted.filter((k) => !(k in fileValues));
|
|
24324
|
+
if (missing.length > 0) {
|
|
24325
|
+
fail(`Not in ${code(options.fromEnvFile)}: ${missing.join(", ")}.`);
|
|
24326
|
+
return 1;
|
|
24327
|
+
}
|
|
24328
|
+
for (const k of wanted) values[k] = fileValues[k];
|
|
24329
|
+
keys = wanted;
|
|
24330
|
+
} else if (options.fromStdin) {
|
|
24331
|
+
if (keys.length !== 1) {
|
|
24332
|
+
fail(`${code("--from-stdin")} reads the value of exactly one key: ${code("dashwise secrets set KEY --from-stdin")}.`);
|
|
24333
|
+
return 1;
|
|
24334
|
+
}
|
|
24335
|
+
if (process.stdin.isTTY) {
|
|
24336
|
+
info(`Nothing is piped into stdin; asking at a hidden prompt instead.`);
|
|
24337
|
+
} else {
|
|
24338
|
+
values[keys[0]] = await readStdin();
|
|
24339
|
+
}
|
|
24340
|
+
} else {
|
|
24341
|
+
if (keys.length === 0) {
|
|
24342
|
+
fail(
|
|
24343
|
+
`Name the keys to set (${declared.length > 0 ? declared.join(", ") : "none declared"}), or use ${code("--from-env-file <path>")}.`
|
|
24344
|
+
);
|
|
24345
|
+
return 1;
|
|
24346
|
+
}
|
|
24347
|
+
if (!interactive()) {
|
|
24348
|
+
fail(`No terminal to prompt in. Use ${code("--from-stdin")} or ${code("--from-env-file <path>")}.`);
|
|
24349
|
+
return 1;
|
|
24350
|
+
}
|
|
24351
|
+
}
|
|
24352
|
+
if (keys.length === 0) {
|
|
24353
|
+
fail(`Nothing to set: no declared key was found.`);
|
|
24354
|
+
return 1;
|
|
24355
|
+
}
|
|
24356
|
+
const undeclaredAt = keys.findIndex(
|
|
24357
|
+
(k) => !KEY_SHAPE.test(k) || !declared.includes(k)
|
|
24358
|
+
);
|
|
24359
|
+
if (undeclaredAt !== -1) {
|
|
24360
|
+
const where = options.keys ? `Item ${undeclaredAt + 1} of --keys` : keyArgs.length > 0 ? `Argument ${keyArgs.indexOf(keys[undeclaredAt]) + 1}` : `Key ${undeclaredAt + 1}`;
|
|
24361
|
+
fail(
|
|
24362
|
+
`${where} is not a declared key; values must not be passed as arguments. Declared: ${declared.length > 0 ? declared.join(", ") : "none"}.`
|
|
24363
|
+
);
|
|
24364
|
+
return 1;
|
|
24365
|
+
}
|
|
24366
|
+
const removed = current.set.filter((k) => !keys.includes(k));
|
|
24367
|
+
if (removed.length > 0 && !options.yes) {
|
|
24368
|
+
if (!interactive()) {
|
|
24369
|
+
fail(
|
|
24370
|
+
`This replaces the whole map and would remove ${removed.join(", ")}. Include them, or pass ${code("--yes")} to remove them.`
|
|
24371
|
+
);
|
|
24372
|
+
return 1;
|
|
24373
|
+
}
|
|
24374
|
+
const ok = await confirm$1({
|
|
24375
|
+
message: `This replaces the whole map: ${removed.join(", ")} will be removed. Continue?`,
|
|
24376
|
+
initialValue: false
|
|
24377
|
+
});
|
|
24378
|
+
if (isCancel(ok) || ok !== true) {
|
|
24379
|
+
info("Nothing changed.");
|
|
24380
|
+
return 1;
|
|
24381
|
+
}
|
|
24382
|
+
}
|
|
24383
|
+
for (const k of keys) {
|
|
24384
|
+
if (k in values) continue;
|
|
24385
|
+
const entered = await password({ message: `Value for ${k}` });
|
|
24386
|
+
if (isCancel(entered)) {
|
|
24387
|
+
info("Nothing changed.");
|
|
24388
|
+
return 1;
|
|
24389
|
+
}
|
|
24390
|
+
values[k] = entered;
|
|
24391
|
+
}
|
|
24392
|
+
const empty = keys.filter((k) => values[k] === "");
|
|
24393
|
+
if (empty.length > 0) {
|
|
24394
|
+
fail(`Empty value for ${empty.join(", ")}; nothing changed. (To remove every value: ${code("dashwise secrets clear")}.)`);
|
|
24395
|
+
return 1;
|
|
24396
|
+
}
|
|
24397
|
+
try {
|
|
24398
|
+
const now = await listInstallationSecrets(ctx.installationId, ctx.requestOptions);
|
|
24399
|
+
const before = [...current.set].sort().join(",");
|
|
24400
|
+
if ([...now.set].sort().join(",") !== before) {
|
|
24401
|
+
fail(
|
|
24402
|
+
`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.`
|
|
24403
|
+
);
|
|
24404
|
+
return 1;
|
|
24405
|
+
}
|
|
24406
|
+
} catch (err) {
|
|
24407
|
+
return surfaceSecretsError(err);
|
|
24408
|
+
}
|
|
24409
|
+
let resp;
|
|
24410
|
+
try {
|
|
24411
|
+
resp = await setInstallationSecrets(ctx.installationId, values, ctx.requestOptions);
|
|
24412
|
+
} catch (err) {
|
|
24413
|
+
return surfaceSecretsError(err);
|
|
24414
|
+
}
|
|
24415
|
+
if (options.json) {
|
|
24416
|
+
process.stdout.write(
|
|
24417
|
+
`${JSON.stringify({ installation_id: ctx.installationId, keys_set: resp.keys_set, removed, runtime: resp.runtime ?? null }, null, 2)}
|
|
24418
|
+
`
|
|
24419
|
+
);
|
|
24420
|
+
return 0;
|
|
24421
|
+
}
|
|
24422
|
+
success(`Saved ${resp.keys_set.join(", ")} for ${code(ctx.slug)}.`);
|
|
24423
|
+
if (removed.length > 0) info(` ${dim(`Removed: ${removed.join(", ")}.`)}`);
|
|
24424
|
+
info(` ${dim(runtimeLine(resp.runtime))}`);
|
|
24425
|
+
return 0;
|
|
24426
|
+
}
|
|
24427
|
+
function runtimeLine(runtime) {
|
|
24428
|
+
const status = runtime?.status;
|
|
24429
|
+
switch (status) {
|
|
24430
|
+
case "queued":
|
|
24431
|
+
return "The running app picks the new values up on its next request (about a minute).";
|
|
24432
|
+
case "deferred":
|
|
24433
|
+
return "Another change of this app is in progress; the values are applied right after it.";
|
|
24434
|
+
case "no_runtime":
|
|
24435
|
+
return "Nothing is deployed yet; the next deploy carries the values.";
|
|
24436
|
+
case "frozen":
|
|
24437
|
+
return "This installation is held; the values apply once it is released.";
|
|
24438
|
+
case void 0:
|
|
24439
|
+
return "The values reach the hosted app at its next deploy (or are read at runtime, where the backend delivers them).";
|
|
24440
|
+
default:
|
|
24441
|
+
return `Runtime: ${status}.`;
|
|
24442
|
+
}
|
|
24443
|
+
}
|
|
24444
|
+
async function secretsClearCommand(options) {
|
|
24445
|
+
const ctx = context(options);
|
|
24446
|
+
if (!ctx) return 1;
|
|
24447
|
+
if (!options.yes) {
|
|
24448
|
+
if (!interactive()) {
|
|
24449
|
+
fail(`This removes every value. Pass ${code("--yes")} to confirm.`);
|
|
24450
|
+
return 1;
|
|
24451
|
+
}
|
|
24452
|
+
const ok = await confirm$1({
|
|
24453
|
+
message: `Remove every secret value of ${ctx.slug}?`,
|
|
24454
|
+
initialValue: false
|
|
24455
|
+
});
|
|
24456
|
+
if (isCancel(ok) || ok !== true) {
|
|
24457
|
+
info("Nothing changed.");
|
|
24458
|
+
return 1;
|
|
24459
|
+
}
|
|
24460
|
+
}
|
|
24461
|
+
try {
|
|
24462
|
+
await clearInstallationSecrets(ctx.installationId, ctx.requestOptions);
|
|
24463
|
+
} catch (err) {
|
|
24464
|
+
return surfaceSecretsError(err);
|
|
24465
|
+
}
|
|
24466
|
+
success(`Removed every secret value of ${code(ctx.slug)}.`);
|
|
24467
|
+
return 0;
|
|
24468
|
+
}
|
|
24469
|
+
function surfaceSecretsError(err) {
|
|
24470
|
+
if (err instanceof ApiError) {
|
|
24471
|
+
const ctx = err.context ?? {};
|
|
24472
|
+
switch (err.code) {
|
|
24473
|
+
case "UNAUTHENTICATED":
|
|
24474
|
+
fail(`Auth rejected by backend. Your CLI token may be stale \u2014 run ${code("dashwise login")} then retry.`);
|
|
24475
|
+
return 1;
|
|
24476
|
+
case "SECRETS_ENV_TOO_LARGE": {
|
|
24477
|
+
const largest = Array.isArray(ctx.largest_keys) ? ctx.largest_keys.map(String).join(", ") : "";
|
|
24478
|
+
fail(
|
|
24479
|
+
`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.`
|
|
24480
|
+
);
|
|
24481
|
+
return 1;
|
|
24482
|
+
}
|
|
24483
|
+
case "UNKNOWN_SECRET_KEY":
|
|
24484
|
+
fail(`The backend does not know these keys: ${Array.isArray(ctx.unknown) ? ctx.unknown.join(", ") : err.message}.`);
|
|
24485
|
+
return 1;
|
|
24486
|
+
case "SECRETS_NOT_CONFIGURED":
|
|
24487
|
+
fail("Secrets are not configured on this backend (its master key is unset).");
|
|
24488
|
+
return 1;
|
|
24489
|
+
case "FORBIDDEN":
|
|
24490
|
+
case "PERMISSION_DENIED":
|
|
24491
|
+
case "NOT_WORKSPACE_ADMIN":
|
|
24492
|
+
fail("Not authorized. Setting secrets needs a company admin or an installation manager.");
|
|
24493
|
+
return 1;
|
|
24494
|
+
case "NOT_FOUND":
|
|
24495
|
+
case "INSTALLATION_NOT_FOUND":
|
|
24496
|
+
fail(`Installation not found (or not visible to you). Check the id with ${code("dashwise module status <slug>")}.`);
|
|
24497
|
+
return 1;
|
|
24498
|
+
default:
|
|
24499
|
+
fail(`Backend rejected: ${err.code} ${err.status} \u2014 ${err.message}`);
|
|
24500
|
+
return 1;
|
|
24501
|
+
}
|
|
24502
|
+
}
|
|
24503
|
+
fail(`Secrets operation failed: ${err.message}`);
|
|
24504
|
+
return 1;
|
|
24505
|
+
}
|
|
24506
|
+
|
|
24133
24507
|
// src/commands/env-export.ts
|
|
24134
24508
|
init_api_client();
|
|
24135
24509
|
init_config();
|
|
@@ -26418,6 +26792,55 @@ originsCmd.command("remove <url>").description("Remove an SSO callback origin fr
|
|
|
26418
26792
|
...cmdOpts.install !== void 0 ? { install: cmdOpts.install } : {}
|
|
26419
26793
|
});
|
|
26420
26794
|
});
|
|
26795
|
+
var secretsCmd = program.command("secrets").description(
|
|
26796
|
+
"Manage the current module install's runtime secrets (write-only: names are shown, values never are)."
|
|
26797
|
+
);
|
|
26798
|
+
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(
|
|
26799
|
+
"--install <id>",
|
|
26800
|
+
"Target a specific installation id instead of the cached dev install (production installs included). Find ids via `dashwise module status <slug>`.",
|
|
26801
|
+
parseIntOption
|
|
26802
|
+
).option("--json", "Emit JSON instead of the human-friendly list.").action(async (cmdOpts) => {
|
|
26803
|
+
process.exitCode = await secretsListCommand({
|
|
26804
|
+
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
26805
|
+
...cmdOpts.install !== void 0 ? { install: cmdOpts.install } : {},
|
|
26806
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
26807
|
+
});
|
|
26808
|
+
});
|
|
26809
|
+
secretsCmd.command("set [keys...]").description(
|
|
26810
|
+
"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."
|
|
26811
|
+
).option("--dir <path>", "Project root (default: current directory).").option(
|
|
26812
|
+
"--install <id>",
|
|
26813
|
+
"Target a specific installation id instead of the cached dev install (production installs included). Find ids via `dashwise module status <slug>`.",
|
|
26814
|
+
parseIntOption
|
|
26815
|
+
).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(
|
|
26816
|
+
"before",
|
|
26817
|
+
pc.bold(
|
|
26818
|
+
"set REPLACES THE WHOLE MAP: every key you do not give is removed (you are asked first; --yes skips the question)."
|
|
26819
|
+
)
|
|
26820
|
+
).action(
|
|
26821
|
+
async (keys, cmdOpts) => {
|
|
26822
|
+
process.exitCode = await secretsSetCommand(keys ?? [], {
|
|
26823
|
+
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
26824
|
+
...cmdOpts.install !== void 0 ? { install: cmdOpts.install } : {},
|
|
26825
|
+
...cmdOpts.fromStdin !== void 0 ? { fromStdin: cmdOpts.fromStdin } : {},
|
|
26826
|
+
...cmdOpts.fromEnvFile !== void 0 ? { fromEnvFile: cmdOpts.fromEnvFile } : {},
|
|
26827
|
+
...cmdOpts.keys !== void 0 ? { keys: cmdOpts.keys } : {},
|
|
26828
|
+
...cmdOpts.yes !== void 0 ? { yes: cmdOpts.yes } : {},
|
|
26829
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
26830
|
+
});
|
|
26831
|
+
}
|
|
26832
|
+
);
|
|
26833
|
+
secretsCmd.command("clear").description("Remove every secret value of the install.").option("--dir <path>", "Project root (default: current directory).").option(
|
|
26834
|
+
"--install <id>",
|
|
26835
|
+
"Target a specific installation id instead of the cached dev install (production installs included). Find ids via `dashwise module status <slug>`.",
|
|
26836
|
+
parseIntOption
|
|
26837
|
+
).option("-y, --yes", "Do not ask for confirmation.").action(async (cmdOpts) => {
|
|
26838
|
+
process.exitCode = await secretsClearCommand({
|
|
26839
|
+
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
26840
|
+
...cmdOpts.install !== void 0 ? { install: cmdOpts.install } : {},
|
|
26841
|
+
...cmdOpts.yes !== void 0 ? { yes: cmdOpts.yes } : {}
|
|
26842
|
+
});
|
|
26843
|
+
});
|
|
26421
26844
|
var envCmd = program.command("env").description("Environment-variable helpers for deploying a module.");
|
|
26422
26845
|
envCmd.command("export").description(
|
|
26423
26846
|
"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."
|
|
@@ -26874,7 +27297,7 @@ fieldCmd.command("set-type <table-slug> <field-slug> <new-type>").description(
|
|
|
26874
27297
|
}
|
|
26875
27298
|
);
|
|
26876
27299
|
function getVersion() {
|
|
26877
|
-
return "0.
|
|
27300
|
+
return "0.29.0";
|
|
26878
27301
|
}
|
|
26879
27302
|
function parsePositiveIdOption(value) {
|
|
26880
27303
|
if (!/^[1-9][0-9]*$/.test(value.trim())) {
|