@dashai/cli 0.25.0 → 0.27.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 +1328 -87
- 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, text, confirm, multiselect } from '@clack/prompts';
|
|
7
|
+
import { intro, log, select, isCancel, cancel, spinner, outro, text, confirm as confirm$1, multiselect } from '@clack/prompts';
|
|
8
8
|
import { Command, Option } from 'commander';
|
|
9
9
|
import open from 'open';
|
|
10
10
|
import AdmZip from 'adm-zip';
|
|
@@ -828,6 +828,74 @@ var init_connect_status = __esm({
|
|
|
828
828
|
SETUP_SETTINGS_PATH = "Modules \u2192 Installed modules \u2192 Technical \u2192 Data connections";
|
|
829
829
|
}
|
|
830
830
|
});
|
|
831
|
+
function declaredSecretNames(manifest) {
|
|
832
|
+
const names = [];
|
|
833
|
+
for (const s of manifest?.runtime?.secrets ?? []) {
|
|
834
|
+
if (typeof s?.key === "string" && SECRET_KEY.test(s.key) && !names.includes(s.key)) {
|
|
835
|
+
names.push(s.key);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
return names;
|
|
839
|
+
}
|
|
840
|
+
function envLocalSetNames(projectRoot) {
|
|
841
|
+
const set = /* @__PURE__ */ new Set();
|
|
842
|
+
const path = join(projectRoot, ".env.local");
|
|
843
|
+
if (!existsSync(path)) return set;
|
|
844
|
+
for (const line of readFileSync(path, "utf-8").split(/\r?\n/)) {
|
|
845
|
+
const m = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(line);
|
|
846
|
+
if (!m) continue;
|
|
847
|
+
const value = m[2].trim().replace(/^(['"])(.*)\1$/, "$2");
|
|
848
|
+
if (value.length > 0) set.add(m[1]);
|
|
849
|
+
}
|
|
850
|
+
return set;
|
|
851
|
+
}
|
|
852
|
+
function missingSecretNames(declared, env, envLocal2) {
|
|
853
|
+
return declared.filter((k) => !(env[k] !== void 0 && env[k] !== "") && !envLocal2.has(k));
|
|
854
|
+
}
|
|
855
|
+
function secretsBannerLine(declared, missing) {
|
|
856
|
+
if (declared.length === 0) return null;
|
|
857
|
+
if (missing.length === 0) return `${declared.length} declared, all set`;
|
|
858
|
+
return `${declared.length} declared, ${missing.length} missing: ${missing.join(", ")} (add ${missing.length === 1 ? "it" : "them"} to .env.local)`;
|
|
859
|
+
}
|
|
860
|
+
function withSecretLines(content, names) {
|
|
861
|
+
const mentioned = (k) => new RegExp(`^\\s*#?\\s*(?:export\\s+)?${k}\\s*=`, "m").test(content);
|
|
862
|
+
const add = names.filter((k) => !mentioned(k));
|
|
863
|
+
if (add.length === 0) return content;
|
|
864
|
+
const hasHeader = content.includes(ENV_LOCAL_SECRETS_HEADER[0]);
|
|
865
|
+
const block = [...hasHeader ? [] : ENV_LOCAL_SECRETS_HEADER, ...add.map((k) => `# ${k}=`)].join("\n");
|
|
866
|
+
const base = content.length === 0 || content.endsWith("\n") ? content : `${content}
|
|
867
|
+
`;
|
|
868
|
+
return `${base}${base.length > 0 && !hasHeader ? "\n" : ""}${block}
|
|
869
|
+
`;
|
|
870
|
+
}
|
|
871
|
+
function syncEnvLocalSecretLines(projectRoot, names) {
|
|
872
|
+
if (names.length === 0) return [];
|
|
873
|
+
const path = join(projectRoot, ".env.local");
|
|
874
|
+
const before = existsSync(path) ? readFileSync(path, "utf-8") : "";
|
|
875
|
+
const after = withSecretLines(before, names);
|
|
876
|
+
if (after === before) return [];
|
|
877
|
+
writeFileSync(path, after, "utf-8");
|
|
878
|
+
return names.filter((k) => !new RegExp(`^\\s*#?\\s*(?:export\\s+)?${k}\\s*=`, "m").test(before));
|
|
879
|
+
}
|
|
880
|
+
function reportSecretLines(added) {
|
|
881
|
+
if (added.length === 0) return;
|
|
882
|
+
info(
|
|
883
|
+
`Added ${added.map((k) => code(`# ${k}=`)).join(", ")} to ${code(".env.local")}: set dev values there. Hosted modules do not receive declared secrets yet.`
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
var SECRET_KEY, ENV_LOCAL_SECRETS_HEADER;
|
|
887
|
+
var init_declared_secrets = __esm({
|
|
888
|
+
"src/lib/declared-secrets.ts"() {
|
|
889
|
+
init_output();
|
|
890
|
+
SECRET_KEY = /^[A-Z][A-Z0-9_]*$/;
|
|
891
|
+
ENV_LOCAL_SECRETS_HEADER = [
|
|
892
|
+
"# Declared runtime secrets (module.json runtime.secrets[]). Set a dev value",
|
|
893
|
+
"# after the `=` and remove the `#`; `dashwise dev` names any that are missing.",
|
|
894
|
+
"# Hosted modules do not receive declared secrets yet (platform item U1);",
|
|
895
|
+
"# locally `dashwise dev` runs `next dev`, which reads them from this file."
|
|
896
|
+
];
|
|
897
|
+
}
|
|
898
|
+
});
|
|
831
899
|
function manifestPath(dir) {
|
|
832
900
|
return join(resolve(dir ?? process.cwd()), "module.json");
|
|
833
901
|
}
|
|
@@ -1119,16 +1187,16 @@ async function devCommand(options) {
|
|
|
1119
1187
|
}
|
|
1120
1188
|
const callbackOrigins = computeCallbackOrigins(port, options.callbackOrigin ?? []);
|
|
1121
1189
|
try {
|
|
1122
|
-
const
|
|
1190
|
+
const session2 = await registerDevSession(apiUrl, apiToken, {
|
|
1123
1191
|
target_workspace_id: workspaceContext.id,
|
|
1124
1192
|
callback_origins: callbackOrigins
|
|
1125
1193
|
});
|
|
1126
|
-
revokeDevSession = () => revokeDevSessionBestEffort(apiUrl, apiToken,
|
|
1127
|
-
devHandoffEnv = devSessionHandoffEnv(
|
|
1128
|
-
info(` ${dim("dev session")} ${
|
|
1194
|
+
revokeDevSession = () => revokeDevSessionBestEffort(apiUrl, apiToken, session2.id);
|
|
1195
|
+
devHandoffEnv = devSessionHandoffEnv(session2);
|
|
1196
|
+
info(` ${dim("dev session")} ${session2.id}`);
|
|
1129
1197
|
info(` ${dim("workspace")} ${workspaceContext.name} (id=${workspaceContext.id}, slug=${workspaceContext.slug})`);
|
|
1130
1198
|
info(` ${dim("callbacks")} ${callbackOrigins.join(", ")}`);
|
|
1131
|
-
info(` ${dim("expires")} ${
|
|
1199
|
+
info(` ${dim("expires")} ${session2.expires_at}`);
|
|
1132
1200
|
info(
|
|
1133
1201
|
` ${dim("sign-in")} ${Object.keys(devHandoffEnv).length > 0 ? "zero-click (server-side dev handoff armed)" : "browser SSO (backend issued no handoff secret \u2014 pre-2026-07-15 backend)"}`
|
|
1134
1202
|
);
|
|
@@ -1172,6 +1240,12 @@ async function devCommand(options) {
|
|
|
1172
1240
|
(Ensure you're logged into the right backend: ${code(`dashwise login --api-url ${apiUrl}`)}.)`
|
|
1173
1241
|
);
|
|
1174
1242
|
}
|
|
1243
|
+
{
|
|
1244
|
+
const declared = declaredSecretNames(readManifest(projectRoot));
|
|
1245
|
+
const missing = missingSecretNames(declared, childEnv, envLocalSetNames(projectRoot));
|
|
1246
|
+
const line = secretsBannerLine(declared, missing);
|
|
1247
|
+
if (line !== null) (missing.length > 0 ? warn : info)(` ${dim("secrets")} ${line}`);
|
|
1248
|
+
}
|
|
1175
1249
|
info("");
|
|
1176
1250
|
if (moduleEntry !== null && manifestSlug !== void 0) {
|
|
1177
1251
|
await reportConnectStatus({
|
|
@@ -1381,11 +1455,11 @@ async function resolveModuleApiKey(slug, entry, apiUrl, cliBearer) {
|
|
|
1381
1455
|
success(`Upgraded ${code(slug)} to a dwk_ API key (id=${minted.api_key.id}).`);
|
|
1382
1456
|
return upgraded;
|
|
1383
1457
|
}
|
|
1384
|
-
function devSessionHandoffEnv(
|
|
1385
|
-
const secret =
|
|
1458
|
+
function devSessionHandoffEnv(session2) {
|
|
1459
|
+
const secret = session2.handoff_secret;
|
|
1386
1460
|
if (typeof secret !== "string" || secret.length === 0) return {};
|
|
1387
1461
|
return {
|
|
1388
|
-
DASHWISE_DEV_SESSION_ID:
|
|
1462
|
+
DASHWISE_DEV_SESSION_ID: session2.id,
|
|
1389
1463
|
DASHWISE_DEV_HANDOFF_SECRET: secret
|
|
1390
1464
|
};
|
|
1391
1465
|
}
|
|
@@ -1412,13 +1486,13 @@ async function resolveDevPort(requested) {
|
|
|
1412
1486
|
}
|
|
1413
1487
|
async function isPortFree(port) {
|
|
1414
1488
|
const { createServer } = await import('net');
|
|
1415
|
-
return new Promise((
|
|
1489
|
+
return new Promise((resolve16) => {
|
|
1416
1490
|
const server = createServer();
|
|
1417
1491
|
server.once("error", () => {
|
|
1418
|
-
|
|
1492
|
+
resolve16(false);
|
|
1419
1493
|
});
|
|
1420
1494
|
server.once("listening", () => {
|
|
1421
|
-
server.close(() =>
|
|
1495
|
+
server.close(() => resolve16(true));
|
|
1422
1496
|
});
|
|
1423
1497
|
server.listen(port, "0.0.0.0");
|
|
1424
1498
|
});
|
|
@@ -1486,6 +1560,7 @@ var init_dev = __esm({
|
|
|
1486
1560
|
init_api_client();
|
|
1487
1561
|
init_connect_status();
|
|
1488
1562
|
init_config();
|
|
1563
|
+
init_declared_secrets();
|
|
1489
1564
|
init_manifest_io();
|
|
1490
1565
|
init_manifest_poll();
|
|
1491
1566
|
init_output();
|
|
@@ -12595,9 +12670,14 @@ dashwise module destroy-dev-install # tear down when done (cleans backend + aut
|
|
|
12595
12670
|
- **No untyped \`any\`.** The generated SDK gives you types \u2014 use
|
|
12596
12671
|
them. If you need an escape hatch, add a \`@ts-expect-error\` with
|
|
12597
12672
|
a comment explaining why.
|
|
12598
|
-
- **No secrets in code or env files.**
|
|
12599
|
-
\`module.json
|
|
12600
|
-
|
|
12673
|
+
- **No secrets in code or committed env files.** Declare each one in
|
|
12674
|
+
\`runtime.secrets[]\` in \`module.json\` (by name; values never go in the
|
|
12675
|
+
repo). Hosted modules do not receive declared secrets yet (platform item
|
|
12676
|
+
U1): until that lands, a hosted module cannot read them. Locally, put dev
|
|
12677
|
+
values in the gitignored \`.env.local\` (\`dashwise init\` and
|
|
12678
|
+
\`dashwise module generate\` add a commented \`# KEY=\` line per declared
|
|
12679
|
+
secret); \`dashwise dev\` runs \`next dev\`, which loads them, and names
|
|
12680
|
+
any that are missing.
|
|
12601
12681
|
|
|
12602
12682
|
## Pointers
|
|
12603
12683
|
|
|
@@ -12773,9 +12853,7 @@ or in their own \`CLAUDE.md\` / \`AGENTS.md\`), follow them instead.
|
|
|
12773
12853
|
person in plain words that the changes are saved and that Release in
|
|
12774
12854
|
Dashwise includes them. Don't ask them to merge anything.
|
|
12775
12855
|
- Never force-push and never rewrite the default branch's history.
|
|
12776
|
-
-
|
|
12777
|
-
pushed, tell them it is ready: in Dashwise open the module, click Release,
|
|
12778
|
-
then Take live.
|
|
12856
|
+
- When the work is pushed, tell the person it is ready to release.
|
|
12779
12857
|
|
|
12780
12858
|
## Cloud workflow
|
|
12781
12859
|
|
|
@@ -12786,8 +12864,8 @@ or in their own \`CLAUDE.md\` / \`AGENTS.md\`), follow them instead.
|
|
|
12786
12864
|
- \`npm run smoke:production\` starts the built app on a local loopback port. If a cloud sandbox refuses that port, rerun the command with the network permission; never skip the check.
|
|
12787
12865
|
- Report what you changed, the pushed SHA and the checks you ran, including failures, in plain words.
|
|
12788
12866
|
- Never print, persist or commit secrets. Repository code delivery needs no Dashwise token. Do not log in or access company data for setup or checks; see "Dashwise login" for full development.
|
|
12789
|
-
-
|
|
12790
|
-
-
|
|
12867
|
+
- Before releasing, installing/updating an installation or taking live, propose the exact action in the chat and wait for the person to accept or change it; never do it on your own.
|
|
12868
|
+
- When the person accepts, do exactly that action if your Dashwise connection allows it; if Dashwise refuses, tell them what it said (for example a permission to turn on in My Account). If you have no way to do it, tell them in one sentence where to click (in Dashwise open the module, click Release, then Take live). Never ask for a \`dashwise login\` or a token just to release.
|
|
12791
12869
|
|
|
12792
12870
|
## If you can't see the repository
|
|
12793
12871
|
|
|
@@ -13468,6 +13546,9 @@ async function moduleInitCommand(slugArg, options) {
|
|
|
13468
13546
|
writeFileSync(absPath, content, "utf-8");
|
|
13469
13547
|
written++;
|
|
13470
13548
|
}
|
|
13549
|
+
reportSecretLines(
|
|
13550
|
+
syncEnvLocalSecretLines(dir, declaredSecretNames(readManifest(dir)))
|
|
13551
|
+
);
|
|
13471
13552
|
}
|
|
13472
13553
|
if (writeMode !== "none" && !cloudReady) {
|
|
13473
13554
|
process.stdout.write(
|
|
@@ -13710,10 +13791,297 @@ var init_init = __esm({
|
|
|
13710
13791
|
init_output();
|
|
13711
13792
|
init_api_client();
|
|
13712
13793
|
init_config();
|
|
13794
|
+
init_declared_secrets();
|
|
13713
13795
|
init_manifest_io();
|
|
13714
13796
|
init_workspaces();
|
|
13715
13797
|
}
|
|
13716
13798
|
});
|
|
13799
|
+
|
|
13800
|
+
// src/lib/manifest-evolution.ts
|
|
13801
|
+
function compareManifests(base, head, history = []) {
|
|
13802
|
+
const violations = [];
|
|
13803
|
+
const additions = [];
|
|
13804
|
+
const warnings = [];
|
|
13805
|
+
const v = (rule, where, detail) => violations.push({ rule, where, detail });
|
|
13806
|
+
const baseTables = tablesOf(base);
|
|
13807
|
+
const headTables = tablesOf(head);
|
|
13808
|
+
const everTables = /* @__PURE__ */ new Set();
|
|
13809
|
+
const everFields = /* @__PURE__ */ new Set();
|
|
13810
|
+
for (const m of history) {
|
|
13811
|
+
for (const [slug, t] of tablesOf(m)) {
|
|
13812
|
+
everTables.add(slug);
|
|
13813
|
+
for (const f of t.fields ?? []) everFields.add(`${slug}.${f.slug}`);
|
|
13814
|
+
}
|
|
13815
|
+
}
|
|
13816
|
+
for (const [slug, bt] of baseTables) {
|
|
13817
|
+
const ht = headTables.get(slug);
|
|
13818
|
+
if (!ht) {
|
|
13819
|
+
v("removed_table", slug, "tables are never removed; deprecate instead");
|
|
13820
|
+
continue;
|
|
13821
|
+
}
|
|
13822
|
+
const bf = fieldsOf(bt);
|
|
13823
|
+
const hf = fieldsOf(ht);
|
|
13824
|
+
for (const [fs, f] of bf) {
|
|
13825
|
+
const g = hf.get(fs);
|
|
13826
|
+
const where = `${slug}.${fs}`;
|
|
13827
|
+
if (!g) {
|
|
13828
|
+
v("removed_field", where, "fields are never removed; stop writing it and set human_edit: open");
|
|
13829
|
+
continue;
|
|
13830
|
+
}
|
|
13831
|
+
if (f.type !== g.type) v("retyped_field", where, `${f.type} \u2192 ${g.type}`);
|
|
13832
|
+
if ((f.decimal_places ?? null) !== (g.decimal_places ?? null)) {
|
|
13833
|
+
v("retyped_field", where, `decimal_places ${f.decimal_places ?? "none"} \u2192 ${g.decimal_places ?? "none"}`);
|
|
13834
|
+
}
|
|
13835
|
+
if ((f.target ?? null) !== (g.target ?? null)) v("retyped_field", where, `link target ${f.target} \u2192 ${g.target}`);
|
|
13836
|
+
if (f.required !== true && g.required === true) {
|
|
13837
|
+
v("required_tightened", where, "old rows are empty; enforce it in the tool layer instead");
|
|
13838
|
+
}
|
|
13839
|
+
const bo = f.options ?? [];
|
|
13840
|
+
const ho = g.options ?? [];
|
|
13841
|
+
for (const [i, o] of bo.entries()) {
|
|
13842
|
+
if (!ho.includes(o)) v("removed_option", where, `option "${String(o)}" removed`);
|
|
13843
|
+
else if (ho[i] !== o) {
|
|
13844
|
+
v("reordered_option", where, `option "${String(o)}" moved from ${i} to ${ho.indexOf(o)}; options are append-only`);
|
|
13845
|
+
}
|
|
13846
|
+
}
|
|
13847
|
+
for (const o of ho.slice(bo.length)) if (!bo.includes(o)) additions.push(`option ${where} "${String(o)}"`);
|
|
13848
|
+
}
|
|
13849
|
+
for (const [fs] of hf) {
|
|
13850
|
+
if (bf.has(fs)) continue;
|
|
13851
|
+
if (everFields.has(`${slug}.${fs}`)) {
|
|
13852
|
+
v("slug_reused", `${slug}.${fs}`, "this slug was removed in an earlier version and may not come back");
|
|
13853
|
+
} else additions.push(`field ${slug}.${fs}`);
|
|
13854
|
+
}
|
|
13855
|
+
const hu = new Set(uniqueIndexes(ht));
|
|
13856
|
+
for (const idx of uniqueIndexes(bt)) {
|
|
13857
|
+
if (!hu.has(idx)) v("dropped_unique_index", `${slug}(${idx})`, "a unique index is a protocol lock; it may not be dropped or changed");
|
|
13858
|
+
}
|
|
13859
|
+
const bu = new Set(uniqueIndexes(bt));
|
|
13860
|
+
for (const idx of hu) {
|
|
13861
|
+
if (bu.has(idx)) continue;
|
|
13862
|
+
const overExisting = idx.split(",").some((f) => f.length > 0 && bf.has(f));
|
|
13863
|
+
if (overExisting) {
|
|
13864
|
+
v("unique_on_populated", `${slug}(${idx})`, "a new unique index (indexes[].unique) over existing fields fails on duplicates already stored; add a new field instead");
|
|
13865
|
+
} else additions.push(`unique index ${slug}(${idx})`);
|
|
13866
|
+
}
|
|
13867
|
+
}
|
|
13868
|
+
for (const [slug] of headTables) {
|
|
13869
|
+
if (baseTables.has(slug)) continue;
|
|
13870
|
+
if (everTables.has(slug)) v("slug_reused", slug, "this table slug was removed in an earlier version and may not come back");
|
|
13871
|
+
else additions.push(`table ${slug}`);
|
|
13872
|
+
}
|
|
13873
|
+
const baseViews = new Set((base?.owns?.views ?? []).map((x) => x.slug));
|
|
13874
|
+
const headViews = new Set((head?.owns?.views ?? []).map((x) => x.slug));
|
|
13875
|
+
for (const s of baseViews) if (!headViews.has(s)) warnings.push(`view ${s} removed (callers of qbViews.${s} break)`);
|
|
13876
|
+
for (const s of headViews) if (!baseViews.has(s)) additions.push(`view ${s}`);
|
|
13877
|
+
return { violations, additions, warnings };
|
|
13878
|
+
}
|
|
13879
|
+
var tablesOf, fieldsOf, uniqueIndexes;
|
|
13880
|
+
var init_manifest_evolution = __esm({
|
|
13881
|
+
"src/lib/manifest-evolution.ts"() {
|
|
13882
|
+
tablesOf = (m) => new Map((m?.owns?.tables ?? []).map((t) => [t.slug, t]));
|
|
13883
|
+
fieldsOf = (t) => new Map((t?.fields ?? []).map((f) => [f.slug, f]));
|
|
13884
|
+
uniqueIndexes = (t) => (t?.indexes ?? []).filter((i) => i.unique === true).map((i) => (i.fields ?? []).join(","));
|
|
13885
|
+
}
|
|
13886
|
+
});
|
|
13887
|
+
|
|
13888
|
+
// src/commands/module/diff-check.ts
|
|
13889
|
+
var diff_check_exports = {};
|
|
13890
|
+
__export(diff_check_exports, {
|
|
13891
|
+
EvolutionCheckError: () => EvolutionCheckError,
|
|
13892
|
+
moduleDiffCheckCommand: () => moduleDiffCheckCommand,
|
|
13893
|
+
printEvolutionReport: () => printEvolutionReport,
|
|
13894
|
+
publishEvolutionGate: () => publishEvolutionGate,
|
|
13895
|
+
runEvolutionCheck: () => runEvolutionCheck
|
|
13896
|
+
});
|
|
13897
|
+
function readJson(path) {
|
|
13898
|
+
try {
|
|
13899
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
13900
|
+
} catch (err) {
|
|
13901
|
+
throw new EvolutionCheckError(`could not read ${path}: ${err.message}`);
|
|
13902
|
+
}
|
|
13903
|
+
}
|
|
13904
|
+
function git(cwd, args) {
|
|
13905
|
+
try {
|
|
13906
|
+
return execFileSync("git", ["-C", cwd, ...args], {
|
|
13907
|
+
encoding: "utf8",
|
|
13908
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
13909
|
+
maxBuffer: 64 * 1024 * 1024
|
|
13910
|
+
});
|
|
13911
|
+
} catch (err) {
|
|
13912
|
+
const stderr = err.stderr?.trim();
|
|
13913
|
+
throw new EvolutionCheckError(`git ${args.join(" ")} failed${stderr ? `: ${stderr}` : ""}`);
|
|
13914
|
+
}
|
|
13915
|
+
}
|
|
13916
|
+
async function runEvolutionCheck(options) {
|
|
13917
|
+
const projectRoot = resolve(options.dir ?? process.cwd());
|
|
13918
|
+
const headPath = options.head ? resolve(options.head) : join(projectRoot, "module.json");
|
|
13919
|
+
if (!existsSync(headPath)) throw new EvolutionCheckError(`no manifest at ${headPath}`);
|
|
13920
|
+
const head = readJson(headPath);
|
|
13921
|
+
const baseFile = options.baseFile ?? (options.base && existsSync(options.base) ? options.base : void 0);
|
|
13922
|
+
if (baseFile) {
|
|
13923
|
+
const base = readJson(baseFile);
|
|
13924
|
+
return { ...compareManifests(base, head), base: baseFile, head: headPath, history: 0, nothingReleased: false };
|
|
13925
|
+
}
|
|
13926
|
+
if (options.base) {
|
|
13927
|
+
const ref = options.base;
|
|
13928
|
+
const base = JSON.parse(git(projectRoot, ["show", `${ref}:./module.json`]));
|
|
13929
|
+
let history2 = [];
|
|
13930
|
+
if (!options.noHistory) {
|
|
13931
|
+
const shas = git(projectRoot, ["log", "--format=%H", "--reverse", ref, "--", "module.json"]).split("\n").filter(Boolean);
|
|
13932
|
+
history2 = shas.slice(0, -1).map((sha) => JSON.parse(git(projectRoot, ["show", `${sha}:./module.json`])));
|
|
13933
|
+
}
|
|
13934
|
+
return {
|
|
13935
|
+
...compareManifests(base, head, history2),
|
|
13936
|
+
base: `${ref}:module.json`,
|
|
13937
|
+
head: headPath,
|
|
13938
|
+
history: history2.length,
|
|
13939
|
+
nothingReleased: false
|
|
13940
|
+
};
|
|
13941
|
+
}
|
|
13942
|
+
const slug = head.module?.slug;
|
|
13943
|
+
if (!slug) throw new EvolutionCheckError(`${headPath} has no module.slug`);
|
|
13944
|
+
let mod;
|
|
13945
|
+
let versions;
|
|
13946
|
+
try {
|
|
13947
|
+
mod = await apiRequest(
|
|
13948
|
+
"GET",
|
|
13949
|
+
`/api/modules/by-slug/${encodeURIComponent(slug)}`
|
|
13950
|
+
);
|
|
13951
|
+
if (!mod.current_version) {
|
|
13952
|
+
return {
|
|
13953
|
+
violations: [],
|
|
13954
|
+
additions: [],
|
|
13955
|
+
warnings: [],
|
|
13956
|
+
base: "(nothing released)",
|
|
13957
|
+
head: headPath,
|
|
13958
|
+
history: 0,
|
|
13959
|
+
nothingReleased: true
|
|
13960
|
+
};
|
|
13961
|
+
}
|
|
13962
|
+
versions = await apiRequest("GET", `/api/modules/${mod.id}/versions`);
|
|
13963
|
+
} catch (err) {
|
|
13964
|
+
const why = err instanceof ApiError ? `${err.code || err.status} ${err.message}` : err.message;
|
|
13965
|
+
throw new EvolutionCheckError(
|
|
13966
|
+
`could not read the released manifest of ${slug} (${why}); pass --base <ref|file> to check offline`
|
|
13967
|
+
);
|
|
13968
|
+
}
|
|
13969
|
+
const released = versions.find((v) => v.version === mod.current_version);
|
|
13970
|
+
if (!released?.manifest_jsonb) {
|
|
13971
|
+
throw new EvolutionCheckError(
|
|
13972
|
+
`the platform returned no manifest for ${slug} v${mod.current_version}; pass --base <ref|file> to check offline`
|
|
13973
|
+
);
|
|
13974
|
+
}
|
|
13975
|
+
const releasedAt = released.created_on ? Date.parse(released.created_on) : Number.POSITIVE_INFINITY;
|
|
13976
|
+
const history = options.noHistory ? [] : versions.filter((v) => v !== released && v.manifest_jsonb && v.created_on && Date.parse(v.created_on) < releasedAt).sort((a, b) => Date.parse(a.created_on) - Date.parse(b.created_on)).map((v) => v.manifest_jsonb);
|
|
13977
|
+
return {
|
|
13978
|
+
...compareManifests(released.manifest_jsonb, head, history),
|
|
13979
|
+
base: `${slug} v${mod.current_version} (released)`,
|
|
13980
|
+
head: headPath,
|
|
13981
|
+
history: history.length,
|
|
13982
|
+
nothingReleased: false
|
|
13983
|
+
};
|
|
13984
|
+
}
|
|
13985
|
+
function printEvolutionReport(r, print = out) {
|
|
13986
|
+
print(`module diff --check: ${r.head} against ${r.base} (${r.history} older version${r.history === 1 ? "" : "s"} scanned)`);
|
|
13987
|
+
for (const a of r.additions) print(` + ${a}`);
|
|
13988
|
+
for (const w of r.warnings) print(` ! ${w}`);
|
|
13989
|
+
for (const x of r.violations) print(` \u2717 ${x.rule}: ${x.where}: ${x.detail}`);
|
|
13990
|
+
}
|
|
13991
|
+
async function moduleDiffCheckCommand(options) {
|
|
13992
|
+
let r;
|
|
13993
|
+
try {
|
|
13994
|
+
r = await runEvolutionCheck(options);
|
|
13995
|
+
} catch (err) {
|
|
13996
|
+
if (err instanceof EvolutionCheckError) {
|
|
13997
|
+
if (options.json) out(JSON.stringify({ ok: false, error: { code: "CHECK_FAILED", message: err.message } }));
|
|
13998
|
+
else fail(`module diff --check: ${err.message}`);
|
|
13999
|
+
return 2;
|
|
14000
|
+
}
|
|
14001
|
+
throw err;
|
|
14002
|
+
}
|
|
14003
|
+
if (options.json) {
|
|
14004
|
+
out(JSON.stringify({ ok: r.violations.length === 0, ...r }, null, 2));
|
|
14005
|
+
return r.violations.length === 0 ? 0 : 1;
|
|
14006
|
+
}
|
|
14007
|
+
if (r.nothingReleased) {
|
|
14008
|
+
success("Nothing released yet: every change is additive.");
|
|
14009
|
+
return 0;
|
|
14010
|
+
}
|
|
14011
|
+
printEvolutionReport(r);
|
|
14012
|
+
if (r.violations.length === 0) {
|
|
14013
|
+
success("additive");
|
|
14014
|
+
return 0;
|
|
14015
|
+
}
|
|
14016
|
+
fail(`${r.violations.length} violation(s): not additive. Released copies would lose data or fail to upgrade.`);
|
|
14017
|
+
info(`Add a new field or table instead, and stop writing the old one (${code('human_edit: "open"')}).`);
|
|
14018
|
+
return 1;
|
|
14019
|
+
}
|
|
14020
|
+
async function publishEvolutionGate(projectRoot, strict) {
|
|
14021
|
+
let r;
|
|
14022
|
+
try {
|
|
14023
|
+
r = await runEvolutionCheck({ dir: projectRoot });
|
|
14024
|
+
} catch (err) {
|
|
14025
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
14026
|
+
if (strict) {
|
|
14027
|
+
fail(`Evolution check could not run (${msg}). Refusing with --strict. Nothing was published.`);
|
|
14028
|
+
return 1;
|
|
14029
|
+
}
|
|
14030
|
+
warn(`Evolution check skipped: ${msg}.`);
|
|
14031
|
+
return 0;
|
|
14032
|
+
}
|
|
14033
|
+
if (r.nothingReleased || r.violations.length === 0) return 0;
|
|
14034
|
+
printEvolutionReport(r, (s) => (strict ? fail : warn)(s));
|
|
14035
|
+
if (strict) {
|
|
14036
|
+
fail(
|
|
14037
|
+
`module.json is not an additive evolution of the released version (${r.violations.length} violation(s)). Refusing with --strict. Nothing was published.`
|
|
14038
|
+
);
|
|
14039
|
+
return 1;
|
|
14040
|
+
}
|
|
14041
|
+
warn(
|
|
14042
|
+
`module.json is not an additive evolution of the released version: installs that upgrade lose the data above. ${code("--strict")} turns this into a refusal; ${code("dashwise module diff --check")} shows it on its own.`
|
|
14043
|
+
);
|
|
14044
|
+
return 0;
|
|
14045
|
+
}
|
|
14046
|
+
var EvolutionCheckError;
|
|
14047
|
+
var init_diff_check = __esm({
|
|
14048
|
+
"src/commands/module/diff-check.ts"() {
|
|
14049
|
+
init_api_client();
|
|
14050
|
+
init_manifest_evolution();
|
|
14051
|
+
init_output();
|
|
14052
|
+
EvolutionCheckError = class extends Error {
|
|
14053
|
+
};
|
|
14054
|
+
}
|
|
14055
|
+
});
|
|
14056
|
+
|
|
14057
|
+
// src/lib/manifest-reference-lint.ts
|
|
14058
|
+
function referenceFieldWarnings(manifest) {
|
|
14059
|
+
const tables = manifest?.owns?.tables;
|
|
14060
|
+
if (!Array.isArray(tables)) return [];
|
|
14061
|
+
const warnings = [];
|
|
14062
|
+
for (const table of tables) {
|
|
14063
|
+
if (typeof table?.slug !== "string" || !Array.isArray(table.fields)) continue;
|
|
14064
|
+
for (const field of table.fields) {
|
|
14065
|
+
if (typeof field?.slug !== "string" || typeof field.type !== "string") continue;
|
|
14066
|
+
if (field.slug === "id" || !REFERENCE_NAME.test(field.slug)) continue;
|
|
14067
|
+
if (!PLAIN_TYPES.has(field.type)) continue;
|
|
14068
|
+
warnings.push({
|
|
14069
|
+
table: table.slug,
|
|
14070
|
+
field: field.slug,
|
|
14071
|
+
type: field.type,
|
|
14072
|
+
message: `${table.slug}.${field.slug} is a ${field.type} field named like a reference. If it points at a row in Dashwise, use a link_row field: plain ids break silently when data is copied to another installation. Ignore this for ids from outside systems.`
|
|
14073
|
+
});
|
|
14074
|
+
}
|
|
14075
|
+
}
|
|
14076
|
+
return warnings;
|
|
14077
|
+
}
|
|
14078
|
+
var REFERENCE_NAME, PLAIN_TYPES;
|
|
14079
|
+
var init_manifest_reference_lint = __esm({
|
|
14080
|
+
"src/lib/manifest-reference-lint.ts"() {
|
|
14081
|
+
REFERENCE_NAME = /(?:_id|_ref|Id|Ref)$/;
|
|
14082
|
+
PLAIN_TYPES = /* @__PURE__ */ new Set(["number", "text", "autonumber"]);
|
|
14083
|
+
}
|
|
14084
|
+
});
|
|
13717
14085
|
function loadSchemaCommandContext(opts) {
|
|
13718
14086
|
const projectRoot = resolve(opts.dir ?? process.cwd());
|
|
13719
14087
|
const manifestPath2 = join(projectRoot, "module.json");
|
|
@@ -13786,6 +14154,89 @@ var init_dotenv_local = __esm({
|
|
|
13786
14154
|
}
|
|
13787
14155
|
});
|
|
13788
14156
|
|
|
14157
|
+
// src/lib/install-target.ts
|
|
14158
|
+
function keyDisplayPrefix(apiKey) {
|
|
14159
|
+
if (!apiKey.startsWith(KEY_SCHEME)) return null;
|
|
14160
|
+
const prefix = apiKey.slice(KEY_SCHEME.length, KEY_SCHEME.length + KEY_PREFIX_CHARS);
|
|
14161
|
+
return prefix.length === KEY_PREFIX_CHARS ? prefix : null;
|
|
14162
|
+
}
|
|
14163
|
+
async function keyBelongsToInstall(apiKey, installationId, options, now = /* @__PURE__ */ new Date()) {
|
|
14164
|
+
const prefix = keyDisplayPrefix(apiKey);
|
|
14165
|
+
if (prefix === null) return false;
|
|
14166
|
+
const { api_keys } = await listApiKeys(installationId, options);
|
|
14167
|
+
return api_keys.some(
|
|
14168
|
+
(k) => k.installation_id === installationId && k.key_prefix === prefix && (k.status === void 0 || k.status === "active") && k.revoked_at === null && (k.expires_at === null || new Date(k.expires_at) > now)
|
|
14169
|
+
);
|
|
14170
|
+
}
|
|
14171
|
+
function formatInstallTarget(id, detail) {
|
|
14172
|
+
return detail ? `\u2192 install #${id} (${detail})` : `\u2192 install #${id}`;
|
|
14173
|
+
}
|
|
14174
|
+
async function nameKeyTarget(args) {
|
|
14175
|
+
const { apiKey, keySource, entry, cfg, apiUrl, install } = args;
|
|
14176
|
+
const request = cfg ? { baseUrl: apiUrl, auth: { token: cfg.token }, signal: AbortSignal.timeout(LOOKUP_TIMEOUT_MS) } : null;
|
|
14177
|
+
if (install === void 0) {
|
|
14178
|
+
if (keySource !== "cached" || !entry) {
|
|
14179
|
+
info(
|
|
14180
|
+
`\u2192 install not checked: ${code("DASHWISE_API_KEY")} comes from ${keySource === "environment" ? "the environment" : code(".env.local")}. Pass ${code("--install <id>")} to name its target and refuse a mismatch.`
|
|
14181
|
+
);
|
|
14182
|
+
return 0;
|
|
14183
|
+
}
|
|
14184
|
+
if (!request || entry.workspaceId === void 0) {
|
|
14185
|
+
info(formatInstallTarget(entry.installationId, "the cached dev install"));
|
|
14186
|
+
return 0;
|
|
14187
|
+
}
|
|
14188
|
+
try {
|
|
14189
|
+
const workspaces = await apiRequest("GET", "/api/workspaces", void 0, request);
|
|
14190
|
+
const ws = workspaces.find((w) => w.id === entry.workspaceId);
|
|
14191
|
+
info(formatInstallTarget(entry.installationId, `${ws ? ws.slug : `workspace #${entry.workspaceId}`}, local`));
|
|
14192
|
+
} catch {
|
|
14193
|
+
info(formatInstallTarget(entry.installationId));
|
|
14194
|
+
}
|
|
14195
|
+
return 0;
|
|
14196
|
+
}
|
|
14197
|
+
const source = keySource === "cached" ? "the cached key" : `${code("DASHWISE_API_KEY")} (from ${keySource === "environment" ? "the environment" : code(".env.local")})`;
|
|
14198
|
+
if (!request) {
|
|
14199
|
+
fail(
|
|
14200
|
+
`${code("--install")} looks the install up with your CLI login, and there is none. Run ${code("dashwise login")} first. Nothing was run.`
|
|
14201
|
+
);
|
|
14202
|
+
return 1;
|
|
14203
|
+
}
|
|
14204
|
+
let belongs;
|
|
14205
|
+
try {
|
|
14206
|
+
belongs = await keyBelongsToInstall(apiKey, install, request);
|
|
14207
|
+
} catch (err) {
|
|
14208
|
+
fail(
|
|
14209
|
+
`Could not read the keys of install #${install} to confirm ${source} (${describe(err)}). Refusing to run against an unconfirmed install. Nothing was run.`
|
|
14210
|
+
);
|
|
14211
|
+
return 1;
|
|
14212
|
+
}
|
|
14213
|
+
const cachedDev = entry?.installationId === install ? "the cached dev install, " : "";
|
|
14214
|
+
if (!belongs) {
|
|
14215
|
+
const other = entry && entry.installationId !== install && entry.apiKey === apiKey ? ` It is the cached key of install #${entry.installationId}.` : "";
|
|
14216
|
+
fail(`${source} is not a live key of install #${install}.${other} Nothing was run.`);
|
|
14217
|
+
info(
|
|
14218
|
+
`Use a key of install #${install} (mint one with ${code(`dashwise api-keys create <name> --install ${install}`)}), or drop ${code("--install")} to run against the key's own install.`
|
|
14219
|
+
);
|
|
14220
|
+
return 1;
|
|
14221
|
+
}
|
|
14222
|
+
info(formatInstallTarget(install, `${cachedDev}key checked`));
|
|
14223
|
+
return 0;
|
|
14224
|
+
}
|
|
14225
|
+
function describe(err) {
|
|
14226
|
+
if (err instanceof ApiError) return `${err.code || err.status} ${err.message}`;
|
|
14227
|
+
return err.message;
|
|
14228
|
+
}
|
|
14229
|
+
var KEY_PREFIX_CHARS, KEY_SCHEME, LOOKUP_TIMEOUT_MS;
|
|
14230
|
+
var init_install_target = __esm({
|
|
14231
|
+
"src/lib/install-target.ts"() {
|
|
14232
|
+
init_api_client();
|
|
14233
|
+
init_output();
|
|
14234
|
+
KEY_PREFIX_CHARS = 8;
|
|
14235
|
+
KEY_SCHEME = "dwk_";
|
|
14236
|
+
LOOKUP_TIMEOUT_MS = 1500;
|
|
14237
|
+
}
|
|
14238
|
+
});
|
|
14239
|
+
|
|
13789
14240
|
// src/commands/run.ts
|
|
13790
14241
|
var run_exports = {};
|
|
13791
14242
|
__export(run_exports, {
|
|
@@ -13798,6 +14249,10 @@ async function runCommand(cmdArgs, options) {
|
|
|
13798
14249
|
);
|
|
13799
14250
|
return 1;
|
|
13800
14251
|
}
|
|
14252
|
+
if (options.install !== void 0 && (!Number.isInteger(options.install) || options.install <= 0)) {
|
|
14253
|
+
fail(`--install expects a positive integer installation id (got "${options.install}").`);
|
|
14254
|
+
return 1;
|
|
14255
|
+
}
|
|
13801
14256
|
const projectRoot = resolve(options.dir ?? process.cwd());
|
|
13802
14257
|
let manifestSlug;
|
|
13803
14258
|
try {
|
|
@@ -13810,6 +14265,7 @@ async function runCommand(cmdArgs, options) {
|
|
|
13810
14265
|
fail(`module.json has no ${code("module.slug")} \u2014 the manifest is malformed.`);
|
|
13811
14266
|
return 1;
|
|
13812
14267
|
}
|
|
14268
|
+
const keyInShell = process.env.DASHWISE_API_KEY !== void 0;
|
|
13813
14269
|
hydrateEnvFromDotenvLocal(projectRoot);
|
|
13814
14270
|
const cfg = (() => {
|
|
13815
14271
|
try {
|
|
@@ -13820,8 +14276,19 @@ async function runCommand(cmdArgs, options) {
|
|
|
13820
14276
|
})();
|
|
13821
14277
|
const apiUrl = process.env.DASHWISE_API_URL ?? cfg?.apiUrl ?? "http://localhost:3000";
|
|
13822
14278
|
let apiKey = process.env.DASHWISE_API_KEY;
|
|
14279
|
+
let keySource = keyInShell ? "environment" : ".env.local";
|
|
14280
|
+
let entry = null;
|
|
13823
14281
|
if (!apiKey) {
|
|
13824
|
-
|
|
14282
|
+
entry = readModuleEntry(manifestSlug);
|
|
14283
|
+
if (entry && options.install !== void 0 && entry.installationId !== options.install) {
|
|
14284
|
+
fail(
|
|
14285
|
+
`No key for install #${options.install}: the cached ${code("dwk_")} key of ${code(manifestSlug)} belongs to install #${entry.installationId}. Nothing was run.`
|
|
14286
|
+
);
|
|
14287
|
+
info(
|
|
14288
|
+
`Set ${code("DASHWISE_API_KEY")} to a key of install #${options.install} (mint one with ${code(`dashwise api-keys create <name> --install ${options.install}`)}).`
|
|
14289
|
+
);
|
|
14290
|
+
return 1;
|
|
14291
|
+
}
|
|
13825
14292
|
if (cfg) {
|
|
13826
14293
|
const healed = await resolveModuleApiKey(
|
|
13827
14294
|
manifestSlug,
|
|
@@ -13832,6 +14299,7 @@ async function runCommand(cmdArgs, options) {
|
|
|
13832
14299
|
if (healed) entry = healed;
|
|
13833
14300
|
}
|
|
13834
14301
|
apiKey = entry?.apiKey;
|
|
14302
|
+
keySource = "cached";
|
|
13835
14303
|
}
|
|
13836
14304
|
if (!apiKey) {
|
|
13837
14305
|
fail(
|
|
@@ -13842,6 +14310,15 @@ async function runCommand(cmdArgs, options) {
|
|
|
13842
14310
|
);
|
|
13843
14311
|
return 1;
|
|
13844
14312
|
}
|
|
14313
|
+
const target = await nameKeyTarget({
|
|
14314
|
+
apiKey,
|
|
14315
|
+
keySource,
|
|
14316
|
+
entry: entry ?? readModuleEntry(manifestSlug),
|
|
14317
|
+
cfg,
|
|
14318
|
+
apiUrl,
|
|
14319
|
+
...options.install !== void 0 ? { install: options.install } : {}
|
|
14320
|
+
});
|
|
14321
|
+
if (target !== 0) return target;
|
|
13845
14322
|
const [cmd, ...rest] = cmdArgs;
|
|
13846
14323
|
info(
|
|
13847
14324
|
`${dim("run")} ${cmd} ${rest.join(" ")} ${dim(`(DASHWISE_API_URL=${apiUrl}, DASHWISE_API_KEY injected)`)}`
|
|
@@ -13871,6 +14348,7 @@ var init_run = __esm({
|
|
|
13871
14348
|
"src/commands/run.ts"() {
|
|
13872
14349
|
init_config();
|
|
13873
14350
|
init_dotenv_local();
|
|
14351
|
+
init_install_target();
|
|
13874
14352
|
init_manifest_io();
|
|
13875
14353
|
init_output();
|
|
13876
14354
|
init_dev();
|
|
@@ -13914,7 +14392,7 @@ async function destroyDevInstallCommand(options) {
|
|
|
13914
14392
|
return 0;
|
|
13915
14393
|
}
|
|
13916
14394
|
if (!options.yes && process.stdout.isTTY) {
|
|
13917
|
-
const choice = await confirm({
|
|
14395
|
+
const choice = await confirm$1({
|
|
13918
14396
|
message: `Destroy dev install ${code(`${slug} (id=${entry.installationId})`)}? Tables and rows will be soft-deleted (recoverable for 30 days via dashboard trash).`
|
|
13919
14397
|
});
|
|
13920
14398
|
if (isCancel(choice) || !choice) {
|
|
@@ -14161,6 +14639,7 @@ async function schemaPushCommand(options) {
|
|
|
14161
14639
|
return surfaceSchemaPushError(err, slug);
|
|
14162
14640
|
}
|
|
14163
14641
|
printApplySummary(applied);
|
|
14642
|
+
for (const w of referenceFieldWarnings(manifest)) warn(w.message);
|
|
14164
14643
|
if (dwkAuth) {
|
|
14165
14644
|
try {
|
|
14166
14645
|
const { manifest: canonical } = await getInstallationSchema(dwkAuth);
|
|
@@ -14309,6 +14788,7 @@ var init_push = __esm({
|
|
|
14309
14788
|
init_manifest_io();
|
|
14310
14789
|
init_manifest_merge();
|
|
14311
14790
|
init_output();
|
|
14791
|
+
init_manifest_reference_lint();
|
|
14312
14792
|
}
|
|
14313
14793
|
});
|
|
14314
14794
|
|
|
@@ -15853,7 +16333,7 @@ function startMcpProxy(opts) {
|
|
|
15853
16333
|
const reconnectMs = opts.reconnectMs ?? 2e3;
|
|
15854
16334
|
const verbose = opts.verbose === true;
|
|
15855
16335
|
const readOnly = opts.readOnly === true;
|
|
15856
|
-
let
|
|
16336
|
+
let session2 = opts.session;
|
|
15857
16337
|
let sessionId = null;
|
|
15858
16338
|
let protocolVersion = null;
|
|
15859
16339
|
let readTools = null;
|
|
@@ -15866,8 +16346,8 @@ function startMcpProxy(opts) {
|
|
|
15866
16346
|
let streamUnavailable = false;
|
|
15867
16347
|
let streamStarted = false;
|
|
15868
16348
|
let resolveDone;
|
|
15869
|
-
const done = new Promise((
|
|
15870
|
-
resolveDone =
|
|
16349
|
+
const done = new Promise((resolve16) => {
|
|
16350
|
+
resolveDone = resolve16;
|
|
15871
16351
|
});
|
|
15872
16352
|
const trace = (line) => {
|
|
15873
16353
|
if (verbose) log2(line);
|
|
@@ -15882,7 +16362,7 @@ function startMcpProxy(opts) {
|
|
|
15882
16362
|
parsed = JSON.parse(text4);
|
|
15883
16363
|
} catch {
|
|
15884
16364
|
log2(
|
|
15885
|
-
`Discarded a non-JSON response from ${redactUrl(
|
|
16365
|
+
`Discarded a non-JSON response from ${redactUrl(session2.connectUrl)} (${source}); stdout carries MCP frames only.`
|
|
15886
16366
|
);
|
|
15887
16367
|
trace(`discarded ${Buffer.byteLength(text4, "utf8")} bytes of non-JSON`);
|
|
15888
16368
|
return;
|
|
@@ -15892,7 +16372,7 @@ function startMcpProxy(opts) {
|
|
|
15892
16372
|
) : parsed !== null && typeof parsed === "object" && "jsonrpc" in parsed;
|
|
15893
16373
|
if (!looksJsonRpc) {
|
|
15894
16374
|
log2(
|
|
15895
|
-
`Discarded a JSON response from ${redactUrl(
|
|
16375
|
+
`Discarded a JSON response from ${redactUrl(session2.connectUrl)} that is not JSON-RPC (${source}).`
|
|
15896
16376
|
);
|
|
15897
16377
|
return;
|
|
15898
16378
|
}
|
|
@@ -15923,7 +16403,7 @@ function startMcpProxy(opts) {
|
|
|
15923
16403
|
reauthAttempt = (async () => {
|
|
15924
16404
|
log2("The session key expired or was rotated \u2014 re-authenticating once.");
|
|
15925
16405
|
try {
|
|
15926
|
-
|
|
16406
|
+
session2 = await reauth();
|
|
15927
16407
|
sessionId = null;
|
|
15928
16408
|
return true;
|
|
15929
16409
|
} catch (err) {
|
|
@@ -15964,7 +16444,7 @@ function startMcpProxy(opts) {
|
|
|
15964
16444
|
};
|
|
15965
16445
|
const post = async (text4, isInitialize) => {
|
|
15966
16446
|
if (stopped) return null;
|
|
15967
|
-
return doFetch(
|
|
16447
|
+
return doFetch(session2.connectUrl, {
|
|
15968
16448
|
method: "POST",
|
|
15969
16449
|
headers: requestHeaders(isInitialize),
|
|
15970
16450
|
body: text4
|
|
@@ -15992,7 +16472,7 @@ function startMcpProxy(opts) {
|
|
|
15992
16472
|
res = await post(text4, isInitialize);
|
|
15993
16473
|
} catch (err) {
|
|
15994
16474
|
const message = err.message;
|
|
15995
|
-
log2(`Transport error talking to ${redactUrl(
|
|
16475
|
+
log2(`Transport error talking to ${redactUrl(session2.connectUrl)}: ${message}`);
|
|
15996
16476
|
emit(transportErrorFrame(frame?.id ?? null, `Transport error: ${message}`));
|
|
15997
16477
|
return { kind: "failed" };
|
|
15998
16478
|
}
|
|
@@ -16016,7 +16496,7 @@ function startMcpProxy(opts) {
|
|
|
16016
16496
|
const bodyText = await res.text();
|
|
16017
16497
|
if (!res.ok) {
|
|
16018
16498
|
const detail = res.status === 401 ? "the endpoint key is no longer valid (expired, rotated by another `dashwise mcp`, or revoked)" : `HTTP ${res.status}`;
|
|
16019
|
-
log2(`Refused by ${redactUrl(
|
|
16499
|
+
log2(`Refused by ${redactUrl(session2.connectUrl)}: ${detail}`);
|
|
16020
16500
|
emit(transportErrorFrame(frame?.id ?? null, `Dashwise MCP server: ${detail}`));
|
|
16021
16501
|
if (res.status === 401) {
|
|
16022
16502
|
exitCode = 1;
|
|
@@ -16103,7 +16583,7 @@ function startMcpProxy(opts) {
|
|
|
16103
16583
|
streamAbort = new AbortController();
|
|
16104
16584
|
let res;
|
|
16105
16585
|
try {
|
|
16106
|
-
res = await doFetch(
|
|
16586
|
+
res = await doFetch(session2.connectUrl, {
|
|
16107
16587
|
method: "GET",
|
|
16108
16588
|
headers: {
|
|
16109
16589
|
Accept: "text/event-stream",
|
|
@@ -16139,7 +16619,7 @@ function startMcpProxy(opts) {
|
|
|
16139
16619
|
}
|
|
16140
16620
|
streamUnavailable = true;
|
|
16141
16621
|
log2(
|
|
16142
|
-
`Server stream refused by ${redactUrl(
|
|
16622
|
+
`Server stream refused by ${redactUrl(session2.connectUrl)}: the session could not be renewed (the endpoint key expired, was rotated, or was revoked).`
|
|
16143
16623
|
);
|
|
16144
16624
|
emit(
|
|
16145
16625
|
transportErrorFrame(
|
|
@@ -16353,7 +16833,7 @@ async function revokeSession(endpointId) {
|
|
|
16353
16833
|
() => false
|
|
16354
16834
|
);
|
|
16355
16835
|
const timeout = new Promise(
|
|
16356
|
-
(
|
|
16836
|
+
(resolve16) => setTimeout(() => resolve16(false), REVOKE_TIMEOUT_MS).unref()
|
|
16357
16837
|
);
|
|
16358
16838
|
await Promise.race([attempt, timeout]);
|
|
16359
16839
|
}
|
|
@@ -16416,9 +16896,9 @@ async function mcpCommand(options = {}) {
|
|
|
16416
16896
|
return 1;
|
|
16417
16897
|
}
|
|
16418
16898
|
const label = options.endpointName ?? defaultEndpointLabel();
|
|
16419
|
-
let
|
|
16899
|
+
let session2;
|
|
16420
16900
|
try {
|
|
16421
|
-
|
|
16901
|
+
session2 = await openCliSession({
|
|
16422
16902
|
workspaceId: workspace2.id,
|
|
16423
16903
|
label,
|
|
16424
16904
|
...scopes !== void 0 ? { scopes } : {}
|
|
@@ -16427,24 +16907,24 @@ async function mcpCommand(options = {}) {
|
|
|
16427
16907
|
fail(describeSessionError(err, apiUrl, scopes));
|
|
16428
16908
|
return 1;
|
|
16429
16909
|
}
|
|
16430
|
-
if (scopes !== void 0 && !Array.isArray(
|
|
16431
|
-
await revokeSession(
|
|
16910
|
+
if (scopes !== void 0 && !Array.isArray(session2.scopes)) {
|
|
16911
|
+
await revokeSession(session2.endpointId);
|
|
16432
16912
|
fail(SCOPES_NOT_APPLIED);
|
|
16433
16913
|
return 1;
|
|
16434
16914
|
}
|
|
16435
16915
|
info(
|
|
16436
|
-
`Dashwise MCP \u2014 workspace ${workspace2.slug}, endpoint #${
|
|
16916
|
+
`Dashwise MCP \u2014 workspace ${workspace2.slug}, endpoint #${session2.endpointId} (${session2.name}).`
|
|
16437
16917
|
);
|
|
16438
|
-
info(`Session key expires ${dim(
|
|
16439
|
-
info(describeScopes(
|
|
16918
|
+
info(`Session key expires ${dim(session2.expiresAt)}.`);
|
|
16919
|
+
info(describeScopes(session2.scopes));
|
|
16440
16920
|
if (options.readOnly) {
|
|
16441
16921
|
warn(
|
|
16442
16922
|
"Read-only: write tools are refused by this proxy (a client-side guard, not a server-enforced scope \u2014 for that, use --scope)."
|
|
16443
16923
|
);
|
|
16444
16924
|
}
|
|
16445
|
-
let ownedEndpointId =
|
|
16925
|
+
let ownedEndpointId = session2.endpointId;
|
|
16446
16926
|
const proxy = startMcpProxy({
|
|
16447
|
-
session,
|
|
16927
|
+
session: session2,
|
|
16448
16928
|
reauth: async () => {
|
|
16449
16929
|
const fresh = await openCliSession({
|
|
16450
16930
|
workspaceId: workspace2.id,
|
|
@@ -16531,7 +17011,7 @@ async function mcpAddCommand(options = {}) {
|
|
|
16531
17011
|
fail(`Could not run \`claude\`: ${target.refused} Run the command above yourself.`);
|
|
16532
17012
|
return 1;
|
|
16533
17013
|
}
|
|
16534
|
-
return await new Promise((
|
|
17014
|
+
return await new Promise((resolve16) => {
|
|
16535
17015
|
const child = spawn(target.command, target.args, { shell: target.shell, stdio: "inherit" });
|
|
16536
17016
|
let settled = false;
|
|
16537
17017
|
child.on("error", (err) => {
|
|
@@ -16541,16 +17021,16 @@ async function mcpAddCommand(options = {}) {
|
|
|
16541
17021
|
fail(
|
|
16542
17022
|
`Claude Code is not on your PATH, so nothing was registered. Run the command above once ${code("claude")} is installed.`
|
|
16543
17023
|
);
|
|
16544
|
-
|
|
17024
|
+
resolve16(1);
|
|
16545
17025
|
return;
|
|
16546
17026
|
}
|
|
16547
17027
|
fail(`Could not run \`claude\`: ${err.message}`);
|
|
16548
|
-
|
|
17028
|
+
resolve16(1);
|
|
16549
17029
|
});
|
|
16550
17030
|
child.on("close", (codeOut) => {
|
|
16551
17031
|
if (settled) return;
|
|
16552
17032
|
settled = true;
|
|
16553
|
-
|
|
17033
|
+
resolve16(codeOut ?? 1);
|
|
16554
17034
|
});
|
|
16555
17035
|
});
|
|
16556
17036
|
}
|
|
@@ -16669,10 +17149,10 @@ function upgradeCommand(plugin, marketplace) {
|
|
|
16669
17149
|
var PLUGIN_NAME = "dashwise";
|
|
16670
17150
|
var MARKETPLACE_NAME = "dashwise";
|
|
16671
17151
|
var DEFAULT_MARKETPLACE_SOURCE = "Intuize/dashwise-cli";
|
|
16672
|
-
var defaultRunner = (cmd, args, opts) => new Promise((
|
|
17152
|
+
var defaultRunner = (cmd, args, opts) => new Promise((resolve16) => {
|
|
16673
17153
|
const target = spawnTarget(cmd, args);
|
|
16674
17154
|
if ("refused" in target) {
|
|
16675
|
-
|
|
17155
|
+
resolve16({ code: null, stdout: "", stderr: target.refused, missing: false });
|
|
16676
17156
|
return;
|
|
16677
17157
|
}
|
|
16678
17158
|
const child = spawn(target.command, target.args, {
|
|
@@ -16687,7 +17167,7 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolve15) => {
|
|
|
16687
17167
|
child.on("error", (err) => {
|
|
16688
17168
|
if (settled) return;
|
|
16689
17169
|
settled = true;
|
|
16690
|
-
|
|
17170
|
+
resolve16({
|
|
16691
17171
|
code: null,
|
|
16692
17172
|
stdout,
|
|
16693
17173
|
stderr: stderr || err.message,
|
|
@@ -16697,7 +17177,7 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolve15) => {
|
|
|
16697
17177
|
child.on("close", (exitCode) => {
|
|
16698
17178
|
if (settled) return;
|
|
16699
17179
|
settled = true;
|
|
16700
|
-
|
|
17180
|
+
resolve16({ code: exitCode, stdout, stderr, missing: false });
|
|
16701
17181
|
});
|
|
16702
17182
|
});
|
|
16703
17183
|
function readSkillsOnDisk() {
|
|
@@ -17938,6 +18418,7 @@ function judgeGeneratorStamp(stamp, opts = {}) {
|
|
|
17938
18418
|
// src/commands/module/generate.ts
|
|
17939
18419
|
init_sdk_version();
|
|
17940
18420
|
init_output();
|
|
18421
|
+
init_declared_secrets();
|
|
17941
18422
|
function outputDir(projectRoot) {
|
|
17942
18423
|
return join(projectRoot, "node_modules", "@dashai", "generated");
|
|
17943
18424
|
}
|
|
@@ -18014,6 +18495,9 @@ async function moduleGenerateCommand(options) {
|
|
|
18014
18495
|
info(`Hash: ${dim(res.contentHash)}`);
|
|
18015
18496
|
if (verdict.note !== null && declaresUses(manifest)) info(verdict.note);
|
|
18016
18497
|
warnIfSdkBelowUsesFloor(projectRoot, manifest);
|
|
18498
|
+
reportSecretLines(
|
|
18499
|
+
syncEnvLocalSecretLines(projectRoot, declaredSecretNames(manifest))
|
|
18500
|
+
);
|
|
18017
18501
|
return 0;
|
|
18018
18502
|
}
|
|
18019
18503
|
function declaresUses(manifest) {
|
|
@@ -18272,6 +18756,101 @@ init_init();
|
|
|
18272
18756
|
// src/commands/module/install.ts
|
|
18273
18757
|
init_api_client();
|
|
18274
18758
|
init_config();
|
|
18759
|
+
|
|
18760
|
+
// src/lib/agent-session.ts
|
|
18761
|
+
var MARKERS = [
|
|
18762
|
+
["CLAUDECODE", (v) => v === "1"],
|
|
18763
|
+
["CLAUDE_CODE_REMOTE", (v) => v === "true"],
|
|
18764
|
+
["CODEX_SANDBOX", (v) => v.length > 0],
|
|
18765
|
+
["CODEX_SANDBOX_NETWORK_DISABLED", (v) => v.length > 0],
|
|
18766
|
+
["CURSOR_AGENT", (v) => v.length > 0],
|
|
18767
|
+
["DW_AGENT", (v) => v.length > 0],
|
|
18768
|
+
["DASHWISE_AGENT_SESSION", (v) => v === "1" || v === "true"]
|
|
18769
|
+
];
|
|
18770
|
+
function agentSessionMarker(env = process.env) {
|
|
18771
|
+
for (const [name, test] of MARKERS) {
|
|
18772
|
+
const value = env[name];
|
|
18773
|
+
if (value !== void 0 && test(value)) return name;
|
|
18774
|
+
}
|
|
18775
|
+
return null;
|
|
18776
|
+
}
|
|
18777
|
+
|
|
18778
|
+
// src/lib/drift-preview.ts
|
|
18779
|
+
init_api_client();
|
|
18780
|
+
function redactedConsumer(redacted) {
|
|
18781
|
+
switch (redacted) {
|
|
18782
|
+
case "private_copy":
|
|
18783
|
+
return "someone's private copy";
|
|
18784
|
+
case "other_team_copy":
|
|
18785
|
+
return "another team's copy";
|
|
18786
|
+
case "development_copy":
|
|
18787
|
+
return "a development copy you cannot see";
|
|
18788
|
+
default:
|
|
18789
|
+
return "a copy you cannot see";
|
|
18790
|
+
}
|
|
18791
|
+
}
|
|
18792
|
+
var WIDEN_WORDS = {
|
|
18793
|
+
uses_need_added: "a new table it reads from another module",
|
|
18794
|
+
uses_ops_widened: "more operations on a table it reads",
|
|
18795
|
+
uses_fields_added: "more fields of a table it reads",
|
|
18796
|
+
dependency_added: "a new or wider dependency",
|
|
18797
|
+
exposes_widened: "more of what it exposes to other modules",
|
|
18798
|
+
permissions_added: "more permissions",
|
|
18799
|
+
roles_widened: "more roles or permission keys",
|
|
18800
|
+
outbound_added: "new outbound APIs (data can leave)",
|
|
18801
|
+
secrets_added: "new secrets",
|
|
18802
|
+
public_access_enabled: "public access"
|
|
18803
|
+
};
|
|
18804
|
+
function describeWidening(widening) {
|
|
18805
|
+
return widening.map((w) => WIDEN_WORDS[w] ?? w).join(", ");
|
|
18806
|
+
}
|
|
18807
|
+
async function getUpgradePreview(installationId, version, options = {}) {
|
|
18808
|
+
return apiRequest(
|
|
18809
|
+
"GET",
|
|
18810
|
+
`/api/installations/${installationId}/upgrade-preview?version=${encodeURIComponent(version)}`,
|
|
18811
|
+
void 0,
|
|
18812
|
+
options
|
|
18813
|
+
);
|
|
18814
|
+
}
|
|
18815
|
+
function count(n, unit) {
|
|
18816
|
+
if (n === null) return `${unit}s not counted`;
|
|
18817
|
+
return `${n.toLocaleString("en-US")} ${unit}${n === 1 ? "" : "s"}`;
|
|
18818
|
+
}
|
|
18819
|
+
function describeDrift(drift) {
|
|
18820
|
+
const lines = [];
|
|
18821
|
+
const d = drift.details;
|
|
18822
|
+
const removedTables = d?.tables_to_remove ?? drift.tables_to_remove.map((t) => ({ table: t, name: t, rows: null }));
|
|
18823
|
+
for (const t of removedTables) {
|
|
18824
|
+
lines.push({
|
|
18825
|
+
loses: t.rows !== 0,
|
|
18826
|
+
text: `Moves table ${t.table} (${count(t.rows, "row")}) to the Trash`
|
|
18827
|
+
});
|
|
18828
|
+
}
|
|
18829
|
+
const removedFields = d?.fields_to_remove ?? drift.fields_to_remove.map((f) => ({ ...f, name: f.field, values: null }));
|
|
18830
|
+
for (const f of removedFields) {
|
|
18831
|
+
lines.push({
|
|
18832
|
+
loses: f.values !== 0,
|
|
18833
|
+
text: `Moves field ${f.table}.${f.field} (${count(f.values, "value")}) to the Trash`
|
|
18834
|
+
});
|
|
18835
|
+
}
|
|
18836
|
+
const retyped = d?.fields_to_retype ?? (drift.fields_to_retype ?? []).map((f) => ({ ...f, name: f.field, values: null }));
|
|
18837
|
+
for (const f of retyped) {
|
|
18838
|
+
lines.push({
|
|
18839
|
+
loses: f.values !== 0,
|
|
18840
|
+
text: `Converts field ${f.table}.${f.field} from ${f.from} to ${f.to} (${count(f.values, "value")}; a value that does not convert is lost)`
|
|
18841
|
+
});
|
|
18842
|
+
}
|
|
18843
|
+
lines.sort((a, b) => Number(b.loses) - Number(a.loses));
|
|
18844
|
+
for (const t of drift.tables_to_create) {
|
|
18845
|
+
lines.push({ loses: false, text: `Adds table ${t}` });
|
|
18846
|
+
}
|
|
18847
|
+
for (const f of drift.fields_to_create) {
|
|
18848
|
+
lines.push({ loses: false, text: `Adds field ${f.table}.${f.field}` });
|
|
18849
|
+
}
|
|
18850
|
+
return lines;
|
|
18851
|
+
}
|
|
18852
|
+
|
|
18853
|
+
// src/commands/module/install.ts
|
|
18275
18854
|
init_manifest_io();
|
|
18276
18855
|
|
|
18277
18856
|
// src/lib/module-lifecycle.ts
|
|
@@ -18450,18 +19029,31 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
18450
19029
|
);
|
|
18451
19030
|
}
|
|
18452
19031
|
}
|
|
18453
|
-
|
|
18454
|
-
|
|
19032
|
+
let preview;
|
|
19033
|
+
let lossy = false;
|
|
19034
|
+
let lossAccepted = false;
|
|
19035
|
+
if (existing) {
|
|
19036
|
+
const rerun = rerunLine(mod.slug, workspace2.slug, targetVersion, options, devInstallToReplace !== void 0);
|
|
19037
|
+
const previewed = await previewUpgrade(existing.id, targetVersion, options, auth, rerun, devInstallToReplace?.id);
|
|
19038
|
+
if (typeof previewed === "number") return previewed;
|
|
19039
|
+
preview = previewed.preview;
|
|
19040
|
+
lossy = previewed.lossy;
|
|
19041
|
+
lossAccepted = previewed.accepted;
|
|
19042
|
+
}
|
|
19043
|
+
const askForLoss = lossy && !lossAccepted;
|
|
19044
|
+
if (askForLoss || !options.yes && process.stdout.isTTY && !confirmedViaPicker) {
|
|
19045
|
+
const lossClause = lossy ? " It moves the data listed above to the Trash or converts it." : "";
|
|
19046
|
+
const message = devInstallToReplace ? isUpgrade ? `Destroy the dev install (revokes local-dev credentials) and upgrade ${code(mod.slug)} to ${code(`v${targetVersion}`)} in ${code(workspace2.slug)}?${lossClause}` : `Replace the dev install (revokes local-dev credentials) and install ${code(mod.slug)} ${code(`v${targetVersion}`)} into ${code(workspace2.slug)} as production?` : `${isUpgrade ? "Upgrade" : "Install"} ${code(mod.slug)} ${code(`v${targetVersion}`)} into workspace ${code(workspace2.slug)}?${lossClause}`;
|
|
18455
19047
|
const answer = await select({
|
|
18456
19048
|
message,
|
|
18457
19049
|
options: [
|
|
18458
19050
|
{
|
|
18459
19051
|
value: "go",
|
|
18460
|
-
label: devInstallToReplace ? isUpgrade ? "Yes, destroy the dev install + upgrade" : "Yes, replace the dev install + production-install" : isUpgrade ? "Yes, upgrade now" : "Yes, install now"
|
|
19052
|
+
label: devInstallToReplace ? isUpgrade ? lossy ? "Yes, destroy the dev install + upgrade and lose the data listed above" : "Yes, destroy the dev install + upgrade" : "Yes, replace the dev install + production-install" : isUpgrade ? lossy ? "Yes, upgrade and lose the data listed above" : "Yes, upgrade now" : "Yes, install now"
|
|
18461
19053
|
},
|
|
18462
19054
|
{ value: "cancel", label: "No, abort" }
|
|
18463
19055
|
],
|
|
18464
|
-
initialValue: devInstallToReplace ? "cancel" : "go"
|
|
19056
|
+
initialValue: devInstallToReplace || lossy ? "cancel" : "go"
|
|
18465
19057
|
});
|
|
18466
19058
|
if (isCancel(answer) || answer === "cancel") {
|
|
18467
19059
|
fail("Aborted. Nothing was changed.");
|
|
@@ -18483,7 +19075,9 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
18483
19075
|
workspaceId: workspace2.id,
|
|
18484
19076
|
version: targetVersion,
|
|
18485
19077
|
...existing ? { installation_id: existing.id } : {},
|
|
18486
|
-
...options.force !== void 0 ? { force: options.force } : {}
|
|
19078
|
+
...options.force !== void 0 ? { force: options.force } : {},
|
|
19079
|
+
// P5-08: confirms exactly the delta that was shown (409 DRIFT_CHANGED otherwise).
|
|
19080
|
+
...preview ? { drift_hash: preview.drift_hash } : {}
|
|
18487
19081
|
},
|
|
18488
19082
|
auth
|
|
18489
19083
|
);
|
|
@@ -18535,6 +19129,9 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
18535
19129
|
was_upgrade: result.was_upgrade,
|
|
18536
19130
|
public_url: publicUrl,
|
|
18537
19131
|
verified,
|
|
19132
|
+
// P5-08: the confirmed preview; `null` for a fresh install (nothing to preview).
|
|
19133
|
+
drift_hash: preview?.drift_hash ?? null,
|
|
19134
|
+
data_loss: preview ? lossy : null,
|
|
18538
19135
|
// `null` when the binding surface was unreachable; `[]` when the
|
|
18539
19136
|
// module declares no slots. The two are not the same thing.
|
|
18540
19137
|
uses: readiness === null ? null : usesInstallJson(readiness),
|
|
@@ -18703,6 +19300,19 @@ function surfaceInstallError(err, slug, workspaceSlug, devInstallReplaced) {
|
|
|
18703
19300
|
Ask the module's author to invite this workspace (or open the install gate), then retry.`
|
|
18704
19301
|
);
|
|
18705
19302
|
break;
|
|
19303
|
+
case "DRIFT_CHANGED":
|
|
19304
|
+
fail(
|
|
19305
|
+
`Install ${code(slug)} changed since its upgrade preview, so the confirmed change no longer matches. Nothing was changed.
|
|
19306
|
+
Re-run ${code("dashwise module install")} to see the new preview.`
|
|
19307
|
+
);
|
|
19308
|
+
break;
|
|
19309
|
+
case "DATA_LOSS_CONFIRM_REQUIRED":
|
|
19310
|
+
case "UPGRADE_CONFIRM_REQUIRED":
|
|
19311
|
+
fail(
|
|
19312
|
+
`The platform asks for a confirmed preview before this upgrade (${err.code}). Nothing was changed.
|
|
19313
|
+
Re-run ${code("dashwise module install")}: it previews the upgrade and sends the confirmation.`
|
|
19314
|
+
);
|
|
19315
|
+
break;
|
|
18706
19316
|
case "UPGRADE_BREAKS_CONSUMERS":
|
|
18707
19317
|
fail(
|
|
18708
19318
|
`Upgrading ${code(slug)} would break modules that consume its current schema.
|
|
@@ -18752,6 +19362,75 @@ function usesInstallJson(readiness) {
|
|
|
18752
19362
|
}))
|
|
18753
19363
|
};
|
|
18754
19364
|
}
|
|
19365
|
+
async function previewUpgrade(installationId, targetVersion, options, auth, rerun, devInstallToReplaceId) {
|
|
19366
|
+
let preview;
|
|
19367
|
+
try {
|
|
19368
|
+
preview = await getUpgradePreview(installationId, targetVersion, auth);
|
|
19369
|
+
} catch (err) {
|
|
19370
|
+
const why = err instanceof ApiError ? `${err.code || err.status} ${err.message}` : err.message;
|
|
19371
|
+
const checkNote = options.acceptDataLoss !== void 0 ? `, so ${code("--accept-data-loss")} cannot be checked` : "";
|
|
19372
|
+
fail(
|
|
19373
|
+
`Could not preview what upgrading install #${installationId} to ${code(`v${targetVersion}`)} does to its data (${why})${checkNote}. Nothing was changed.`
|
|
19374
|
+
);
|
|
19375
|
+
info(`An upgrade is never applied without its preview; retry when the platform answers.`);
|
|
19376
|
+
return 1;
|
|
19377
|
+
}
|
|
19378
|
+
const lines = describeDrift(preview.drift);
|
|
19379
|
+
const lossy = lines.some((l) => l.loses);
|
|
19380
|
+
const from = preview.from_version ? `v${preview.from_version}` : "its current version";
|
|
19381
|
+
if (lines.length === 0) {
|
|
19382
|
+
info(`Upgrading install #${installationId} from ${from} to v${preview.to_version} changes no table or field.`);
|
|
19383
|
+
} else {
|
|
19384
|
+
info(`What upgrading install #${installationId} from ${from} to v${preview.to_version} does to its data:`);
|
|
19385
|
+
for (const line of lines) {
|
|
19386
|
+
(line.loses ? warn : info)(` ${line.text}`);
|
|
19387
|
+
}
|
|
19388
|
+
}
|
|
19389
|
+
const broken = preview.breaks_consumers ?? [];
|
|
19390
|
+
if (broken.length > 0) {
|
|
19391
|
+
const names = broken.map(
|
|
19392
|
+
(c2) => c2.module_slug ? `${c2.module_slug}${c2.installation_id ? ` #${c2.installation_id}` : ""}` : redactedConsumer(c2.redacted)
|
|
19393
|
+
);
|
|
19394
|
+
warn(` Breaks ${broken.length} module${broken.length === 1 ? "" : "s"} that read${broken.length === 1 ? "s" : ""} this one: ${names.join(", ")}`);
|
|
19395
|
+
}
|
|
19396
|
+
if (preview.approval_required) {
|
|
19397
|
+
const asks = preview.widening && preview.widening.length > 0 ? `: it asks for ${describeWidening(preview.widening)}` : "";
|
|
19398
|
+
warn(` This update waits for the module author's approval${asks}.`);
|
|
19399
|
+
}
|
|
19400
|
+
if (options.acceptDataLoss !== void 0 && options.acceptDataLoss !== preview.drift_hash) {
|
|
19401
|
+
fail(
|
|
19402
|
+
`${code("--accept-data-loss")} does not match this upgrade's preview: the copy or the release changed since that hash was printed. Nothing was changed.`
|
|
19403
|
+
);
|
|
19404
|
+
info(`To accept the change listed above, a person re-runs:
|
|
19405
|
+
${rerun(preview.drift_hash)}`);
|
|
19406
|
+
return 1;
|
|
19407
|
+
}
|
|
19408
|
+
const accepted = lossy && options.acceptDataLoss === preview.drift_hash;
|
|
19409
|
+
if (lossy && !accepted) {
|
|
19410
|
+
const yesNote = options.yes ? ` ${code("--yes")} does not accept data loss.` : "";
|
|
19411
|
+
const marker = agentSessionMarker();
|
|
19412
|
+
const why = marker ? `this is an agent session (${code(marker)} is set): a person decides` : !process.stdout.isTTY ? "there is no terminal to ask in" : null;
|
|
19413
|
+
if (why) {
|
|
19414
|
+
const alsoDev = devInstallToReplaceId !== void 0 ? ` and destroys dev install #${devInstallToReplaceId} first` : "";
|
|
19415
|
+
fail(`This upgrade loses the data listed above${alsoDev}, and ${why}.${yesNote} Refused. Nothing was changed.`);
|
|
19416
|
+
info(`To accept exactly that loss, a person re-runs:
|
|
19417
|
+
${rerun(preview.drift_hash)}`);
|
|
19418
|
+
return 1;
|
|
19419
|
+
}
|
|
19420
|
+
}
|
|
19421
|
+
return { preview, lossy, accepted };
|
|
19422
|
+
}
|
|
19423
|
+
function rerunLine(slug, workspaceSlug, targetVersion, options, replacingDevInstall) {
|
|
19424
|
+
return (hash) => [
|
|
19425
|
+
"dashwise module install",
|
|
19426
|
+
slug,
|
|
19427
|
+
`--workspace ${workspaceSlug}`,
|
|
19428
|
+
`--version ${targetVersion}`,
|
|
19429
|
+
...options.force ? ["--force"] : [],
|
|
19430
|
+
...replacingDevInstall ? ["--replace-dev-install"] : [],
|
|
19431
|
+
`--accept-data-loss ${hash}`
|
|
19432
|
+
].join(" ");
|
|
19433
|
+
}
|
|
18755
19434
|
|
|
18756
19435
|
// src/commands/module/list.ts
|
|
18757
19436
|
init_api_client();
|
|
@@ -19121,6 +19800,7 @@ function formatBytes(n) {
|
|
|
19121
19800
|
}
|
|
19122
19801
|
|
|
19123
19802
|
// src/commands/module/publish.ts
|
|
19803
|
+
init_diff_check();
|
|
19124
19804
|
var VALID_BUMP = /* @__PURE__ */ new Set(["patch", "minor", "major"]);
|
|
19125
19805
|
var PACKED_FILE_LIST_CAP = 20;
|
|
19126
19806
|
function packedFileLines(files, cap = PACKED_FILE_LIST_CAP) {
|
|
@@ -19207,6 +19887,10 @@ async function modulePublishCommand(options) {
|
|
|
19207
19887
|
info(
|
|
19208
19888
|
`Module: ${code(mod.slug)} (id ${mod.id})${mod.current_version ? `, current ${code(`v${mod.current_version}`)}` : dim(", no published version yet")}`
|
|
19209
19889
|
);
|
|
19890
|
+
if (mod.current_version) {
|
|
19891
|
+
const gate = await publishEvolutionGate(projectRoot, options.strict === true);
|
|
19892
|
+
if (gate !== 0) return gate;
|
|
19893
|
+
}
|
|
19210
19894
|
let tarball;
|
|
19211
19895
|
try {
|
|
19212
19896
|
tarball = await packTarball({
|
|
@@ -19563,6 +20247,22 @@ function refusalHint(code2, context, subject = {}) {
|
|
|
19563
20247
|
message: `Too many builds of ${named} in the last hour. Try again later.`,
|
|
19564
20248
|
needsGrant: false
|
|
19565
20249
|
};
|
|
20250
|
+
// Install invitations (`dashwise module invites`, Südbau retro 6).
|
|
20251
|
+
case "AUTHOR_ADMIN_REQUIRED":
|
|
20252
|
+
return {
|
|
20253
|
+
message: `Only an admin of the company that owns ${named} can manage its install invitations. Ask that company's admin.`,
|
|
20254
|
+
needsGrant: true
|
|
20255
|
+
};
|
|
20256
|
+
case "COMPANY_ADMIN_REQUIRED":
|
|
20257
|
+
return {
|
|
20258
|
+
message: "Only an admin of this company can see or decline its install invitations. Ask your company's admin.",
|
|
20259
|
+
needsGrant: true
|
|
20260
|
+
};
|
|
20261
|
+
case "MODULE_PRIVATE_SHARE_FIRST":
|
|
20262
|
+
return {
|
|
20263
|
+
message: `${named} is still private. Share it with your company in Dashwise first, then invite another company.`,
|
|
20264
|
+
needsGrant: false
|
|
20265
|
+
};
|
|
19566
20266
|
default:
|
|
19567
20267
|
if (MODULE_RIGHT_CODES.has(code2)) {
|
|
19568
20268
|
return {
|
|
@@ -19798,7 +20498,7 @@ async function moduleReleaseCommand(slugArg, options) {
|
|
|
19798
20498
|
}
|
|
19799
20499
|
async function wait() {
|
|
19800
20500
|
const remaining = Math.max(0, deadline - Date.now());
|
|
19801
|
-
await new Promise((
|
|
20501
|
+
await new Promise((resolve16) => setTimeout(resolve16, Math.min(options.pollIntervalMs ?? 4e3, remaining)));
|
|
19802
20502
|
}
|
|
19803
20503
|
}
|
|
19804
20504
|
|
|
@@ -19972,6 +20672,133 @@ function usesJson(readiness) {
|
|
|
19972
20672
|
};
|
|
19973
20673
|
}
|
|
19974
20674
|
|
|
20675
|
+
// src/commands/module/installations.ts
|
|
20676
|
+
init_api_client();
|
|
20677
|
+
init_config();
|
|
20678
|
+
init_output();
|
|
20679
|
+
var AUDIENCE = {
|
|
20680
|
+
company: "company",
|
|
20681
|
+
teams: "Team Spaces",
|
|
20682
|
+
people: "named people"
|
|
20683
|
+
};
|
|
20684
|
+
var givesRole = (roleKey) => typeof roleKey === "string" && roleKey !== "none";
|
|
20685
|
+
function governanceAudience(row) {
|
|
20686
|
+
if (!row.rules) return null;
|
|
20687
|
+
if (givesRole(row.rules.company)) return AUDIENCE.company;
|
|
20688
|
+
if (row.rules.teams.some((t) => t.live === true && givesRole(t.role_key))) return AUDIENCE.teams;
|
|
20689
|
+
return AUDIENCE.people;
|
|
20690
|
+
}
|
|
20691
|
+
function toListEntry(row) {
|
|
20692
|
+
return {
|
|
20693
|
+
id: row.id,
|
|
20694
|
+
module_slug: row.module?.slug ?? null,
|
|
20695
|
+
name: row.name ?? row.module?.name ?? null,
|
|
20696
|
+
audience: row.reach?.kind ? AUDIENCE[row.reach.kind] ?? row.reach.kind : null,
|
|
20697
|
+
version: row.module_version?.version ?? null,
|
|
20698
|
+
held: row.held === true,
|
|
20699
|
+
development: isDevInstallKind(row.kind),
|
|
20700
|
+
public_url: row.public_url,
|
|
20701
|
+
admin_view_only: false
|
|
20702
|
+
};
|
|
20703
|
+
}
|
|
20704
|
+
async function moduleInstallationsCommand(slug, options) {
|
|
20705
|
+
if (!isLoggedIn()) {
|
|
20706
|
+
fail(`Not logged in. Run ${code("dashwise login")} first.`);
|
|
20707
|
+
return 1;
|
|
20708
|
+
}
|
|
20709
|
+
const cfg = readConfig();
|
|
20710
|
+
if (!cfg) {
|
|
20711
|
+
fail(`CLI config missing. Run ${code("dashwise login")}.`);
|
|
20712
|
+
return 1;
|
|
20713
|
+
}
|
|
20714
|
+
const auth = { baseUrl: resolveApiUrl(), auth: { token: cfg.token } };
|
|
20715
|
+
let workspace2;
|
|
20716
|
+
try {
|
|
20717
|
+
workspace2 = await resolveWorkspaceWithFallback({
|
|
20718
|
+
userFlag: options.workspace,
|
|
20719
|
+
cachedWorkspaceId: slug ? readModuleEntry(slug)?.workspaceId : void 0,
|
|
20720
|
+
configDefault: cfg.workspaceSlug,
|
|
20721
|
+
apiUrl: resolveApiUrl(),
|
|
20722
|
+
apiToken: cfg.token
|
|
20723
|
+
});
|
|
20724
|
+
} catch (err) {
|
|
20725
|
+
fail(`Company selection failed: ${err.message}`);
|
|
20726
|
+
return 1;
|
|
20727
|
+
}
|
|
20728
|
+
let rows;
|
|
20729
|
+
try {
|
|
20730
|
+
rows = await listWorkspaceInstallations(workspace2.id, auth);
|
|
20731
|
+
} catch (err) {
|
|
20732
|
+
fail(`Could not read the installations in ${code(workspace2.slug)}: ${err.message}`);
|
|
20733
|
+
return 1;
|
|
20734
|
+
}
|
|
20735
|
+
const governance = [];
|
|
20736
|
+
try {
|
|
20737
|
+
const govRows = await apiRequest(
|
|
20738
|
+
"GET",
|
|
20739
|
+
`/api/modules/workspaces/${workspace2.id}/installations/governance`,
|
|
20740
|
+
void 0,
|
|
20741
|
+
auth
|
|
20742
|
+
);
|
|
20743
|
+
const seen = new Set(rows.map((row) => row.id));
|
|
20744
|
+
const slugOf = new Map(rows.map((row) => [row.module_id, row.module?.slug ?? null]));
|
|
20745
|
+
let slugModuleId;
|
|
20746
|
+
if (slug) {
|
|
20747
|
+
slugModuleId = rows.find((row) => row.module?.slug === slug)?.module_id;
|
|
20748
|
+
if (slugModuleId === void 0) {
|
|
20749
|
+
try {
|
|
20750
|
+
slugModuleId = (await getModuleBySlug(slug, auth)).id;
|
|
20751
|
+
} catch {
|
|
20752
|
+
}
|
|
20753
|
+
}
|
|
20754
|
+
}
|
|
20755
|
+
for (const g of govRows) {
|
|
20756
|
+
if (g.trashed || seen.has(g.installation_id)) continue;
|
|
20757
|
+
if (slug && g.module_id !== slugModuleId) continue;
|
|
20758
|
+
governance.push({
|
|
20759
|
+
id: g.installation_id,
|
|
20760
|
+
module_slug: slugOf.get(g.module_id) ?? (slug && g.module_id === slugModuleId ? slug : null),
|
|
20761
|
+
name: g.name || null,
|
|
20762
|
+
audience: governanceAudience(g),
|
|
20763
|
+
version: null,
|
|
20764
|
+
held: g.held === true,
|
|
20765
|
+
development: null,
|
|
20766
|
+
public_url: null,
|
|
20767
|
+
admin_view_only: true
|
|
20768
|
+
});
|
|
20769
|
+
}
|
|
20770
|
+
} catch (err) {
|
|
20771
|
+
if (!(err instanceof ApiError && (err.status === 403 || err.status === 404))) {
|
|
20772
|
+
fail(`Could not read the company's full installation list: ${err.message}`);
|
|
20773
|
+
return 1;
|
|
20774
|
+
}
|
|
20775
|
+
}
|
|
20776
|
+
const entries = rows.filter((row) => !slug || row.module?.slug === slug).map(toListEntry).concat(governance).filter((entry) => options.dev || !entry.development).sort((a, b) => (a.module_slug ?? "").localeCompare(b.module_slug ?? "") || a.id - b.id);
|
|
20777
|
+
if (options.json) {
|
|
20778
|
+
out(JSON.stringify({ workspace: workspace2.slug, installations: entries }));
|
|
20779
|
+
return 0;
|
|
20780
|
+
}
|
|
20781
|
+
if (entries.length === 0) {
|
|
20782
|
+
info(
|
|
20783
|
+
slug ? `No installations of ${code(slug)} you can see in ${code(workspace2.slug)}.` : `No installations you can see in ${code(workspace2.slug)}.`
|
|
20784
|
+
);
|
|
20785
|
+
return 0;
|
|
20786
|
+
}
|
|
20787
|
+
for (const e of entries) {
|
|
20788
|
+
const tags = [
|
|
20789
|
+
e.audience,
|
|
20790
|
+
e.version ? `v${e.version}` : null,
|
|
20791
|
+
e.held ? "held" : null,
|
|
20792
|
+
e.development ? "development" : null,
|
|
20793
|
+
e.admin_view_only ? "admin view, not open to you" : null
|
|
20794
|
+
].filter(Boolean);
|
|
20795
|
+
out(
|
|
20796
|
+
`#${e.id} ${e.module_slug ?? "?"} ${e.name ? `"${e.name}"` : ""} ${dim(tags.join(", "))}${e.public_url ? ` ${e.public_url}` : ""}`.replace(/ {3,}/g, " ")
|
|
20797
|
+
);
|
|
20798
|
+
}
|
|
20799
|
+
return 0;
|
|
20800
|
+
}
|
|
20801
|
+
|
|
19975
20802
|
// src/commands/module/uninstall.ts
|
|
19976
20803
|
init_api_client();
|
|
19977
20804
|
init_config();
|
|
@@ -20142,6 +20969,269 @@ function surfaceUninstallError(err, slug, workspaceSlug) {
|
|
|
20142
20969
|
return 1;
|
|
20143
20970
|
}
|
|
20144
20971
|
|
|
20972
|
+
// src/commands/module/invites.ts
|
|
20973
|
+
init_api_client();
|
|
20974
|
+
init_config();
|
|
20975
|
+
init_output();
|
|
20976
|
+
var positiveId2 = (value) => typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
20977
|
+
function session(json) {
|
|
20978
|
+
const cfg = readConfig();
|
|
20979
|
+
if (!isLoggedIn() || !cfg) return reportCliError(`Not logged in. Run ${code("dashwise login")} first.`, { json });
|
|
20980
|
+
return {
|
|
20981
|
+
auth: { baseUrl: resolveApiUrl(), auth: { token: cfg.token } },
|
|
20982
|
+
token: cfg.token,
|
|
20983
|
+
workspaceSlug: cfg.workspaceSlug
|
|
20984
|
+
};
|
|
20985
|
+
}
|
|
20986
|
+
async function resolveModule(slug, s, json) {
|
|
20987
|
+
try {
|
|
20988
|
+
const mod = await getModuleBySlug(slug, s.auth);
|
|
20989
|
+
return { id: mod.id, slug: mod.slug };
|
|
20990
|
+
} catch (err) {
|
|
20991
|
+
return reportRefusal(err, { module: slug }, { json, action: "Resolving the module" });
|
|
20992
|
+
}
|
|
20993
|
+
}
|
|
20994
|
+
async function confirm(question, confirmLabel, opts) {
|
|
20995
|
+
if (opts.yes) return null;
|
|
20996
|
+
if (opts.json || !process.stdin.isTTY || !process.stdout.isTTY) {
|
|
20997
|
+
return reportCliError("This changes an invitation. Pass --yes to confirm in non-interactive or --json mode.", {
|
|
20998
|
+
json: opts.json,
|
|
20999
|
+
code: "USAGE"
|
|
21000
|
+
});
|
|
21001
|
+
}
|
|
21002
|
+
const answer = await select({
|
|
21003
|
+
message: question,
|
|
21004
|
+
options: [
|
|
21005
|
+
{ value: "cancel", label: "Cancel" },
|
|
21006
|
+
{ value: "go", label: confirmLabel }
|
|
21007
|
+
],
|
|
21008
|
+
initialValue: "cancel"
|
|
21009
|
+
});
|
|
21010
|
+
if (isCancel(answer) || answer !== "go") {
|
|
21011
|
+
reportCliError("Aborted.", {});
|
|
21012
|
+
return 130;
|
|
21013
|
+
}
|
|
21014
|
+
return null;
|
|
21015
|
+
}
|
|
21016
|
+
function companyLabel(row) {
|
|
21017
|
+
return row.company_name ? `${row.company_name} (company #${row.workspace_id})` : `company #${row.workspace_id}`;
|
|
21018
|
+
}
|
|
21019
|
+
async function moduleInvitesListCommand(slug, options) {
|
|
21020
|
+
const { json } = options;
|
|
21021
|
+
const s = session(json);
|
|
21022
|
+
if (typeof s === "number") return s;
|
|
21023
|
+
const mod = await resolveModule(slug, s, json);
|
|
21024
|
+
if (typeof mod === "number") return mod;
|
|
21025
|
+
let rows;
|
|
21026
|
+
try {
|
|
21027
|
+
rows = await apiRequest("GET", `/api/modules/${mod.id}/invites`, void 0, s.auth);
|
|
21028
|
+
} catch (err) {
|
|
21029
|
+
return reportRefusal(err, { module: mod.slug }, { json, action: "Reading the invitations" });
|
|
21030
|
+
}
|
|
21031
|
+
if (json) {
|
|
21032
|
+
out(JSON.stringify({ module: mod.slug, invites: rows }));
|
|
21033
|
+
return 0;
|
|
21034
|
+
}
|
|
21035
|
+
if (rows.length === 0) {
|
|
21036
|
+
info(`No open invitations for ${code(mod.slug)}.`);
|
|
21037
|
+
return 0;
|
|
21038
|
+
}
|
|
21039
|
+
for (const row of rows) {
|
|
21040
|
+
const note = row.note ? ` ${dim(`"${row.note}"`)}` : "";
|
|
21041
|
+
out(`#${row.id} ${companyLabel(row)} ${dim(row.invited_at.slice(0, 10))}${note}`);
|
|
21042
|
+
}
|
|
21043
|
+
return 0;
|
|
21044
|
+
}
|
|
21045
|
+
async function moduleInvitesSendCommand(slug, options) {
|
|
21046
|
+
const { json } = options;
|
|
21047
|
+
const usage2 = (message) => reportCliError(message, { json, code: "USAGE" });
|
|
21048
|
+
if (!options.to) return usage2("Pass --to <company id> to name the company to invite.");
|
|
21049
|
+
const s = session(json);
|
|
21050
|
+
if (typeof s === "number") return s;
|
|
21051
|
+
let target;
|
|
21052
|
+
const numeric = /^[1-9][0-9]*$/.test(options.to) ? Number(options.to) : void 0;
|
|
21053
|
+
let mine;
|
|
21054
|
+
try {
|
|
21055
|
+
mine = await apiRequest("GET", "/api/workspaces", void 0, s.auth);
|
|
21056
|
+
} catch (err) {
|
|
21057
|
+
return reportRefusal(err, {}, { json, action: "Reading your companies" });
|
|
21058
|
+
}
|
|
21059
|
+
const own = mine.find((w) => numeric !== void 0 ? w.id === numeric : w.slug === options.to);
|
|
21060
|
+
if (own) {
|
|
21061
|
+
target = { id: own.id, label: `${own.name} (company #${own.id})` };
|
|
21062
|
+
} else if (positiveId2(numeric)) {
|
|
21063
|
+
target = { id: numeric, label: `company #${numeric}` };
|
|
21064
|
+
} else {
|
|
21065
|
+
return usage2(
|
|
21066
|
+
`${options.to} is not one of your companies. For another company, pass its company id (an admin of that company finds it in its settings).`
|
|
21067
|
+
);
|
|
21068
|
+
}
|
|
21069
|
+
const mod = await resolveModule(slug, s, json);
|
|
21070
|
+
if (typeof mod === "number") return mod;
|
|
21071
|
+
const stop = await confirm(
|
|
21072
|
+
[
|
|
21073
|
+
`Invite ${target.label} to install ${mod.slug}?`,
|
|
21074
|
+
`- Admins of that company can then see ${mod.slug} and install it.`,
|
|
21075
|
+
"- Their data stays theirs; you do not see it.",
|
|
21076
|
+
`- Your module's logic and its updates reach their installations.`,
|
|
21077
|
+
"- You see their company name only after one of their admins installs it or asks to.",
|
|
21078
|
+
`- You can withdraw the invitation at any time with ${code(`dashwise module invites revoke ${mod.slug} <invitation id>`)}.`
|
|
21079
|
+
].join("\n"),
|
|
21080
|
+
"Send the invitation",
|
|
21081
|
+
options
|
|
21082
|
+
);
|
|
21083
|
+
if (stop !== null) return stop;
|
|
21084
|
+
let invite;
|
|
21085
|
+
try {
|
|
21086
|
+
const body = await apiRequest(
|
|
21087
|
+
"POST",
|
|
21088
|
+
`/api/modules/${mod.id}/invites`,
|
|
21089
|
+
{ workspaceId: target.id, ...options.note !== void 0 ? { note: options.note } : {} },
|
|
21090
|
+
s.auth
|
|
21091
|
+
);
|
|
21092
|
+
invite = body.invite;
|
|
21093
|
+
} catch (err) {
|
|
21094
|
+
return reportRefusal(err, { module: mod.slug }, { json, action: "Sending the invitation" });
|
|
21095
|
+
}
|
|
21096
|
+
if (json) out(JSON.stringify({ module: mod.slug, invite }));
|
|
21097
|
+
else success(`Invited ${target.label} to install ${mod.slug} (invitation #${invite.id}).`);
|
|
21098
|
+
return 0;
|
|
21099
|
+
}
|
|
21100
|
+
async function moduleInvitesRevokeCommand(slug, inviteId, options) {
|
|
21101
|
+
const { json } = options;
|
|
21102
|
+
if (!positiveId2(inviteId)) {
|
|
21103
|
+
return reportCliError(`Pass the invitation id. List them with ${code(`dashwise module invites list ${slug}`)}.`, {
|
|
21104
|
+
json,
|
|
21105
|
+
code: "USAGE"
|
|
21106
|
+
});
|
|
21107
|
+
}
|
|
21108
|
+
const s = session(json);
|
|
21109
|
+
if (typeof s === "number") return s;
|
|
21110
|
+
const mod = await resolveModule(slug, s, json);
|
|
21111
|
+
if (typeof mod === "number") return mod;
|
|
21112
|
+
const stop = await confirm(
|
|
21113
|
+
[
|
|
21114
|
+
`Withdraw invitation #${inviteId} for ${mod.slug}?`,
|
|
21115
|
+
`- Installations made with it stay, but they no longer receive updates of ${mod.slug}.`,
|
|
21116
|
+
"- No new installation can start from it."
|
|
21117
|
+
].join("\n"),
|
|
21118
|
+
"Withdraw the invitation",
|
|
21119
|
+
options
|
|
21120
|
+
);
|
|
21121
|
+
if (stop !== null) return stop;
|
|
21122
|
+
try {
|
|
21123
|
+
await apiRequest("DELETE", `/api/modules/${mod.id}/invites/${inviteId}`, void 0, s.auth);
|
|
21124
|
+
} catch (err) {
|
|
21125
|
+
return reportRefusal(err, { module: mod.slug }, { json, action: "Withdrawing the invitation" });
|
|
21126
|
+
}
|
|
21127
|
+
if (json) out(JSON.stringify({ module: mod.slug, revoked: inviteId }));
|
|
21128
|
+
else success(`Withdrew invitation #${inviteId} for ${mod.slug}.`);
|
|
21129
|
+
return 0;
|
|
21130
|
+
}
|
|
21131
|
+
async function resolveOwnCompany(s, flag, opts) {
|
|
21132
|
+
const { json } = opts;
|
|
21133
|
+
let mine;
|
|
21134
|
+
try {
|
|
21135
|
+
mine = await apiRequest("GET", "/api/workspaces", void 0, s.auth);
|
|
21136
|
+
} catch (err) {
|
|
21137
|
+
return reportRefusal(err, {}, { json, action: "Reading your companies" });
|
|
21138
|
+
}
|
|
21139
|
+
const list = mine.map((w) => `${w.slug} (company #${w.id})`).join(", ");
|
|
21140
|
+
const wanted = flag ?? s.workspaceSlug;
|
|
21141
|
+
if (wanted !== void 0) {
|
|
21142
|
+
const found = mine.find((w) => w.slug === wanted || String(w.id) === wanted);
|
|
21143
|
+
if (found) return found;
|
|
21144
|
+
return reportCliError(`${wanted} is not one of your companies. Your companies: ${list}.`, { json, code: "USAGE" });
|
|
21145
|
+
}
|
|
21146
|
+
if (mine.length === 1) return mine[0];
|
|
21147
|
+
if (mine.length === 0) return reportCliError("Your account belongs to no company yet.", { json });
|
|
21148
|
+
if (!opts.interactive || json || !process.stdin.isTTY || !process.stdout.isTTY) {
|
|
21149
|
+
return reportCliError(`Choose the company with --workspace <slug or id>. Your companies: ${list}.`, {
|
|
21150
|
+
json,
|
|
21151
|
+
code: "USAGE"
|
|
21152
|
+
});
|
|
21153
|
+
}
|
|
21154
|
+
const choice = await select({
|
|
21155
|
+
message: "Which company?",
|
|
21156
|
+
options: mine.map((w) => ({ value: String(w.id), label: w.name, hint: w.slug }))
|
|
21157
|
+
});
|
|
21158
|
+
const picked = isCancel(choice) ? void 0 : mine.find((w) => String(w.id) === choice);
|
|
21159
|
+
if (!picked) {
|
|
21160
|
+
reportCliError("Aborted.", {});
|
|
21161
|
+
return 130;
|
|
21162
|
+
}
|
|
21163
|
+
return picked;
|
|
21164
|
+
}
|
|
21165
|
+
async function moduleInvitesReceivedCommand(options) {
|
|
21166
|
+
const { json } = options;
|
|
21167
|
+
const s = session(json);
|
|
21168
|
+
if (typeof s === "number") return s;
|
|
21169
|
+
const company = await resolveOwnCompany(s, options.workspace, { json, interactive: true });
|
|
21170
|
+
if (typeof company === "number") return company;
|
|
21171
|
+
let rows;
|
|
21172
|
+
try {
|
|
21173
|
+
rows = await apiRequest(
|
|
21174
|
+
"GET",
|
|
21175
|
+
`/api/modules/workspaces/${company.id}/invites`,
|
|
21176
|
+
void 0,
|
|
21177
|
+
s.auth
|
|
21178
|
+
);
|
|
21179
|
+
} catch (err) {
|
|
21180
|
+
return reportRefusal(err, {}, { json, action: "Reading the invitations" });
|
|
21181
|
+
}
|
|
21182
|
+
if (json) {
|
|
21183
|
+
out(JSON.stringify({ workspace: company.slug, invites: rows }));
|
|
21184
|
+
return 0;
|
|
21185
|
+
}
|
|
21186
|
+
if (rows.length === 0) {
|
|
21187
|
+
info(`No open invitations for ${code(company.slug)}.`);
|
|
21188
|
+
return 0;
|
|
21189
|
+
}
|
|
21190
|
+
for (const row of rows) {
|
|
21191
|
+
const note = row.note ? ` ${dim(`"${row.note}"`)}` : "";
|
|
21192
|
+
out(
|
|
21193
|
+
`#${row.id} ${row.module.slug} ${dim(`v${row.module.current_version}`)} from ${row.from_company.name} ${dim(row.invited_at.slice(0, 10))}${note}`
|
|
21194
|
+
);
|
|
21195
|
+
}
|
|
21196
|
+
const inCompany = `--workspace ${company.slug}`;
|
|
21197
|
+
info(
|
|
21198
|
+
`Install one with ${code(`dashwise module install <slug> ${inCompany}`)}, or decline it with ${code(`dashwise module invites decline <id> ${inCompany}`)}.`
|
|
21199
|
+
);
|
|
21200
|
+
return 0;
|
|
21201
|
+
}
|
|
21202
|
+
async function moduleInvitesDeclineCommand(inviteId, options) {
|
|
21203
|
+
const { json } = options;
|
|
21204
|
+
if (!positiveId2(inviteId)) {
|
|
21205
|
+
return reportCliError(`Pass the invitation id. List them with ${code("dashwise module invites received")}.`, {
|
|
21206
|
+
json,
|
|
21207
|
+
code: "USAGE"
|
|
21208
|
+
});
|
|
21209
|
+
}
|
|
21210
|
+
const s = session(json);
|
|
21211
|
+
if (typeof s === "number") return s;
|
|
21212
|
+
const company = await resolveOwnCompany(s, options.workspace, { json, interactive: !options.yes });
|
|
21213
|
+
if (typeof company === "number") return company;
|
|
21214
|
+
const stop = await confirm(
|
|
21215
|
+
`Decline invitation #${inviteId} for ${company.slug}? The module's company may invite you again.`,
|
|
21216
|
+
"Decline the invitation",
|
|
21217
|
+
options
|
|
21218
|
+
);
|
|
21219
|
+
if (stop !== null) return stop;
|
|
21220
|
+
try {
|
|
21221
|
+
await apiRequest(
|
|
21222
|
+
"POST",
|
|
21223
|
+
`/api/modules/workspaces/${company.id}/invites/${inviteId}/decline`,
|
|
21224
|
+
void 0,
|
|
21225
|
+
s.auth
|
|
21226
|
+
);
|
|
21227
|
+
} catch (err) {
|
|
21228
|
+
return reportRefusal(err, {}, { json, action: "Declining the invitation" });
|
|
21229
|
+
}
|
|
21230
|
+
if (json) out(JSON.stringify({ workspace: company.slug, declined: inviteId }));
|
|
21231
|
+
else success(`Declined invitation #${inviteId}.`);
|
|
21232
|
+
return 0;
|
|
21233
|
+
}
|
|
21234
|
+
|
|
20145
21235
|
// src/commands/module/pull-data.ts
|
|
20146
21236
|
init_api_client();
|
|
20147
21237
|
init_output();
|
|
@@ -20220,6 +21310,7 @@ async function modulePullDataCommand(options) {
|
|
|
20220
21310
|
// src/commands/module/validate.ts
|
|
20221
21311
|
init_api_client();
|
|
20222
21312
|
init_output();
|
|
21313
|
+
init_manifest_reference_lint();
|
|
20223
21314
|
async function moduleValidateCommand(options) {
|
|
20224
21315
|
const projectRoot = resolve(options.dir ?? process.cwd());
|
|
20225
21316
|
const manifestPath2 = join(projectRoot, "module.json");
|
|
@@ -20244,6 +21335,7 @@ async function moduleValidateCommand(options) {
|
|
|
20244
21335
|
);
|
|
20245
21336
|
if (res.ok) {
|
|
20246
21337
|
success("Manifest is valid.");
|
|
21338
|
+
for (const w of referenceFieldWarnings(manifest)) warn(w.message);
|
|
20247
21339
|
return 0;
|
|
20248
21340
|
}
|
|
20249
21341
|
fail(`Unexpected response shape: ${JSON.stringify(res)}`);
|
|
@@ -22646,6 +23738,23 @@ init_api_client();
|
|
|
22646
23738
|
init_config();
|
|
22647
23739
|
init_manifest_io();
|
|
22648
23740
|
init_output();
|
|
23741
|
+
init_declared_secrets();
|
|
23742
|
+
var SECRETS_NOTE = "# Declared secrets (runtime.secrets[]): set each value on your host; Dashwise never exports a secret value.";
|
|
23743
|
+
function secretNameLines(names, format) {
|
|
23744
|
+
const lines = names.map((name) => {
|
|
23745
|
+
switch (format) {
|
|
23746
|
+
case "env":
|
|
23747
|
+
return `# ${name}=`;
|
|
23748
|
+
case "railway":
|
|
23749
|
+
return `# ${name}: railway variables set ${name}='<value>'`;
|
|
23750
|
+
case "vercel":
|
|
23751
|
+
return `# ${name}: vercel env add ${name} production`;
|
|
23752
|
+
case "fly":
|
|
23753
|
+
return `# ${name}: fly secrets set ${name}='<value>'`;
|
|
23754
|
+
}
|
|
23755
|
+
});
|
|
23756
|
+
return [SECRETS_NOTE, ...lines].join("\n");
|
|
23757
|
+
}
|
|
22649
23758
|
var PLACEHOLDER = "<run-`dashwise-api-keys-create-<name>`-and-paste-here>";
|
|
22650
23759
|
async function envExportCommand(options) {
|
|
22651
23760
|
const format = options.format ?? "env";
|
|
@@ -22736,7 +23845,19 @@ async function envExportCommand(options) {
|
|
|
22736
23845
|
...apiKeyComment !== null ? { comment: apiKeyComment } : {}
|
|
22737
23846
|
}
|
|
22738
23847
|
];
|
|
23848
|
+
const secretNames = options.withSecretNames ? declaredSecretNames(manifest) : [];
|
|
22739
23849
|
process.stdout.write(render(entries, format));
|
|
23850
|
+
if (secretNames.length > 0) {
|
|
23851
|
+
if (format === "json") {
|
|
23852
|
+
info(
|
|
23853
|
+
`Declared secrets to set on your host (Dashwise never exports a secret value): ${secretNames.join(", ")}`
|
|
23854
|
+
);
|
|
23855
|
+
} else {
|
|
23856
|
+
process.stdout.write(`
|
|
23857
|
+
|
|
23858
|
+
${secretNameLines(secretNames, format)}`);
|
|
23859
|
+
}
|
|
23860
|
+
}
|
|
22740
23861
|
if (format !== "json") process.stdout.write("\n");
|
|
22741
23862
|
if (apiKey === PLACEHOLDER) {
|
|
22742
23863
|
info("");
|
|
@@ -22792,6 +23913,7 @@ init_config();
|
|
|
22792
23913
|
init_dotenv_local();
|
|
22793
23914
|
init_manifest_io();
|
|
22794
23915
|
init_output();
|
|
23916
|
+
init_install_target();
|
|
22795
23917
|
init_dev();
|
|
22796
23918
|
async function queryCommand(options) {
|
|
22797
23919
|
const projectRoot = resolve(options.dir ?? process.cwd());
|
|
@@ -22814,17 +23936,25 @@ async function queryCommand(options) {
|
|
|
22814
23936
|
);
|
|
22815
23937
|
return 1;
|
|
22816
23938
|
}
|
|
23939
|
+
const keyInShell = process.env.DASHWISE_API_KEY !== void 0;
|
|
22817
23940
|
hydrateEnvFromDotenvLocal(projectRoot);
|
|
23941
|
+
const cfg = (() => {
|
|
23942
|
+
try {
|
|
23943
|
+
return readConfig();
|
|
23944
|
+
} catch {
|
|
23945
|
+
return null;
|
|
23946
|
+
}
|
|
23947
|
+
})();
|
|
23948
|
+
const apiUrl = process.env.DASHWISE_API_URL ?? cfg?.apiUrl ?? "http://localhost:3000";
|
|
23949
|
+
let keySource = keyInShell ? "environment" : ".env.local";
|
|
23950
|
+
let entry = manifestSlug ? readModuleEntry(manifestSlug) : null;
|
|
22818
23951
|
if (!process.env.DASHWISE_API_KEY && manifestSlug) {
|
|
22819
|
-
|
|
22820
|
-
|
|
22821
|
-
|
|
22822
|
-
|
|
22823
|
-
|
|
22824
|
-
|
|
22825
|
-
})();
|
|
22826
|
-
const apiUrl = process.env.DASHWISE_API_URL ?? cfg?.apiUrl ?? "http://localhost:3000";
|
|
22827
|
-
let entry = readModuleEntry(manifestSlug);
|
|
23952
|
+
if (entry && options.install !== void 0 && entry.installationId !== options.install) {
|
|
23953
|
+
fail(
|
|
23954
|
+
`No key for install #${options.install}: the cached ${code("dwk_")} key of ${code(manifestSlug)} belongs to install #${entry.installationId}. Nothing was run.`
|
|
23955
|
+
);
|
|
23956
|
+
return 1;
|
|
23957
|
+
}
|
|
22828
23958
|
if (cfg) {
|
|
22829
23959
|
const healed = await resolveModuleApiKey(
|
|
22830
23960
|
manifestSlug,
|
|
@@ -22836,11 +23966,26 @@ async function queryCommand(options) {
|
|
|
22836
23966
|
}
|
|
22837
23967
|
if (entry?.apiKey) {
|
|
22838
23968
|
process.env.DASHWISE_API_KEY = entry.apiKey;
|
|
23969
|
+
keySource = "cached";
|
|
22839
23970
|
if (!process.env.DASHWISE_API_URL) {
|
|
22840
23971
|
process.env.DASHWISE_API_URL = apiUrl;
|
|
22841
23972
|
}
|
|
22842
23973
|
}
|
|
22843
23974
|
}
|
|
23975
|
+
if (process.env.DASHWISE_API_KEY) {
|
|
23976
|
+
const target = await nameKeyTarget({
|
|
23977
|
+
apiKey: process.env.DASHWISE_API_KEY,
|
|
23978
|
+
keySource,
|
|
23979
|
+
entry,
|
|
23980
|
+
cfg,
|
|
23981
|
+
apiUrl: process.env.DASHWISE_API_URL ?? apiUrl,
|
|
23982
|
+
...options.install !== void 0 ? { install: options.install } : {}
|
|
23983
|
+
});
|
|
23984
|
+
if (target !== 0) return target;
|
|
23985
|
+
} else if (options.install !== void 0) {
|
|
23986
|
+
fail(`No ${code("dwk_")} key to check against install #${options.install}. Set ${code("DASHWISE_API_KEY")}. Nothing was run.`);
|
|
23987
|
+
return 1;
|
|
23988
|
+
}
|
|
22844
23989
|
const modes = [
|
|
22845
23990
|
options.inline !== void 0 ? "inline" : null,
|
|
22846
23991
|
options.file !== void 0 ? "file" : null,
|
|
@@ -23206,7 +24351,7 @@ function formatTimestamp(iso) {
|
|
|
23206
24351
|
return iso.slice(0, 19).replace("T", " ");
|
|
23207
24352
|
}
|
|
23208
24353
|
function sleep2(ms) {
|
|
23209
|
-
return new Promise((
|
|
24354
|
+
return new Promise((resolve16) => setTimeout(resolve16, ms));
|
|
23210
24355
|
}
|
|
23211
24356
|
|
|
23212
24357
|
// src/commands/module/action-log.ts
|
|
@@ -23308,7 +24453,7 @@ async function runTail2(installationId, options) {
|
|
|
23308
24453
|
}
|
|
23309
24454
|
dim(`(tail: transient error \u2014 ${err.message})`);
|
|
23310
24455
|
}
|
|
23311
|
-
await new Promise((
|
|
24456
|
+
await new Promise((resolve16) => setTimeout(resolve16, POLL_INTERVAL_MS2));
|
|
23312
24457
|
}
|
|
23313
24458
|
}
|
|
23314
24459
|
function printTable(rows) {
|
|
@@ -23693,7 +24838,7 @@ async function sleep3(ms, aborted) {
|
|
|
23693
24838
|
const slice = 100;
|
|
23694
24839
|
let waited = 0;
|
|
23695
24840
|
while (waited < ms && !aborted()) {
|
|
23696
|
-
await new Promise((
|
|
24841
|
+
await new Promise((resolve16) => setTimeout(resolve16, Math.min(slice, ms - waited)));
|
|
23697
24842
|
waited += slice;
|
|
23698
24843
|
}
|
|
23699
24844
|
}
|
|
@@ -24034,7 +25179,7 @@ async function moduleRuntimeSetCommand(slugArg, options) {
|
|
|
24034
25179
|
const slug = before.module.slug;
|
|
24035
25180
|
const subject = { module: slug, installationId: before.installation_id };
|
|
24036
25181
|
if (streaming !== void 0 && !options.yes && !options.json && process.stdin.isTTY) {
|
|
24037
|
-
const ok = await confirm({
|
|
25182
|
+
const ok = await confirm$1({
|
|
24038
25183
|
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.`
|
|
24039
25184
|
});
|
|
24040
25185
|
if (isCancel(ok) || ok !== true) {
|
|
@@ -24524,12 +25669,29 @@ moduleCmd.command("list").description("List modules you own (or `--all` for owne
|
|
|
24524
25669
|
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
24525
25670
|
});
|
|
24526
25671
|
});
|
|
24527
|
-
moduleCmd.command("diff").description(
|
|
24528
|
-
|
|
24529
|
-
|
|
24530
|
-
|
|
24531
|
-
|
|
24532
|
-
});
|
|
25672
|
+
moduleCmd.command("diff").description(
|
|
25673
|
+
"Show what would change at the next publish vs the currently-published version. With --check: exit 1 when module.json is not an additive evolution of the released manifest (a removed table or field, a retype, a removed or reordered option, a new unique index (`indexes[].unique`) over an existing field, a dropped unique index, required tightened, or a slug reused after an earlier removal), 2 on a usage or read error. Offline with --base."
|
|
25674
|
+
).option("--dir <path>", "Project root (default: current directory).").option("--json", "Emit the structured diff as JSON instead of the human-readable summary.").option("--check", "Check additive evolution against the released manifest (exit 0 additive, 1 not additive, 2 error).").option("--base <ref|path>", "With --check: the base manifest from a git ref (its module.json, history from its log) or, when the path exists, a file. Default: the released version on the platform.").option("--base-file <path>", "With --check: the base manifest from a file.").option("--head <path>", "With --check: the manifest to check (default: <dir>/module.json).").option("--no-history", "With --check: skip the slug-reuse scan of older versions.").option("--dry-run", "Accepted for uniformity; --check never writes anything.").action(
|
|
25675
|
+
async (cmdOpts) => {
|
|
25676
|
+
if (cmdOpts.check) {
|
|
25677
|
+
const { moduleDiffCheckCommand: moduleDiffCheckCommand2 } = await Promise.resolve().then(() => (init_diff_check(), diff_check_exports));
|
|
25678
|
+
process.exitCode = await moduleDiffCheckCommand2({
|
|
25679
|
+
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
25680
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {},
|
|
25681
|
+
...cmdOpts.base !== void 0 ? { base: cmdOpts.base } : {},
|
|
25682
|
+
...cmdOpts.baseFile !== void 0 ? { baseFile: cmdOpts.baseFile } : {},
|
|
25683
|
+
...cmdOpts.head !== void 0 ? { head: cmdOpts.head } : {},
|
|
25684
|
+
// commander maps `--no-history` to `history === false`.
|
|
25685
|
+
...cmdOpts.history === false ? { noHistory: true } : {}
|
|
25686
|
+
});
|
|
25687
|
+
return;
|
|
25688
|
+
}
|
|
25689
|
+
process.exitCode = await moduleDiffCommand({
|
|
25690
|
+
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
25691
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
25692
|
+
});
|
|
25693
|
+
}
|
|
25694
|
+
);
|
|
24533
25695
|
moduleCmd.command("pull-data").description("Export the module's own-table rows to a local JSON file (for fixtures, local dev seeding).").option("--dir <path>", "Project root (default: current directory).").option("--limit <n>", "Max rows per table (1-1000, default 100).", parseIntOption).option("--out <path>", "Output file path (default: ./dashwise-data.json).").action(async (cmdOpts) => {
|
|
24534
25696
|
process.exitCode = await modulePullDataCommand({
|
|
24535
25697
|
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
@@ -24681,6 +25843,10 @@ program.command("query").description(
|
|
|
24681
25843
|
).option(
|
|
24682
25844
|
"--stream",
|
|
24683
25845
|
"Call .stream() instead of .execute() \u2014 prints rows incrementally as JSON-lines (one row per line). Lets you see progress on big queries + Ctrl-C cleanly midway. Mutually exclusive with --explain and --repl."
|
|
25846
|
+
).option(
|
|
25847
|
+
"--install <id>",
|
|
25848
|
+
"The install the key must belong to: reads that install's key list with your CLI login (one request, 1.5 s), prints `\u2192 install #<id> (key checked)` and refuses (exit 1) when DASHWISE_API_KEY is not an active key of that install or the list cannot be read.",
|
|
25849
|
+
parseIntOption
|
|
24684
25850
|
).action(
|
|
24685
25851
|
async (cmdOpts) => {
|
|
24686
25852
|
process.exitCode = await queryCommand({
|
|
@@ -24690,16 +25856,22 @@ program.command("query").description(
|
|
|
24690
25856
|
...cmdOpts.repl !== void 0 ? { repl: cmdOpts.repl } : {},
|
|
24691
25857
|
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {},
|
|
24692
25858
|
...cmdOpts.explain !== void 0 ? { explain: cmdOpts.explain } : {},
|
|
24693
|
-
...cmdOpts.stream !== void 0 ? { stream: cmdOpts.stream } : {}
|
|
25859
|
+
...cmdOpts.stream !== void 0 ? { stream: cmdOpts.stream } : {},
|
|
25860
|
+
...cmdOpts.install !== void 0 ? { install: cmdOpts.install } : {}
|
|
24694
25861
|
});
|
|
24695
25862
|
}
|
|
24696
25863
|
);
|
|
24697
25864
|
program.command("run [cmd...]").description(
|
|
24698
|
-
"Run a command with the module's Dashwise env injected \u2014 resolves DASHWISE_API_URL + DASHWISE_API_KEY through the same chain as `dashwise query` (explicit env \u2192 .env.local \u2192 the cached dwk_ key) and spawns the command with them overlaid, inheriting stdio and propagating the exit code. Example: `dashwise run -- node seed.mjs`."
|
|
24699
|
-
).option("--dir <path>", "Project root containing `module.json` (default: current directory).").
|
|
25865
|
+
"Run a command with the module's Dashwise env injected \u2014 resolves DASHWISE_API_URL + DASHWISE_API_KEY through the same chain as `dashwise query` (explicit env \u2192 .env.local \u2192 the cached dwk_ key) and spawns the command with them overlaid, inheriting stdio and propagating the exit code. Names its target first (`\u2192 install #<id> (\u2026)`, at most one lookup request); `--install <id>` refuses a key of any other install. Example: `dashwise run --install 42 -- node seed.mjs`."
|
|
25866
|
+
).option("--dir <path>", "Project root containing `module.json` (default: current directory).").option(
|
|
25867
|
+
"--install <id>",
|
|
25868
|
+
"The install the key must belong to. Reads that install's key list with your CLI login (one request, 1.5 s), prints `\u2192 install #<id> (key checked)` and refuses (exit 1, nothing run) when DASHWISE_API_KEY is not an active key of that install or the list cannot be read.",
|
|
25869
|
+
parseIntOption
|
|
25870
|
+
).action(async (cmdArgs, cmdOpts) => {
|
|
24700
25871
|
const { runCommand: runCommand2 } = await Promise.resolve().then(() => (init_run(), run_exports));
|
|
24701
25872
|
process.exitCode = await runCommand2(cmdArgs ?? [], {
|
|
24702
|
-
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {}
|
|
25873
|
+
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
25874
|
+
...cmdOpts.install !== void 0 ? { install: cmdOpts.install } : {}
|
|
24703
25875
|
});
|
|
24704
25876
|
});
|
|
24705
25877
|
program.command("pull").description(
|
|
@@ -24826,13 +25998,17 @@ envCmd.command("export").description(
|
|
|
24826
25998
|
).option(
|
|
24827
25999
|
"--key <dwk_value>",
|
|
24828
26000
|
"Use a previously-minted API key (paste plaintext). (Mutually exclusive with --new-key.)"
|
|
26001
|
+
).option(
|
|
26002
|
+
"--with-secret-names",
|
|
26003
|
+
"Also name each secret declared in module.json runtime.secrets[] as a commented `# KEY=` line (a comment naming the command for railway/vercel/fly; stderr for json). Never a value: values never leave Dashwise; set them on your host."
|
|
24829
26004
|
).action(
|
|
24830
26005
|
async (cmdOpts) => {
|
|
24831
26006
|
process.exitCode = await envExportCommand({
|
|
24832
26007
|
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
24833
26008
|
...cmdOpts.format !== void 0 ? { format: cmdOpts.format } : {},
|
|
24834
26009
|
...cmdOpts.newKey !== void 0 ? { newKey: cmdOpts.newKey } : {},
|
|
24835
|
-
...cmdOpts.key !== void 0 ? { key: cmdOpts.key } : {}
|
|
26010
|
+
...cmdOpts.key !== void 0 ? { key: cmdOpts.key } : {},
|
|
26011
|
+
...cmdOpts.withSecretNames ? { withSecretNames: true } : {}
|
|
24836
26012
|
});
|
|
24837
26013
|
}
|
|
24838
26014
|
);
|
|
@@ -24858,7 +26034,10 @@ moduleCmd.command("publish").description("Pack the current directory + upload it
|
|
|
24858
26034
|
).option(
|
|
24859
26035
|
"--timeout <seconds>",
|
|
24860
26036
|
"Max seconds to wait for an async build before giving up polling (default 360)."
|
|
24861
|
-
).option("--json", "Emit the final build result as machine-readable JSON.").
|
|
26037
|
+
).option("--json", "Emit the final build result as machine-readable JSON.").option(
|
|
26038
|
+
"--strict",
|
|
26039
|
+
"Refuse to publish when module.json is not an additive evolution of the released version (the `module diff --check` rules), or when that check cannot run. Without it the check only warns."
|
|
26040
|
+
).action(
|
|
24862
26041
|
async (cmdOpts) => {
|
|
24863
26042
|
const timeoutSec = cmdOpts.timeout !== void 0 ? Number(cmdOpts.timeout) : void 0;
|
|
24864
26043
|
process.exitCode = await modulePublishCommand({
|
|
@@ -24874,19 +26053,23 @@ moduleCmd.command("publish").description("Pack the current directory + upload it
|
|
|
24874
26053
|
// commander maps `--no-wait` to `wait === false`.
|
|
24875
26054
|
...cmdOpts.wait === false ? { noWait: true } : {},
|
|
24876
26055
|
...timeoutSec !== void 0 && Number.isFinite(timeoutSec) ? { timeoutSec } : {},
|
|
24877
|
-
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
26056
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {},
|
|
26057
|
+
...cmdOpts.strict !== void 0 ? { strict: cmdOpts.strict } : {}
|
|
24878
26058
|
});
|
|
24879
26059
|
}
|
|
24880
26060
|
);
|
|
24881
|
-
moduleCmd.command("install [slug]").alias("deploy").description(
|
|
24882
|
-
|
|
26061
|
+
moduleCmd.command("install [slug]").alias("deploy").alias("upgrade").description(
|
|
26062
|
+
"Install (deploy) a published module into a workspace \u2014 provisions the runtime and prints the public URL. Upgrades/repairs an existing production install idempotently: an upgrade first prints what it does to the copy's data (tables and fields it moves to the Trash or converts, with their counts), asks once, and sends the preview's drift_hash. A move that loses data is accepted only by a person: the one question in a terminal, or --accept-data-loss <drift_hash> with the hash the preview printed. --yes never accepts data loss; with no terminal, or inside an AI agent session, a lossy move is refused and the refusal prints the re-run line. When only a dev install exists it asks whether to install alongside it (--keep-dev-install) or replace it (--replace-dev-install), rather than silently landing on the dev row. Exits 3 \u2014 \"deployed, setup pending\" \u2014 when a required `uses` slot is still unbound: the deploy succeeded but the module throws on its first data call until an admin binds it."
|
|
24883
26063
|
).option("--workspace <slug-or-id>", "Target workspace (default: cached module workspace, config default, or interactive picker).").option("--version <semver>", "Install a specific published version (default: latest).").option("--force", "Proceed even when the upgrade breaks consumer modules (UPGRADE_BREAKS_CONSUMERS).").option(
|
|
24884
26064
|
"--keep-dev-install",
|
|
24885
26065
|
"Install production ALONGSIDE the workspace's kind=local dev install, leaving local dev working. Non-destructive. Backends >= 2026-07-15 keep a separate live-install slot per kind group, so the two coexist."
|
|
24886
26066
|
).option(
|
|
24887
26067
|
"--replace-dev-install",
|
|
24888
26068
|
"Destroy the workspace's kind=local dev install first, then fresh production install. Revokes the cached local-dev credentials; on backends >= 2026-07-15 local dev can be re-provisioned afterwards (dashwise module reset-dev-install, or dashwise init <slug> --workspace <ws>) and will coexist with the production install. On OLDER backends this is one-way \u2014 prefer --keep-dev-install."
|
|
24889
|
-
).option("--dir <path>", "Project root for the module.json slug lookup (default: current directory).").option("-y, --yes", "Skip the confirmation prompt (for CI / scripted use).
|
|
26069
|
+
).option("--dir <path>", "Project root for the module.json slug lookup (default: current directory).").option("-y, --yes", "Skip the confirmation prompt (for CI / scripted use). Never accepts data loss: see --accept-data-loss.").option(
|
|
26070
|
+
"--accept-data-loss <drift_hash>",
|
|
26071
|
+
"Accept the data loss of this upgrade: the drift_hash its preview printed. Only that exact hash accepts it (a person decides; an agent stops and reports the hash)."
|
|
26072
|
+
).option("--json", "Emit the final result as machine-readable JSON.").action(
|
|
24890
26073
|
async (slug, cmdOpts) => {
|
|
24891
26074
|
process.exitCode = await moduleInstallCommand(slug, {
|
|
24892
26075
|
...cmdOpts.workspace !== void 0 ? { workspace: cmdOpts.workspace } : {},
|
|
@@ -24896,6 +26079,7 @@ moduleCmd.command("install [slug]").alias("deploy").description(
|
|
|
24896
26079
|
...cmdOpts.replaceDevInstall !== void 0 ? { replaceDevInstall: cmdOpts.replaceDevInstall } : {},
|
|
24897
26080
|
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
24898
26081
|
...cmdOpts.yes !== void 0 ? { yes: cmdOpts.yes } : {},
|
|
26082
|
+
...cmdOpts.acceptDataLoss !== void 0 ? { acceptDataLoss: cmdOpts.acceptDataLoss } : {},
|
|
24899
26083
|
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
24900
26084
|
});
|
|
24901
26085
|
}
|
|
@@ -24915,6 +26099,63 @@ moduleCmd.command("uninstall [slug]").description(
|
|
|
24915
26099
|
});
|
|
24916
26100
|
}
|
|
24917
26101
|
);
|
|
26102
|
+
var invitesCmd = moduleCmd.command("invites").description(
|
|
26103
|
+
"Install invitations: send, list and withdraw them for your module, or see and decline the ones your company received."
|
|
26104
|
+
);
|
|
26105
|
+
var inviteIdArg = (value) => value !== void 0 && /^[1-9][0-9]*$/.test(value) ? Number(value) : void 0;
|
|
26106
|
+
invitesCmd.command("list <slug>").description("List the open install invitations for your module.").option("--json", "Emit machine-readable JSON.").action(async (slug, cmdOpts) => {
|
|
26107
|
+
process.exitCode = await moduleInvitesListCommand(slug, {
|
|
26108
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
26109
|
+
});
|
|
26110
|
+
});
|
|
26111
|
+
invitesCmd.command("send <slug>").description(
|
|
26112
|
+
"Invite another company to install your module. Needs admin rights in the module's company. Asks before sending; pass --yes in scripts."
|
|
26113
|
+
).option("--to <company>", "The company id to invite, or the slug of a company you belong to.").option("--note <text>", "A short note shown to the invited company (max 1000 characters).").option("-y, --yes", "Send without asking (required in non-interactive or --json mode).").option("--json", "Emit machine-readable JSON.").action(
|
|
26114
|
+
async (slug, cmdOpts) => {
|
|
26115
|
+
process.exitCode = await moduleInvitesSendCommand(slug, {
|
|
26116
|
+
...cmdOpts.to !== void 0 ? { to: cmdOpts.to } : {},
|
|
26117
|
+
...cmdOpts.note !== void 0 ? { note: cmdOpts.note } : {},
|
|
26118
|
+
...cmdOpts.yes !== void 0 ? { yes: cmdOpts.yes } : {},
|
|
26119
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
26120
|
+
});
|
|
26121
|
+
}
|
|
26122
|
+
);
|
|
26123
|
+
invitesCmd.command("revoke <slug> <invite-id>").description(
|
|
26124
|
+
"Withdraw an install invitation. Installations made before stay. Asks first; pass --yes in scripts."
|
|
26125
|
+
).option("-y, --yes", "Withdraw without asking (required in non-interactive or --json mode).").option("--json", "Emit machine-readable JSON.").action(async (slug, inviteId, cmdOpts) => {
|
|
26126
|
+
process.exitCode = await moduleInvitesRevokeCommand(slug, inviteIdArg(inviteId), {
|
|
26127
|
+
...cmdOpts.yes !== void 0 ? { yes: cmdOpts.yes } : {},
|
|
26128
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
26129
|
+
});
|
|
26130
|
+
});
|
|
26131
|
+
invitesCmd.command("received").description("List the open install invitations your company received (company admins).").option("--workspace <slug-or-id>", "Company (default: config default or interactive picker).").option("--json", "Emit machine-readable JSON.").action(async (cmdOpts) => {
|
|
26132
|
+
process.exitCode = await moduleInvitesReceivedCommand({
|
|
26133
|
+
...cmdOpts.workspace !== void 0 ? { workspace: cmdOpts.workspace } : {},
|
|
26134
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
26135
|
+
});
|
|
26136
|
+
});
|
|
26137
|
+
invitesCmd.command("decline <invite-id>").description(
|
|
26138
|
+
"Decline an install invitation your company received (company admins). Asks first; pass --yes in scripts."
|
|
26139
|
+
).option("--workspace <slug-or-id>", "Company (default: config default or interactive picker).").option("-y, --yes", "Decline without asking (required in non-interactive or --json mode).").option("--json", "Emit machine-readable JSON.").action(
|
|
26140
|
+
async (inviteId, cmdOpts) => {
|
|
26141
|
+
process.exitCode = await moduleInvitesDeclineCommand(inviteIdArg(inviteId), {
|
|
26142
|
+
...cmdOpts.workspace !== void 0 ? { workspace: cmdOpts.workspace } : {},
|
|
26143
|
+
...cmdOpts.yes !== void 0 ? { yes: cmdOpts.yes } : {},
|
|
26144
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
26145
|
+
});
|
|
26146
|
+
}
|
|
26147
|
+
);
|
|
26148
|
+
moduleCmd.command("installations [slug]").description(
|
|
26149
|
+
"List the installations in a company that you can see: id, module, name, audience, version, held state and public address. With a slug, only that module. Read-only."
|
|
26150
|
+
).option("--workspace <slug-or-id>", "Company (default: cached module company, config default, or interactive picker).").option("--dev", "Include development installations.").option("--json", "Emit machine-readable JSON.").action(
|
|
26151
|
+
async (slug, cmdOpts) => {
|
|
26152
|
+
process.exitCode = await moduleInstallationsCommand(slug, {
|
|
26153
|
+
...cmdOpts.workspace !== void 0 ? { workspace: cmdOpts.workspace } : {},
|
|
26154
|
+
...cmdOpts.dev !== void 0 ? { dev: cmdOpts.dev } : {},
|
|
26155
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
26156
|
+
});
|
|
26157
|
+
}
|
|
26158
|
+
);
|
|
24918
26159
|
moduleCmd.command("status [slug]").description(
|
|
24919
26160
|
"Show a module's deploy state in a workspace: install kind, installed vs latest published version, runtime provisioning, and the public URL."
|
|
24920
26161
|
).option("--workspace <slug-or-id>", "Target workspace (default: cached module workspace, config default, or interactive picker).").option("--dir <path>", "Project root for the module.json slug lookup (default: current directory).").option("--json", "Emit machine-readable JSON.").action(
|
|
@@ -25192,7 +26433,7 @@ fieldCmd.command("set-type <table-slug> <field-slug> <new-type>").description(
|
|
|
25192
26433
|
}
|
|
25193
26434
|
);
|
|
25194
26435
|
function getVersion() {
|
|
25195
|
-
return "0.
|
|
26436
|
+
return "0.27.0";
|
|
25196
26437
|
}
|
|
25197
26438
|
function parseIntOption(value) {
|
|
25198
26439
|
const n = Number.parseInt(value, 10);
|