@dashai/cli 0.25.0 → 0.26.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 +833 -53
- package/dist/bin.js.map +1 -1
- package/package.json +2 -2
package/dist/bin.js
CHANGED
|
@@ -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
|
}
|
|
@@ -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({
|
|
@@ -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
|
|
|
@@ -13468,6 +13548,9 @@ async function moduleInitCommand(slugArg, options) {
|
|
|
13468
13548
|
writeFileSync(absPath, content, "utf-8");
|
|
13469
13549
|
written++;
|
|
13470
13550
|
}
|
|
13551
|
+
reportSecretLines(
|
|
13552
|
+
syncEnvLocalSecretLines(dir, declaredSecretNames(readManifest(dir)))
|
|
13553
|
+
);
|
|
13471
13554
|
}
|
|
13472
13555
|
if (writeMode !== "none" && !cloudReady) {
|
|
13473
13556
|
process.stdout.write(
|
|
@@ -13710,10 +13793,297 @@ var init_init = __esm({
|
|
|
13710
13793
|
init_output();
|
|
13711
13794
|
init_api_client();
|
|
13712
13795
|
init_config();
|
|
13796
|
+
init_declared_secrets();
|
|
13713
13797
|
init_manifest_io();
|
|
13714
13798
|
init_workspaces();
|
|
13715
13799
|
}
|
|
13716
13800
|
});
|
|
13801
|
+
|
|
13802
|
+
// src/lib/manifest-evolution.ts
|
|
13803
|
+
function compareManifests(base, head, history = []) {
|
|
13804
|
+
const violations = [];
|
|
13805
|
+
const additions = [];
|
|
13806
|
+
const warnings = [];
|
|
13807
|
+
const v = (rule, where, detail) => violations.push({ rule, where, detail });
|
|
13808
|
+
const baseTables = tablesOf(base);
|
|
13809
|
+
const headTables = tablesOf(head);
|
|
13810
|
+
const everTables = /* @__PURE__ */ new Set();
|
|
13811
|
+
const everFields = /* @__PURE__ */ new Set();
|
|
13812
|
+
for (const m of history) {
|
|
13813
|
+
for (const [slug, t] of tablesOf(m)) {
|
|
13814
|
+
everTables.add(slug);
|
|
13815
|
+
for (const f of t.fields ?? []) everFields.add(`${slug}.${f.slug}`);
|
|
13816
|
+
}
|
|
13817
|
+
}
|
|
13818
|
+
for (const [slug, bt] of baseTables) {
|
|
13819
|
+
const ht = headTables.get(slug);
|
|
13820
|
+
if (!ht) {
|
|
13821
|
+
v("removed_table", slug, "tables are never removed; deprecate instead");
|
|
13822
|
+
continue;
|
|
13823
|
+
}
|
|
13824
|
+
const bf = fieldsOf(bt);
|
|
13825
|
+
const hf = fieldsOf(ht);
|
|
13826
|
+
for (const [fs, f] of bf) {
|
|
13827
|
+
const g = hf.get(fs);
|
|
13828
|
+
const where = `${slug}.${fs}`;
|
|
13829
|
+
if (!g) {
|
|
13830
|
+
v("removed_field", where, "fields are never removed; stop writing it and set human_edit: open");
|
|
13831
|
+
continue;
|
|
13832
|
+
}
|
|
13833
|
+
if (f.type !== g.type) v("retyped_field", where, `${f.type} \u2192 ${g.type}`);
|
|
13834
|
+
if ((f.decimal_places ?? null) !== (g.decimal_places ?? null)) {
|
|
13835
|
+
v("retyped_field", where, `decimal_places ${f.decimal_places ?? "none"} \u2192 ${g.decimal_places ?? "none"}`);
|
|
13836
|
+
}
|
|
13837
|
+
if ((f.target ?? null) !== (g.target ?? null)) v("retyped_field", where, `link target ${f.target} \u2192 ${g.target}`);
|
|
13838
|
+
if (f.required !== true && g.required === true) {
|
|
13839
|
+
v("required_tightened", where, "old rows are empty; enforce it in the tool layer instead");
|
|
13840
|
+
}
|
|
13841
|
+
const bo = f.options ?? [];
|
|
13842
|
+
const ho = g.options ?? [];
|
|
13843
|
+
for (const [i, o] of bo.entries()) {
|
|
13844
|
+
if (!ho.includes(o)) v("removed_option", where, `option "${String(o)}" removed`);
|
|
13845
|
+
else if (ho[i] !== o) {
|
|
13846
|
+
v("reordered_option", where, `option "${String(o)}" moved from ${i} to ${ho.indexOf(o)}; options are append-only`);
|
|
13847
|
+
}
|
|
13848
|
+
}
|
|
13849
|
+
for (const o of ho.slice(bo.length)) if (!bo.includes(o)) additions.push(`option ${where} "${String(o)}"`);
|
|
13850
|
+
}
|
|
13851
|
+
for (const [fs] of hf) {
|
|
13852
|
+
if (bf.has(fs)) continue;
|
|
13853
|
+
if (everFields.has(`${slug}.${fs}`)) {
|
|
13854
|
+
v("slug_reused", `${slug}.${fs}`, "this slug was removed in an earlier version and may not come back");
|
|
13855
|
+
} else additions.push(`field ${slug}.${fs}`);
|
|
13856
|
+
}
|
|
13857
|
+
const hu = new Set(uniqueIndexes(ht));
|
|
13858
|
+
for (const idx of uniqueIndexes(bt)) {
|
|
13859
|
+
if (!hu.has(idx)) v("dropped_unique_index", `${slug}(${idx})`, "a unique index is a protocol lock; it may not be dropped or changed");
|
|
13860
|
+
}
|
|
13861
|
+
const bu = new Set(uniqueIndexes(bt));
|
|
13862
|
+
for (const idx of hu) {
|
|
13863
|
+
if (bu.has(idx)) continue;
|
|
13864
|
+
const overExisting = idx.split(",").some((f) => f.length > 0 && bf.has(f));
|
|
13865
|
+
if (overExisting) {
|
|
13866
|
+
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");
|
|
13867
|
+
} else additions.push(`unique index ${slug}(${idx})`);
|
|
13868
|
+
}
|
|
13869
|
+
}
|
|
13870
|
+
for (const [slug] of headTables) {
|
|
13871
|
+
if (baseTables.has(slug)) continue;
|
|
13872
|
+
if (everTables.has(slug)) v("slug_reused", slug, "this table slug was removed in an earlier version and may not come back");
|
|
13873
|
+
else additions.push(`table ${slug}`);
|
|
13874
|
+
}
|
|
13875
|
+
const baseViews = new Set((base?.owns?.views ?? []).map((x) => x.slug));
|
|
13876
|
+
const headViews = new Set((head?.owns?.views ?? []).map((x) => x.slug));
|
|
13877
|
+
for (const s of baseViews) if (!headViews.has(s)) warnings.push(`view ${s} removed (callers of qbViews.${s} break)`);
|
|
13878
|
+
for (const s of headViews) if (!baseViews.has(s)) additions.push(`view ${s}`);
|
|
13879
|
+
return { violations, additions, warnings };
|
|
13880
|
+
}
|
|
13881
|
+
var tablesOf, fieldsOf, uniqueIndexes;
|
|
13882
|
+
var init_manifest_evolution = __esm({
|
|
13883
|
+
"src/lib/manifest-evolution.ts"() {
|
|
13884
|
+
tablesOf = (m) => new Map((m?.owns?.tables ?? []).map((t) => [t.slug, t]));
|
|
13885
|
+
fieldsOf = (t) => new Map((t?.fields ?? []).map((f) => [f.slug, f]));
|
|
13886
|
+
uniqueIndexes = (t) => (t?.indexes ?? []).filter((i) => i.unique === true).map((i) => (i.fields ?? []).join(","));
|
|
13887
|
+
}
|
|
13888
|
+
});
|
|
13889
|
+
|
|
13890
|
+
// src/commands/module/diff-check.ts
|
|
13891
|
+
var diff_check_exports = {};
|
|
13892
|
+
__export(diff_check_exports, {
|
|
13893
|
+
EvolutionCheckError: () => EvolutionCheckError,
|
|
13894
|
+
moduleDiffCheckCommand: () => moduleDiffCheckCommand,
|
|
13895
|
+
printEvolutionReport: () => printEvolutionReport,
|
|
13896
|
+
publishEvolutionGate: () => publishEvolutionGate,
|
|
13897
|
+
runEvolutionCheck: () => runEvolutionCheck
|
|
13898
|
+
});
|
|
13899
|
+
function readJson(path) {
|
|
13900
|
+
try {
|
|
13901
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
13902
|
+
} catch (err) {
|
|
13903
|
+
throw new EvolutionCheckError(`could not read ${path}: ${err.message}`);
|
|
13904
|
+
}
|
|
13905
|
+
}
|
|
13906
|
+
function git(cwd, args) {
|
|
13907
|
+
try {
|
|
13908
|
+
return execFileSync("git", ["-C", cwd, ...args], {
|
|
13909
|
+
encoding: "utf8",
|
|
13910
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
13911
|
+
maxBuffer: 64 * 1024 * 1024
|
|
13912
|
+
});
|
|
13913
|
+
} catch (err) {
|
|
13914
|
+
const stderr = err.stderr?.trim();
|
|
13915
|
+
throw new EvolutionCheckError(`git ${args.join(" ")} failed${stderr ? `: ${stderr}` : ""}`);
|
|
13916
|
+
}
|
|
13917
|
+
}
|
|
13918
|
+
async function runEvolutionCheck(options) {
|
|
13919
|
+
const projectRoot = resolve(options.dir ?? process.cwd());
|
|
13920
|
+
const headPath = options.head ? resolve(options.head) : join(projectRoot, "module.json");
|
|
13921
|
+
if (!existsSync(headPath)) throw new EvolutionCheckError(`no manifest at ${headPath}`);
|
|
13922
|
+
const head = readJson(headPath);
|
|
13923
|
+
const baseFile = options.baseFile ?? (options.base && existsSync(options.base) ? options.base : void 0);
|
|
13924
|
+
if (baseFile) {
|
|
13925
|
+
const base = readJson(baseFile);
|
|
13926
|
+
return { ...compareManifests(base, head), base: baseFile, head: headPath, history: 0, nothingReleased: false };
|
|
13927
|
+
}
|
|
13928
|
+
if (options.base) {
|
|
13929
|
+
const ref = options.base;
|
|
13930
|
+
const base = JSON.parse(git(projectRoot, ["show", `${ref}:./module.json`]));
|
|
13931
|
+
let history2 = [];
|
|
13932
|
+
if (!options.noHistory) {
|
|
13933
|
+
const shas = git(projectRoot, ["log", "--format=%H", "--reverse", ref, "--", "module.json"]).split("\n").filter(Boolean);
|
|
13934
|
+
history2 = shas.slice(0, -1).map((sha) => JSON.parse(git(projectRoot, ["show", `${sha}:./module.json`])));
|
|
13935
|
+
}
|
|
13936
|
+
return {
|
|
13937
|
+
...compareManifests(base, head, history2),
|
|
13938
|
+
base: `${ref}:module.json`,
|
|
13939
|
+
head: headPath,
|
|
13940
|
+
history: history2.length,
|
|
13941
|
+
nothingReleased: false
|
|
13942
|
+
};
|
|
13943
|
+
}
|
|
13944
|
+
const slug = head.module?.slug;
|
|
13945
|
+
if (!slug) throw new EvolutionCheckError(`${headPath} has no module.slug`);
|
|
13946
|
+
let mod;
|
|
13947
|
+
let versions;
|
|
13948
|
+
try {
|
|
13949
|
+
mod = await apiRequest(
|
|
13950
|
+
"GET",
|
|
13951
|
+
`/api/modules/by-slug/${encodeURIComponent(slug)}`
|
|
13952
|
+
);
|
|
13953
|
+
if (!mod.current_version) {
|
|
13954
|
+
return {
|
|
13955
|
+
violations: [],
|
|
13956
|
+
additions: [],
|
|
13957
|
+
warnings: [],
|
|
13958
|
+
base: "(nothing released)",
|
|
13959
|
+
head: headPath,
|
|
13960
|
+
history: 0,
|
|
13961
|
+
nothingReleased: true
|
|
13962
|
+
};
|
|
13963
|
+
}
|
|
13964
|
+
versions = await apiRequest("GET", `/api/modules/${mod.id}/versions`);
|
|
13965
|
+
} catch (err) {
|
|
13966
|
+
const why = err instanceof ApiError ? `${err.code || err.status} ${err.message}` : err.message;
|
|
13967
|
+
throw new EvolutionCheckError(
|
|
13968
|
+
`could not read the released manifest of ${slug} (${why}); pass --base <ref|file> to check offline`
|
|
13969
|
+
);
|
|
13970
|
+
}
|
|
13971
|
+
const released = versions.find((v) => v.version === mod.current_version);
|
|
13972
|
+
if (!released?.manifest_jsonb) {
|
|
13973
|
+
throw new EvolutionCheckError(
|
|
13974
|
+
`the platform returned no manifest for ${slug} v${mod.current_version}; pass --base <ref|file> to check offline`
|
|
13975
|
+
);
|
|
13976
|
+
}
|
|
13977
|
+
const releasedAt = released.created_on ? Date.parse(released.created_on) : Number.POSITIVE_INFINITY;
|
|
13978
|
+
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);
|
|
13979
|
+
return {
|
|
13980
|
+
...compareManifests(released.manifest_jsonb, head, history),
|
|
13981
|
+
base: `${slug} v${mod.current_version} (released)`,
|
|
13982
|
+
head: headPath,
|
|
13983
|
+
history: history.length,
|
|
13984
|
+
nothingReleased: false
|
|
13985
|
+
};
|
|
13986
|
+
}
|
|
13987
|
+
function printEvolutionReport(r, print = out) {
|
|
13988
|
+
print(`module diff --check: ${r.head} against ${r.base} (${r.history} older version${r.history === 1 ? "" : "s"} scanned)`);
|
|
13989
|
+
for (const a of r.additions) print(` + ${a}`);
|
|
13990
|
+
for (const w of r.warnings) print(` ! ${w}`);
|
|
13991
|
+
for (const x of r.violations) print(` \u2717 ${x.rule}: ${x.where}: ${x.detail}`);
|
|
13992
|
+
}
|
|
13993
|
+
async function moduleDiffCheckCommand(options) {
|
|
13994
|
+
let r;
|
|
13995
|
+
try {
|
|
13996
|
+
r = await runEvolutionCheck(options);
|
|
13997
|
+
} catch (err) {
|
|
13998
|
+
if (err instanceof EvolutionCheckError) {
|
|
13999
|
+
if (options.json) out(JSON.stringify({ ok: false, error: { code: "CHECK_FAILED", message: err.message } }));
|
|
14000
|
+
else fail(`module diff --check: ${err.message}`);
|
|
14001
|
+
return 2;
|
|
14002
|
+
}
|
|
14003
|
+
throw err;
|
|
14004
|
+
}
|
|
14005
|
+
if (options.json) {
|
|
14006
|
+
out(JSON.stringify({ ok: r.violations.length === 0, ...r }, null, 2));
|
|
14007
|
+
return r.violations.length === 0 ? 0 : 1;
|
|
14008
|
+
}
|
|
14009
|
+
if (r.nothingReleased) {
|
|
14010
|
+
success("Nothing released yet: every change is additive.");
|
|
14011
|
+
return 0;
|
|
14012
|
+
}
|
|
14013
|
+
printEvolutionReport(r);
|
|
14014
|
+
if (r.violations.length === 0) {
|
|
14015
|
+
success("additive");
|
|
14016
|
+
return 0;
|
|
14017
|
+
}
|
|
14018
|
+
fail(`${r.violations.length} violation(s): not additive. Released copies would lose data or fail to upgrade.`);
|
|
14019
|
+
info(`Add a new field or table instead, and stop writing the old one (${code('human_edit: "open"')}).`);
|
|
14020
|
+
return 1;
|
|
14021
|
+
}
|
|
14022
|
+
async function publishEvolutionGate(projectRoot, strict) {
|
|
14023
|
+
let r;
|
|
14024
|
+
try {
|
|
14025
|
+
r = await runEvolutionCheck({ dir: projectRoot });
|
|
14026
|
+
} catch (err) {
|
|
14027
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
14028
|
+
if (strict) {
|
|
14029
|
+
fail(`Evolution check could not run (${msg}). Refusing with --strict. Nothing was published.`);
|
|
14030
|
+
return 1;
|
|
14031
|
+
}
|
|
14032
|
+
warn(`Evolution check skipped: ${msg}.`);
|
|
14033
|
+
return 0;
|
|
14034
|
+
}
|
|
14035
|
+
if (r.nothingReleased || r.violations.length === 0) return 0;
|
|
14036
|
+
printEvolutionReport(r, (s) => (strict ? fail : warn)(s));
|
|
14037
|
+
if (strict) {
|
|
14038
|
+
fail(
|
|
14039
|
+
`module.json is not an additive evolution of the released version (${r.violations.length} violation(s)). Refusing with --strict. Nothing was published.`
|
|
14040
|
+
);
|
|
14041
|
+
return 1;
|
|
14042
|
+
}
|
|
14043
|
+
warn(
|
|
14044
|
+
`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.`
|
|
14045
|
+
);
|
|
14046
|
+
return 0;
|
|
14047
|
+
}
|
|
14048
|
+
var EvolutionCheckError;
|
|
14049
|
+
var init_diff_check = __esm({
|
|
14050
|
+
"src/commands/module/diff-check.ts"() {
|
|
14051
|
+
init_api_client();
|
|
14052
|
+
init_manifest_evolution();
|
|
14053
|
+
init_output();
|
|
14054
|
+
EvolutionCheckError = class extends Error {
|
|
14055
|
+
};
|
|
14056
|
+
}
|
|
14057
|
+
});
|
|
14058
|
+
|
|
14059
|
+
// src/lib/manifest-reference-lint.ts
|
|
14060
|
+
function referenceFieldWarnings(manifest) {
|
|
14061
|
+
const tables = manifest?.owns?.tables;
|
|
14062
|
+
if (!Array.isArray(tables)) return [];
|
|
14063
|
+
const warnings = [];
|
|
14064
|
+
for (const table of tables) {
|
|
14065
|
+
if (typeof table?.slug !== "string" || !Array.isArray(table.fields)) continue;
|
|
14066
|
+
for (const field of table.fields) {
|
|
14067
|
+
if (typeof field?.slug !== "string" || typeof field.type !== "string") continue;
|
|
14068
|
+
if (field.slug === "id" || !REFERENCE_NAME.test(field.slug)) continue;
|
|
14069
|
+
if (!PLAIN_TYPES.has(field.type)) continue;
|
|
14070
|
+
warnings.push({
|
|
14071
|
+
table: table.slug,
|
|
14072
|
+
field: field.slug,
|
|
14073
|
+
type: field.type,
|
|
14074
|
+
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.`
|
|
14075
|
+
});
|
|
14076
|
+
}
|
|
14077
|
+
}
|
|
14078
|
+
return warnings;
|
|
14079
|
+
}
|
|
14080
|
+
var REFERENCE_NAME, PLAIN_TYPES;
|
|
14081
|
+
var init_manifest_reference_lint = __esm({
|
|
14082
|
+
"src/lib/manifest-reference-lint.ts"() {
|
|
14083
|
+
REFERENCE_NAME = /(?:_id|_ref|Id|Ref)$/;
|
|
14084
|
+
PLAIN_TYPES = /* @__PURE__ */ new Set(["number", "text", "autonumber"]);
|
|
14085
|
+
}
|
|
14086
|
+
});
|
|
13717
14087
|
function loadSchemaCommandContext(opts) {
|
|
13718
14088
|
const projectRoot = resolve(opts.dir ?? process.cwd());
|
|
13719
14089
|
const manifestPath2 = join(projectRoot, "module.json");
|
|
@@ -13786,6 +14156,89 @@ var init_dotenv_local = __esm({
|
|
|
13786
14156
|
}
|
|
13787
14157
|
});
|
|
13788
14158
|
|
|
14159
|
+
// src/lib/install-target.ts
|
|
14160
|
+
function keyDisplayPrefix(apiKey) {
|
|
14161
|
+
if (!apiKey.startsWith(KEY_SCHEME)) return null;
|
|
14162
|
+
const prefix = apiKey.slice(KEY_SCHEME.length, KEY_SCHEME.length + KEY_PREFIX_CHARS);
|
|
14163
|
+
return prefix.length === KEY_PREFIX_CHARS ? prefix : null;
|
|
14164
|
+
}
|
|
14165
|
+
async function keyBelongsToInstall(apiKey, installationId, options, now = /* @__PURE__ */ new Date()) {
|
|
14166
|
+
const prefix = keyDisplayPrefix(apiKey);
|
|
14167
|
+
if (prefix === null) return false;
|
|
14168
|
+
const { api_keys } = await listApiKeys(installationId, options);
|
|
14169
|
+
return api_keys.some(
|
|
14170
|
+
(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)
|
|
14171
|
+
);
|
|
14172
|
+
}
|
|
14173
|
+
function formatInstallTarget(id, detail) {
|
|
14174
|
+
return detail ? `\u2192 install #${id} (${detail})` : `\u2192 install #${id}`;
|
|
14175
|
+
}
|
|
14176
|
+
async function nameKeyTarget(args) {
|
|
14177
|
+
const { apiKey, keySource, entry, cfg, apiUrl, install } = args;
|
|
14178
|
+
const request = cfg ? { baseUrl: apiUrl, auth: { token: cfg.token }, signal: AbortSignal.timeout(LOOKUP_TIMEOUT_MS) } : null;
|
|
14179
|
+
if (install === void 0) {
|
|
14180
|
+
if (keySource !== "cached" || !entry) {
|
|
14181
|
+
info(
|
|
14182
|
+
`\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.`
|
|
14183
|
+
);
|
|
14184
|
+
return 0;
|
|
14185
|
+
}
|
|
14186
|
+
if (!request || entry.workspaceId === void 0) {
|
|
14187
|
+
info(formatInstallTarget(entry.installationId, "the cached dev install"));
|
|
14188
|
+
return 0;
|
|
14189
|
+
}
|
|
14190
|
+
try {
|
|
14191
|
+
const workspaces = await apiRequest("GET", "/api/workspaces", void 0, request);
|
|
14192
|
+
const ws = workspaces.find((w) => w.id === entry.workspaceId);
|
|
14193
|
+
info(formatInstallTarget(entry.installationId, `${ws ? ws.slug : `workspace #${entry.workspaceId}`}, local`));
|
|
14194
|
+
} catch {
|
|
14195
|
+
info(formatInstallTarget(entry.installationId));
|
|
14196
|
+
}
|
|
14197
|
+
return 0;
|
|
14198
|
+
}
|
|
14199
|
+
const source = keySource === "cached" ? "the cached key" : `${code("DASHWISE_API_KEY")} (from ${keySource === "environment" ? "the environment" : code(".env.local")})`;
|
|
14200
|
+
if (!request) {
|
|
14201
|
+
fail(
|
|
14202
|
+
`${code("--install")} looks the install up with your CLI login, and there is none. Run ${code("dashwise login")} first. Nothing was run.`
|
|
14203
|
+
);
|
|
14204
|
+
return 1;
|
|
14205
|
+
}
|
|
14206
|
+
let belongs;
|
|
14207
|
+
try {
|
|
14208
|
+
belongs = await keyBelongsToInstall(apiKey, install, request);
|
|
14209
|
+
} catch (err) {
|
|
14210
|
+
fail(
|
|
14211
|
+
`Could not read the keys of install #${install} to confirm ${source} (${describe(err)}). Refusing to run against an unconfirmed install. Nothing was run.`
|
|
14212
|
+
);
|
|
14213
|
+
return 1;
|
|
14214
|
+
}
|
|
14215
|
+
const cachedDev = entry?.installationId === install ? "the cached dev install, " : "";
|
|
14216
|
+
if (!belongs) {
|
|
14217
|
+
const other = entry && entry.installationId !== install && entry.apiKey === apiKey ? ` It is the cached key of install #${entry.installationId}.` : "";
|
|
14218
|
+
fail(`${source} is not a live key of install #${install}.${other} Nothing was run.`);
|
|
14219
|
+
info(
|
|
14220
|
+
`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.`
|
|
14221
|
+
);
|
|
14222
|
+
return 1;
|
|
14223
|
+
}
|
|
14224
|
+
info(formatInstallTarget(install, `${cachedDev}key checked`));
|
|
14225
|
+
return 0;
|
|
14226
|
+
}
|
|
14227
|
+
function describe(err) {
|
|
14228
|
+
if (err instanceof ApiError) return `${err.code || err.status} ${err.message}`;
|
|
14229
|
+
return err.message;
|
|
14230
|
+
}
|
|
14231
|
+
var KEY_PREFIX_CHARS, KEY_SCHEME, LOOKUP_TIMEOUT_MS;
|
|
14232
|
+
var init_install_target = __esm({
|
|
14233
|
+
"src/lib/install-target.ts"() {
|
|
14234
|
+
init_api_client();
|
|
14235
|
+
init_output();
|
|
14236
|
+
KEY_PREFIX_CHARS = 8;
|
|
14237
|
+
KEY_SCHEME = "dwk_";
|
|
14238
|
+
LOOKUP_TIMEOUT_MS = 1500;
|
|
14239
|
+
}
|
|
14240
|
+
});
|
|
14241
|
+
|
|
13789
14242
|
// src/commands/run.ts
|
|
13790
14243
|
var run_exports = {};
|
|
13791
14244
|
__export(run_exports, {
|
|
@@ -13798,6 +14251,10 @@ async function runCommand(cmdArgs, options) {
|
|
|
13798
14251
|
);
|
|
13799
14252
|
return 1;
|
|
13800
14253
|
}
|
|
14254
|
+
if (options.install !== void 0 && (!Number.isInteger(options.install) || options.install <= 0)) {
|
|
14255
|
+
fail(`--install expects a positive integer installation id (got "${options.install}").`);
|
|
14256
|
+
return 1;
|
|
14257
|
+
}
|
|
13801
14258
|
const projectRoot = resolve(options.dir ?? process.cwd());
|
|
13802
14259
|
let manifestSlug;
|
|
13803
14260
|
try {
|
|
@@ -13810,6 +14267,7 @@ async function runCommand(cmdArgs, options) {
|
|
|
13810
14267
|
fail(`module.json has no ${code("module.slug")} \u2014 the manifest is malformed.`);
|
|
13811
14268
|
return 1;
|
|
13812
14269
|
}
|
|
14270
|
+
const keyInShell = process.env.DASHWISE_API_KEY !== void 0;
|
|
13813
14271
|
hydrateEnvFromDotenvLocal(projectRoot);
|
|
13814
14272
|
const cfg = (() => {
|
|
13815
14273
|
try {
|
|
@@ -13820,8 +14278,19 @@ async function runCommand(cmdArgs, options) {
|
|
|
13820
14278
|
})();
|
|
13821
14279
|
const apiUrl = process.env.DASHWISE_API_URL ?? cfg?.apiUrl ?? "http://localhost:3000";
|
|
13822
14280
|
let apiKey = process.env.DASHWISE_API_KEY;
|
|
14281
|
+
let keySource = keyInShell ? "environment" : ".env.local";
|
|
14282
|
+
let entry = null;
|
|
13823
14283
|
if (!apiKey) {
|
|
13824
|
-
|
|
14284
|
+
entry = readModuleEntry(manifestSlug);
|
|
14285
|
+
if (entry && options.install !== void 0 && entry.installationId !== options.install) {
|
|
14286
|
+
fail(
|
|
14287
|
+
`No key for install #${options.install}: the cached ${code("dwk_")} key of ${code(manifestSlug)} belongs to install #${entry.installationId}. Nothing was run.`
|
|
14288
|
+
);
|
|
14289
|
+
info(
|
|
14290
|
+
`Set ${code("DASHWISE_API_KEY")} to a key of install #${options.install} (mint one with ${code(`dashwise api-keys create <name> --install ${options.install}`)}).`
|
|
14291
|
+
);
|
|
14292
|
+
return 1;
|
|
14293
|
+
}
|
|
13825
14294
|
if (cfg) {
|
|
13826
14295
|
const healed = await resolveModuleApiKey(
|
|
13827
14296
|
manifestSlug,
|
|
@@ -13832,6 +14301,7 @@ async function runCommand(cmdArgs, options) {
|
|
|
13832
14301
|
if (healed) entry = healed;
|
|
13833
14302
|
}
|
|
13834
14303
|
apiKey = entry?.apiKey;
|
|
14304
|
+
keySource = "cached";
|
|
13835
14305
|
}
|
|
13836
14306
|
if (!apiKey) {
|
|
13837
14307
|
fail(
|
|
@@ -13842,6 +14312,15 @@ async function runCommand(cmdArgs, options) {
|
|
|
13842
14312
|
);
|
|
13843
14313
|
return 1;
|
|
13844
14314
|
}
|
|
14315
|
+
const target = await nameKeyTarget({
|
|
14316
|
+
apiKey,
|
|
14317
|
+
keySource,
|
|
14318
|
+
entry: entry ?? readModuleEntry(manifestSlug),
|
|
14319
|
+
cfg,
|
|
14320
|
+
apiUrl,
|
|
14321
|
+
...options.install !== void 0 ? { install: options.install } : {}
|
|
14322
|
+
});
|
|
14323
|
+
if (target !== 0) return target;
|
|
13845
14324
|
const [cmd, ...rest] = cmdArgs;
|
|
13846
14325
|
info(
|
|
13847
14326
|
`${dim("run")} ${cmd} ${rest.join(" ")} ${dim(`(DASHWISE_API_URL=${apiUrl}, DASHWISE_API_KEY injected)`)}`
|
|
@@ -13871,6 +14350,7 @@ var init_run = __esm({
|
|
|
13871
14350
|
"src/commands/run.ts"() {
|
|
13872
14351
|
init_config();
|
|
13873
14352
|
init_dotenv_local();
|
|
14353
|
+
init_install_target();
|
|
13874
14354
|
init_manifest_io();
|
|
13875
14355
|
init_output();
|
|
13876
14356
|
init_dev();
|
|
@@ -14161,6 +14641,7 @@ async function schemaPushCommand(options) {
|
|
|
14161
14641
|
return surfaceSchemaPushError(err, slug);
|
|
14162
14642
|
}
|
|
14163
14643
|
printApplySummary(applied);
|
|
14644
|
+
for (const w of referenceFieldWarnings(manifest)) warn(w.message);
|
|
14164
14645
|
if (dwkAuth) {
|
|
14165
14646
|
try {
|
|
14166
14647
|
const { manifest: canonical } = await getInstallationSchema(dwkAuth);
|
|
@@ -14309,6 +14790,7 @@ var init_push = __esm({
|
|
|
14309
14790
|
init_manifest_io();
|
|
14310
14791
|
init_manifest_merge();
|
|
14311
14792
|
init_output();
|
|
14793
|
+
init_manifest_reference_lint();
|
|
14312
14794
|
}
|
|
14313
14795
|
});
|
|
14314
14796
|
|
|
@@ -15866,8 +16348,8 @@ function startMcpProxy(opts) {
|
|
|
15866
16348
|
let streamUnavailable = false;
|
|
15867
16349
|
let streamStarted = false;
|
|
15868
16350
|
let resolveDone;
|
|
15869
|
-
const done = new Promise((
|
|
15870
|
-
resolveDone =
|
|
16351
|
+
const done = new Promise((resolve16) => {
|
|
16352
|
+
resolveDone = resolve16;
|
|
15871
16353
|
});
|
|
15872
16354
|
const trace = (line) => {
|
|
15873
16355
|
if (verbose) log2(line);
|
|
@@ -16353,7 +16835,7 @@ async function revokeSession(endpointId) {
|
|
|
16353
16835
|
() => false
|
|
16354
16836
|
);
|
|
16355
16837
|
const timeout = new Promise(
|
|
16356
|
-
(
|
|
16838
|
+
(resolve16) => setTimeout(() => resolve16(false), REVOKE_TIMEOUT_MS).unref()
|
|
16357
16839
|
);
|
|
16358
16840
|
await Promise.race([attempt, timeout]);
|
|
16359
16841
|
}
|
|
@@ -16531,7 +17013,7 @@ async function mcpAddCommand(options = {}) {
|
|
|
16531
17013
|
fail(`Could not run \`claude\`: ${target.refused} Run the command above yourself.`);
|
|
16532
17014
|
return 1;
|
|
16533
17015
|
}
|
|
16534
|
-
return await new Promise((
|
|
17016
|
+
return await new Promise((resolve16) => {
|
|
16535
17017
|
const child = spawn(target.command, target.args, { shell: target.shell, stdio: "inherit" });
|
|
16536
17018
|
let settled = false;
|
|
16537
17019
|
child.on("error", (err) => {
|
|
@@ -16541,16 +17023,16 @@ async function mcpAddCommand(options = {}) {
|
|
|
16541
17023
|
fail(
|
|
16542
17024
|
`Claude Code is not on your PATH, so nothing was registered. Run the command above once ${code("claude")} is installed.`
|
|
16543
17025
|
);
|
|
16544
|
-
|
|
17026
|
+
resolve16(1);
|
|
16545
17027
|
return;
|
|
16546
17028
|
}
|
|
16547
17029
|
fail(`Could not run \`claude\`: ${err.message}`);
|
|
16548
|
-
|
|
17030
|
+
resolve16(1);
|
|
16549
17031
|
});
|
|
16550
17032
|
child.on("close", (codeOut) => {
|
|
16551
17033
|
if (settled) return;
|
|
16552
17034
|
settled = true;
|
|
16553
|
-
|
|
17035
|
+
resolve16(codeOut ?? 1);
|
|
16554
17036
|
});
|
|
16555
17037
|
});
|
|
16556
17038
|
}
|
|
@@ -16669,10 +17151,10 @@ function upgradeCommand(plugin, marketplace) {
|
|
|
16669
17151
|
var PLUGIN_NAME = "dashwise";
|
|
16670
17152
|
var MARKETPLACE_NAME = "dashwise";
|
|
16671
17153
|
var DEFAULT_MARKETPLACE_SOURCE = "Intuize/dashwise-cli";
|
|
16672
|
-
var defaultRunner = (cmd, args, opts) => new Promise((
|
|
17154
|
+
var defaultRunner = (cmd, args, opts) => new Promise((resolve16) => {
|
|
16673
17155
|
const target = spawnTarget(cmd, args);
|
|
16674
17156
|
if ("refused" in target) {
|
|
16675
|
-
|
|
17157
|
+
resolve16({ code: null, stdout: "", stderr: target.refused, missing: false });
|
|
16676
17158
|
return;
|
|
16677
17159
|
}
|
|
16678
17160
|
const child = spawn(target.command, target.args, {
|
|
@@ -16687,7 +17169,7 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolve15) => {
|
|
|
16687
17169
|
child.on("error", (err) => {
|
|
16688
17170
|
if (settled) return;
|
|
16689
17171
|
settled = true;
|
|
16690
|
-
|
|
17172
|
+
resolve16({
|
|
16691
17173
|
code: null,
|
|
16692
17174
|
stdout,
|
|
16693
17175
|
stderr: stderr || err.message,
|
|
@@ -16697,7 +17179,7 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolve15) => {
|
|
|
16697
17179
|
child.on("close", (exitCode) => {
|
|
16698
17180
|
if (settled) return;
|
|
16699
17181
|
settled = true;
|
|
16700
|
-
|
|
17182
|
+
resolve16({ code: exitCode, stdout, stderr, missing: false });
|
|
16701
17183
|
});
|
|
16702
17184
|
});
|
|
16703
17185
|
function readSkillsOnDisk() {
|
|
@@ -17938,6 +18420,7 @@ function judgeGeneratorStamp(stamp, opts = {}) {
|
|
|
17938
18420
|
// src/commands/module/generate.ts
|
|
17939
18421
|
init_sdk_version();
|
|
17940
18422
|
init_output();
|
|
18423
|
+
init_declared_secrets();
|
|
17941
18424
|
function outputDir(projectRoot) {
|
|
17942
18425
|
return join(projectRoot, "node_modules", "@dashai", "generated");
|
|
17943
18426
|
}
|
|
@@ -18014,6 +18497,9 @@ async function moduleGenerateCommand(options) {
|
|
|
18014
18497
|
info(`Hash: ${dim(res.contentHash)}`);
|
|
18015
18498
|
if (verdict.note !== null && declaresUses(manifest)) info(verdict.note);
|
|
18016
18499
|
warnIfSdkBelowUsesFloor(projectRoot, manifest);
|
|
18500
|
+
reportSecretLines(
|
|
18501
|
+
syncEnvLocalSecretLines(projectRoot, declaredSecretNames(manifest))
|
|
18502
|
+
);
|
|
18017
18503
|
return 0;
|
|
18018
18504
|
}
|
|
18019
18505
|
function declaresUses(manifest) {
|
|
@@ -18272,6 +18758,101 @@ init_init();
|
|
|
18272
18758
|
// src/commands/module/install.ts
|
|
18273
18759
|
init_api_client();
|
|
18274
18760
|
init_config();
|
|
18761
|
+
|
|
18762
|
+
// src/lib/agent-session.ts
|
|
18763
|
+
var MARKERS = [
|
|
18764
|
+
["CLAUDECODE", (v) => v === "1"],
|
|
18765
|
+
["CLAUDE_CODE_REMOTE", (v) => v === "true"],
|
|
18766
|
+
["CODEX_SANDBOX", (v) => v.length > 0],
|
|
18767
|
+
["CODEX_SANDBOX_NETWORK_DISABLED", (v) => v.length > 0],
|
|
18768
|
+
["CURSOR_AGENT", (v) => v.length > 0],
|
|
18769
|
+
["DW_AGENT", (v) => v.length > 0],
|
|
18770
|
+
["DASHWISE_AGENT_SESSION", (v) => v === "1" || v === "true"]
|
|
18771
|
+
];
|
|
18772
|
+
function agentSessionMarker(env = process.env) {
|
|
18773
|
+
for (const [name, test] of MARKERS) {
|
|
18774
|
+
const value = env[name];
|
|
18775
|
+
if (value !== void 0 && test(value)) return name;
|
|
18776
|
+
}
|
|
18777
|
+
return null;
|
|
18778
|
+
}
|
|
18779
|
+
|
|
18780
|
+
// src/lib/drift-preview.ts
|
|
18781
|
+
init_api_client();
|
|
18782
|
+
function redactedConsumer(redacted) {
|
|
18783
|
+
switch (redacted) {
|
|
18784
|
+
case "private_copy":
|
|
18785
|
+
return "someone's private copy";
|
|
18786
|
+
case "other_team_copy":
|
|
18787
|
+
return "another team's copy";
|
|
18788
|
+
case "development_copy":
|
|
18789
|
+
return "a development copy you cannot see";
|
|
18790
|
+
default:
|
|
18791
|
+
return "a copy you cannot see";
|
|
18792
|
+
}
|
|
18793
|
+
}
|
|
18794
|
+
var WIDEN_WORDS = {
|
|
18795
|
+
uses_need_added: "a new table it reads from another module",
|
|
18796
|
+
uses_ops_widened: "more operations on a table it reads",
|
|
18797
|
+
uses_fields_added: "more fields of a table it reads",
|
|
18798
|
+
dependency_added: "a new or wider dependency",
|
|
18799
|
+
exposes_widened: "more of what it exposes to other modules",
|
|
18800
|
+
permissions_added: "more permissions",
|
|
18801
|
+
roles_widened: "more roles or permission keys",
|
|
18802
|
+
outbound_added: "new outbound APIs (data can leave)",
|
|
18803
|
+
secrets_added: "new secrets",
|
|
18804
|
+
public_access_enabled: "public access"
|
|
18805
|
+
};
|
|
18806
|
+
function describeWidening(widening) {
|
|
18807
|
+
return widening.map((w) => WIDEN_WORDS[w] ?? w).join(", ");
|
|
18808
|
+
}
|
|
18809
|
+
async function getUpgradePreview(installationId, version, options = {}) {
|
|
18810
|
+
return apiRequest(
|
|
18811
|
+
"GET",
|
|
18812
|
+
`/api/installations/${installationId}/upgrade-preview?version=${encodeURIComponent(version)}`,
|
|
18813
|
+
void 0,
|
|
18814
|
+
options
|
|
18815
|
+
);
|
|
18816
|
+
}
|
|
18817
|
+
function count(n, unit) {
|
|
18818
|
+
if (n === null) return `${unit}s not counted`;
|
|
18819
|
+
return `${n.toLocaleString("en-US")} ${unit}${n === 1 ? "" : "s"}`;
|
|
18820
|
+
}
|
|
18821
|
+
function describeDrift(drift) {
|
|
18822
|
+
const lines = [];
|
|
18823
|
+
const d = drift.details;
|
|
18824
|
+
const removedTables = d?.tables_to_remove ?? drift.tables_to_remove.map((t) => ({ table: t, name: t, rows: null }));
|
|
18825
|
+
for (const t of removedTables) {
|
|
18826
|
+
lines.push({
|
|
18827
|
+
loses: t.rows !== 0,
|
|
18828
|
+
text: `Moves table ${t.table} (${count(t.rows, "row")}) to the Trash`
|
|
18829
|
+
});
|
|
18830
|
+
}
|
|
18831
|
+
const removedFields = d?.fields_to_remove ?? drift.fields_to_remove.map((f) => ({ ...f, name: f.field, values: null }));
|
|
18832
|
+
for (const f of removedFields) {
|
|
18833
|
+
lines.push({
|
|
18834
|
+
loses: f.values !== 0,
|
|
18835
|
+
text: `Moves field ${f.table}.${f.field} (${count(f.values, "value")}) to the Trash`
|
|
18836
|
+
});
|
|
18837
|
+
}
|
|
18838
|
+
const retyped = d?.fields_to_retype ?? (drift.fields_to_retype ?? []).map((f) => ({ ...f, name: f.field, values: null }));
|
|
18839
|
+
for (const f of retyped) {
|
|
18840
|
+
lines.push({
|
|
18841
|
+
loses: f.values !== 0,
|
|
18842
|
+
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)`
|
|
18843
|
+
});
|
|
18844
|
+
}
|
|
18845
|
+
lines.sort((a, b) => Number(b.loses) - Number(a.loses));
|
|
18846
|
+
for (const t of drift.tables_to_create) {
|
|
18847
|
+
lines.push({ loses: false, text: `Adds table ${t}` });
|
|
18848
|
+
}
|
|
18849
|
+
for (const f of drift.fields_to_create) {
|
|
18850
|
+
lines.push({ loses: false, text: `Adds field ${f.table}.${f.field}` });
|
|
18851
|
+
}
|
|
18852
|
+
return lines;
|
|
18853
|
+
}
|
|
18854
|
+
|
|
18855
|
+
// src/commands/module/install.ts
|
|
18275
18856
|
init_manifest_io();
|
|
18276
18857
|
|
|
18277
18858
|
// src/lib/module-lifecycle.ts
|
|
@@ -18450,18 +19031,31 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
18450
19031
|
);
|
|
18451
19032
|
}
|
|
18452
19033
|
}
|
|
18453
|
-
|
|
18454
|
-
|
|
19034
|
+
let preview;
|
|
19035
|
+
let lossy = false;
|
|
19036
|
+
let lossAccepted = false;
|
|
19037
|
+
if (existing) {
|
|
19038
|
+
const rerun = rerunLine(mod.slug, workspace2.slug, targetVersion, options, devInstallToReplace !== void 0);
|
|
19039
|
+
const previewed = await previewUpgrade(existing.id, targetVersion, options, auth, rerun, devInstallToReplace?.id);
|
|
19040
|
+
if (typeof previewed === "number") return previewed;
|
|
19041
|
+
preview = previewed.preview;
|
|
19042
|
+
lossy = previewed.lossy;
|
|
19043
|
+
lossAccepted = previewed.accepted;
|
|
19044
|
+
}
|
|
19045
|
+
const askForLoss = lossy && !lossAccepted;
|
|
19046
|
+
if (askForLoss || !options.yes && process.stdout.isTTY && !confirmedViaPicker) {
|
|
19047
|
+
const lossClause = lossy ? " It moves the data listed above to the Trash or converts it." : "";
|
|
19048
|
+
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
19049
|
const answer = await select({
|
|
18456
19050
|
message,
|
|
18457
19051
|
options: [
|
|
18458
19052
|
{
|
|
18459
19053
|
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"
|
|
19054
|
+
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
19055
|
},
|
|
18462
19056
|
{ value: "cancel", label: "No, abort" }
|
|
18463
19057
|
],
|
|
18464
|
-
initialValue: devInstallToReplace ? "cancel" : "go"
|
|
19058
|
+
initialValue: devInstallToReplace || lossy ? "cancel" : "go"
|
|
18465
19059
|
});
|
|
18466
19060
|
if (isCancel(answer) || answer === "cancel") {
|
|
18467
19061
|
fail("Aborted. Nothing was changed.");
|
|
@@ -18483,7 +19077,9 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
18483
19077
|
workspaceId: workspace2.id,
|
|
18484
19078
|
version: targetVersion,
|
|
18485
19079
|
...existing ? { installation_id: existing.id } : {},
|
|
18486
|
-
...options.force !== void 0 ? { force: options.force } : {}
|
|
19080
|
+
...options.force !== void 0 ? { force: options.force } : {},
|
|
19081
|
+
// P5-08: confirms exactly the delta that was shown (409 DRIFT_CHANGED otherwise).
|
|
19082
|
+
...preview ? { drift_hash: preview.drift_hash } : {}
|
|
18487
19083
|
},
|
|
18488
19084
|
auth
|
|
18489
19085
|
);
|
|
@@ -18535,6 +19131,9 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
18535
19131
|
was_upgrade: result.was_upgrade,
|
|
18536
19132
|
public_url: publicUrl,
|
|
18537
19133
|
verified,
|
|
19134
|
+
// P5-08: the confirmed preview; `null` for a fresh install (nothing to preview).
|
|
19135
|
+
drift_hash: preview?.drift_hash ?? null,
|
|
19136
|
+
data_loss: preview ? lossy : null,
|
|
18538
19137
|
// `null` when the binding surface was unreachable; `[]` when the
|
|
18539
19138
|
// module declares no slots. The two are not the same thing.
|
|
18540
19139
|
uses: readiness === null ? null : usesInstallJson(readiness),
|
|
@@ -18703,6 +19302,19 @@ function surfaceInstallError(err, slug, workspaceSlug, devInstallReplaced) {
|
|
|
18703
19302
|
Ask the module's author to invite this workspace (or open the install gate), then retry.`
|
|
18704
19303
|
);
|
|
18705
19304
|
break;
|
|
19305
|
+
case "DRIFT_CHANGED":
|
|
19306
|
+
fail(
|
|
19307
|
+
`Install ${code(slug)} changed since its upgrade preview, so the confirmed change no longer matches. Nothing was changed.
|
|
19308
|
+
Re-run ${code("dashwise module install")} to see the new preview.`
|
|
19309
|
+
);
|
|
19310
|
+
break;
|
|
19311
|
+
case "DATA_LOSS_CONFIRM_REQUIRED":
|
|
19312
|
+
case "UPGRADE_CONFIRM_REQUIRED":
|
|
19313
|
+
fail(
|
|
19314
|
+
`The platform asks for a confirmed preview before this upgrade (${err.code}). Nothing was changed.
|
|
19315
|
+
Re-run ${code("dashwise module install")}: it previews the upgrade and sends the confirmation.`
|
|
19316
|
+
);
|
|
19317
|
+
break;
|
|
18706
19318
|
case "UPGRADE_BREAKS_CONSUMERS":
|
|
18707
19319
|
fail(
|
|
18708
19320
|
`Upgrading ${code(slug)} would break modules that consume its current schema.
|
|
@@ -18752,6 +19364,75 @@ function usesInstallJson(readiness) {
|
|
|
18752
19364
|
}))
|
|
18753
19365
|
};
|
|
18754
19366
|
}
|
|
19367
|
+
async function previewUpgrade(installationId, targetVersion, options, auth, rerun, devInstallToReplaceId) {
|
|
19368
|
+
let preview;
|
|
19369
|
+
try {
|
|
19370
|
+
preview = await getUpgradePreview(installationId, targetVersion, auth);
|
|
19371
|
+
} catch (err) {
|
|
19372
|
+
const why = err instanceof ApiError ? `${err.code || err.status} ${err.message}` : err.message;
|
|
19373
|
+
const checkNote = options.acceptDataLoss !== void 0 ? `, so ${code("--accept-data-loss")} cannot be checked` : "";
|
|
19374
|
+
fail(
|
|
19375
|
+
`Could not preview what upgrading install #${installationId} to ${code(`v${targetVersion}`)} does to its data (${why})${checkNote}. Nothing was changed.`
|
|
19376
|
+
);
|
|
19377
|
+
info(`An upgrade is never applied without its preview; retry when the platform answers.`);
|
|
19378
|
+
return 1;
|
|
19379
|
+
}
|
|
19380
|
+
const lines = describeDrift(preview.drift);
|
|
19381
|
+
const lossy = lines.some((l) => l.loses);
|
|
19382
|
+
const from = preview.from_version ? `v${preview.from_version}` : "its current version";
|
|
19383
|
+
if (lines.length === 0) {
|
|
19384
|
+
info(`Upgrading install #${installationId} from ${from} to v${preview.to_version} changes no table or field.`);
|
|
19385
|
+
} else {
|
|
19386
|
+
info(`What upgrading install #${installationId} from ${from} to v${preview.to_version} does to its data:`);
|
|
19387
|
+
for (const line of lines) {
|
|
19388
|
+
(line.loses ? warn : info)(` ${line.text}`);
|
|
19389
|
+
}
|
|
19390
|
+
}
|
|
19391
|
+
const broken = preview.breaks_consumers ?? [];
|
|
19392
|
+
if (broken.length > 0) {
|
|
19393
|
+
const names = broken.map(
|
|
19394
|
+
(c2) => c2.module_slug ? `${c2.module_slug}${c2.installation_id ? ` #${c2.installation_id}` : ""}` : redactedConsumer(c2.redacted)
|
|
19395
|
+
);
|
|
19396
|
+
warn(` Breaks ${broken.length} module${broken.length === 1 ? "" : "s"} that read${broken.length === 1 ? "s" : ""} this one: ${names.join(", ")}`);
|
|
19397
|
+
}
|
|
19398
|
+
if (preview.approval_required) {
|
|
19399
|
+
const asks = preview.widening && preview.widening.length > 0 ? `: it asks for ${describeWidening(preview.widening)}` : "";
|
|
19400
|
+
warn(` This update waits for the module author's approval${asks}.`);
|
|
19401
|
+
}
|
|
19402
|
+
if (options.acceptDataLoss !== void 0 && options.acceptDataLoss !== preview.drift_hash) {
|
|
19403
|
+
fail(
|
|
19404
|
+
`${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.`
|
|
19405
|
+
);
|
|
19406
|
+
info(`To accept the change listed above, a person re-runs:
|
|
19407
|
+
${rerun(preview.drift_hash)}`);
|
|
19408
|
+
return 1;
|
|
19409
|
+
}
|
|
19410
|
+
const accepted = lossy && options.acceptDataLoss === preview.drift_hash;
|
|
19411
|
+
if (lossy && !accepted) {
|
|
19412
|
+
const yesNote = options.yes ? ` ${code("--yes")} does not accept data loss.` : "";
|
|
19413
|
+
const marker = agentSessionMarker();
|
|
19414
|
+
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;
|
|
19415
|
+
if (why) {
|
|
19416
|
+
const alsoDev = devInstallToReplaceId !== void 0 ? ` and destroys dev install #${devInstallToReplaceId} first` : "";
|
|
19417
|
+
fail(`This upgrade loses the data listed above${alsoDev}, and ${why}.${yesNote} Refused. Nothing was changed.`);
|
|
19418
|
+
info(`To accept exactly that loss, a person re-runs:
|
|
19419
|
+
${rerun(preview.drift_hash)}`);
|
|
19420
|
+
return 1;
|
|
19421
|
+
}
|
|
19422
|
+
}
|
|
19423
|
+
return { preview, lossy, accepted };
|
|
19424
|
+
}
|
|
19425
|
+
function rerunLine(slug, workspaceSlug, targetVersion, options, replacingDevInstall) {
|
|
19426
|
+
return (hash) => [
|
|
19427
|
+
"dashwise module install",
|
|
19428
|
+
slug,
|
|
19429
|
+
`--workspace ${workspaceSlug}`,
|
|
19430
|
+
`--version ${targetVersion}`,
|
|
19431
|
+
...options.force ? ["--force"] : [],
|
|
19432
|
+
...replacingDevInstall ? ["--replace-dev-install"] : [],
|
|
19433
|
+
`--accept-data-loss ${hash}`
|
|
19434
|
+
].join(" ");
|
|
19435
|
+
}
|
|
18755
19436
|
|
|
18756
19437
|
// src/commands/module/list.ts
|
|
18757
19438
|
init_api_client();
|
|
@@ -19121,6 +19802,7 @@ function formatBytes(n) {
|
|
|
19121
19802
|
}
|
|
19122
19803
|
|
|
19123
19804
|
// src/commands/module/publish.ts
|
|
19805
|
+
init_diff_check();
|
|
19124
19806
|
var VALID_BUMP = /* @__PURE__ */ new Set(["patch", "minor", "major"]);
|
|
19125
19807
|
var PACKED_FILE_LIST_CAP = 20;
|
|
19126
19808
|
function packedFileLines(files, cap = PACKED_FILE_LIST_CAP) {
|
|
@@ -19207,6 +19889,10 @@ async function modulePublishCommand(options) {
|
|
|
19207
19889
|
info(
|
|
19208
19890
|
`Module: ${code(mod.slug)} (id ${mod.id})${mod.current_version ? `, current ${code(`v${mod.current_version}`)}` : dim(", no published version yet")}`
|
|
19209
19891
|
);
|
|
19892
|
+
if (mod.current_version) {
|
|
19893
|
+
const gate = await publishEvolutionGate(projectRoot, options.strict === true);
|
|
19894
|
+
if (gate !== 0) return gate;
|
|
19895
|
+
}
|
|
19210
19896
|
let tarball;
|
|
19211
19897
|
try {
|
|
19212
19898
|
tarball = await packTarball({
|
|
@@ -19798,7 +20484,7 @@ async function moduleReleaseCommand(slugArg, options) {
|
|
|
19798
20484
|
}
|
|
19799
20485
|
async function wait() {
|
|
19800
20486
|
const remaining = Math.max(0, deadline - Date.now());
|
|
19801
|
-
await new Promise((
|
|
20487
|
+
await new Promise((resolve16) => setTimeout(resolve16, Math.min(options.pollIntervalMs ?? 4e3, remaining)));
|
|
19802
20488
|
}
|
|
19803
20489
|
}
|
|
19804
20490
|
|
|
@@ -20220,6 +20906,7 @@ async function modulePullDataCommand(options) {
|
|
|
20220
20906
|
// src/commands/module/validate.ts
|
|
20221
20907
|
init_api_client();
|
|
20222
20908
|
init_output();
|
|
20909
|
+
init_manifest_reference_lint();
|
|
20223
20910
|
async function moduleValidateCommand(options) {
|
|
20224
20911
|
const projectRoot = resolve(options.dir ?? process.cwd());
|
|
20225
20912
|
const manifestPath2 = join(projectRoot, "module.json");
|
|
@@ -20244,6 +20931,7 @@ async function moduleValidateCommand(options) {
|
|
|
20244
20931
|
);
|
|
20245
20932
|
if (res.ok) {
|
|
20246
20933
|
success("Manifest is valid.");
|
|
20934
|
+
for (const w of referenceFieldWarnings(manifest)) warn(w.message);
|
|
20247
20935
|
return 0;
|
|
20248
20936
|
}
|
|
20249
20937
|
fail(`Unexpected response shape: ${JSON.stringify(res)}`);
|
|
@@ -22646,6 +23334,23 @@ init_api_client();
|
|
|
22646
23334
|
init_config();
|
|
22647
23335
|
init_manifest_io();
|
|
22648
23336
|
init_output();
|
|
23337
|
+
init_declared_secrets();
|
|
23338
|
+
var SECRETS_NOTE = "# Declared secrets (runtime.secrets[]): set each value on your host; Dashwise never exports a secret value.";
|
|
23339
|
+
function secretNameLines(names, format) {
|
|
23340
|
+
const lines = names.map((name) => {
|
|
23341
|
+
switch (format) {
|
|
23342
|
+
case "env":
|
|
23343
|
+
return `# ${name}=`;
|
|
23344
|
+
case "railway":
|
|
23345
|
+
return `# ${name}: railway variables set ${name}='<value>'`;
|
|
23346
|
+
case "vercel":
|
|
23347
|
+
return `# ${name}: vercel env add ${name} production`;
|
|
23348
|
+
case "fly":
|
|
23349
|
+
return `# ${name}: fly secrets set ${name}='<value>'`;
|
|
23350
|
+
}
|
|
23351
|
+
});
|
|
23352
|
+
return [SECRETS_NOTE, ...lines].join("\n");
|
|
23353
|
+
}
|
|
22649
23354
|
var PLACEHOLDER = "<run-`dashwise-api-keys-create-<name>`-and-paste-here>";
|
|
22650
23355
|
async function envExportCommand(options) {
|
|
22651
23356
|
const format = options.format ?? "env";
|
|
@@ -22736,7 +23441,19 @@ async function envExportCommand(options) {
|
|
|
22736
23441
|
...apiKeyComment !== null ? { comment: apiKeyComment } : {}
|
|
22737
23442
|
}
|
|
22738
23443
|
];
|
|
23444
|
+
const secretNames = options.withSecretNames ? declaredSecretNames(manifest) : [];
|
|
22739
23445
|
process.stdout.write(render(entries, format));
|
|
23446
|
+
if (secretNames.length > 0) {
|
|
23447
|
+
if (format === "json") {
|
|
23448
|
+
info(
|
|
23449
|
+
`Declared secrets to set on your host (Dashwise never exports a secret value): ${secretNames.join(", ")}`
|
|
23450
|
+
);
|
|
23451
|
+
} else {
|
|
23452
|
+
process.stdout.write(`
|
|
23453
|
+
|
|
23454
|
+
${secretNameLines(secretNames, format)}`);
|
|
23455
|
+
}
|
|
23456
|
+
}
|
|
22740
23457
|
if (format !== "json") process.stdout.write("\n");
|
|
22741
23458
|
if (apiKey === PLACEHOLDER) {
|
|
22742
23459
|
info("");
|
|
@@ -22792,6 +23509,7 @@ init_config();
|
|
|
22792
23509
|
init_dotenv_local();
|
|
22793
23510
|
init_manifest_io();
|
|
22794
23511
|
init_output();
|
|
23512
|
+
init_install_target();
|
|
22795
23513
|
init_dev();
|
|
22796
23514
|
async function queryCommand(options) {
|
|
22797
23515
|
const projectRoot = resolve(options.dir ?? process.cwd());
|
|
@@ -22814,17 +23532,25 @@ async function queryCommand(options) {
|
|
|
22814
23532
|
);
|
|
22815
23533
|
return 1;
|
|
22816
23534
|
}
|
|
23535
|
+
const keyInShell = process.env.DASHWISE_API_KEY !== void 0;
|
|
22817
23536
|
hydrateEnvFromDotenvLocal(projectRoot);
|
|
23537
|
+
const cfg = (() => {
|
|
23538
|
+
try {
|
|
23539
|
+
return readConfig();
|
|
23540
|
+
} catch {
|
|
23541
|
+
return null;
|
|
23542
|
+
}
|
|
23543
|
+
})();
|
|
23544
|
+
const apiUrl = process.env.DASHWISE_API_URL ?? cfg?.apiUrl ?? "http://localhost:3000";
|
|
23545
|
+
let keySource = keyInShell ? "environment" : ".env.local";
|
|
23546
|
+
let entry = manifestSlug ? readModuleEntry(manifestSlug) : null;
|
|
22818
23547
|
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);
|
|
23548
|
+
if (entry && options.install !== void 0 && entry.installationId !== options.install) {
|
|
23549
|
+
fail(
|
|
23550
|
+
`No key for install #${options.install}: the cached ${code("dwk_")} key of ${code(manifestSlug)} belongs to install #${entry.installationId}. Nothing was run.`
|
|
23551
|
+
);
|
|
23552
|
+
return 1;
|
|
23553
|
+
}
|
|
22828
23554
|
if (cfg) {
|
|
22829
23555
|
const healed = await resolveModuleApiKey(
|
|
22830
23556
|
manifestSlug,
|
|
@@ -22836,11 +23562,26 @@ async function queryCommand(options) {
|
|
|
22836
23562
|
}
|
|
22837
23563
|
if (entry?.apiKey) {
|
|
22838
23564
|
process.env.DASHWISE_API_KEY = entry.apiKey;
|
|
23565
|
+
keySource = "cached";
|
|
22839
23566
|
if (!process.env.DASHWISE_API_URL) {
|
|
22840
23567
|
process.env.DASHWISE_API_URL = apiUrl;
|
|
22841
23568
|
}
|
|
22842
23569
|
}
|
|
22843
23570
|
}
|
|
23571
|
+
if (process.env.DASHWISE_API_KEY) {
|
|
23572
|
+
const target = await nameKeyTarget({
|
|
23573
|
+
apiKey: process.env.DASHWISE_API_KEY,
|
|
23574
|
+
keySource,
|
|
23575
|
+
entry,
|
|
23576
|
+
cfg,
|
|
23577
|
+
apiUrl: process.env.DASHWISE_API_URL ?? apiUrl,
|
|
23578
|
+
...options.install !== void 0 ? { install: options.install } : {}
|
|
23579
|
+
});
|
|
23580
|
+
if (target !== 0) return target;
|
|
23581
|
+
} else if (options.install !== void 0) {
|
|
23582
|
+
fail(`No ${code("dwk_")} key to check against install #${options.install}. Set ${code("DASHWISE_API_KEY")}. Nothing was run.`);
|
|
23583
|
+
return 1;
|
|
23584
|
+
}
|
|
22844
23585
|
const modes = [
|
|
22845
23586
|
options.inline !== void 0 ? "inline" : null,
|
|
22846
23587
|
options.file !== void 0 ? "file" : null,
|
|
@@ -23206,7 +23947,7 @@ function formatTimestamp(iso) {
|
|
|
23206
23947
|
return iso.slice(0, 19).replace("T", " ");
|
|
23207
23948
|
}
|
|
23208
23949
|
function sleep2(ms) {
|
|
23209
|
-
return new Promise((
|
|
23950
|
+
return new Promise((resolve16) => setTimeout(resolve16, ms));
|
|
23210
23951
|
}
|
|
23211
23952
|
|
|
23212
23953
|
// src/commands/module/action-log.ts
|
|
@@ -23308,7 +24049,7 @@ async function runTail2(installationId, options) {
|
|
|
23308
24049
|
}
|
|
23309
24050
|
dim(`(tail: transient error \u2014 ${err.message})`);
|
|
23310
24051
|
}
|
|
23311
|
-
await new Promise((
|
|
24052
|
+
await new Promise((resolve16) => setTimeout(resolve16, POLL_INTERVAL_MS2));
|
|
23312
24053
|
}
|
|
23313
24054
|
}
|
|
23314
24055
|
function printTable(rows) {
|
|
@@ -23693,7 +24434,7 @@ async function sleep3(ms, aborted) {
|
|
|
23693
24434
|
const slice = 100;
|
|
23694
24435
|
let waited = 0;
|
|
23695
24436
|
while (waited < ms && !aborted()) {
|
|
23696
|
-
await new Promise((
|
|
24437
|
+
await new Promise((resolve16) => setTimeout(resolve16, Math.min(slice, ms - waited)));
|
|
23697
24438
|
waited += slice;
|
|
23698
24439
|
}
|
|
23699
24440
|
}
|
|
@@ -24524,12 +25265,29 @@ moduleCmd.command("list").description("List modules you own (or `--all` for owne
|
|
|
24524
25265
|
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
24525
25266
|
});
|
|
24526
25267
|
});
|
|
24527
|
-
moduleCmd.command("diff").description(
|
|
24528
|
-
|
|
24529
|
-
|
|
24530
|
-
|
|
24531
|
-
|
|
24532
|
-
});
|
|
25268
|
+
moduleCmd.command("diff").description(
|
|
25269
|
+
"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."
|
|
25270
|
+
).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(
|
|
25271
|
+
async (cmdOpts) => {
|
|
25272
|
+
if (cmdOpts.check) {
|
|
25273
|
+
const { moduleDiffCheckCommand: moduleDiffCheckCommand2 } = await Promise.resolve().then(() => (init_diff_check(), diff_check_exports));
|
|
25274
|
+
process.exitCode = await moduleDiffCheckCommand2({
|
|
25275
|
+
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
25276
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {},
|
|
25277
|
+
...cmdOpts.base !== void 0 ? { base: cmdOpts.base } : {},
|
|
25278
|
+
...cmdOpts.baseFile !== void 0 ? { baseFile: cmdOpts.baseFile } : {},
|
|
25279
|
+
...cmdOpts.head !== void 0 ? { head: cmdOpts.head } : {},
|
|
25280
|
+
// commander maps `--no-history` to `history === false`.
|
|
25281
|
+
...cmdOpts.history === false ? { noHistory: true } : {}
|
|
25282
|
+
});
|
|
25283
|
+
return;
|
|
25284
|
+
}
|
|
25285
|
+
process.exitCode = await moduleDiffCommand({
|
|
25286
|
+
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
25287
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
25288
|
+
});
|
|
25289
|
+
}
|
|
25290
|
+
);
|
|
24533
25291
|
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
25292
|
process.exitCode = await modulePullDataCommand({
|
|
24535
25293
|
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
@@ -24681,6 +25439,10 @@ program.command("query").description(
|
|
|
24681
25439
|
).option(
|
|
24682
25440
|
"--stream",
|
|
24683
25441
|
"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."
|
|
25442
|
+
).option(
|
|
25443
|
+
"--install <id>",
|
|
25444
|
+
"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.",
|
|
25445
|
+
parseIntOption
|
|
24684
25446
|
).action(
|
|
24685
25447
|
async (cmdOpts) => {
|
|
24686
25448
|
process.exitCode = await queryCommand({
|
|
@@ -24690,16 +25452,22 @@ program.command("query").description(
|
|
|
24690
25452
|
...cmdOpts.repl !== void 0 ? { repl: cmdOpts.repl } : {},
|
|
24691
25453
|
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {},
|
|
24692
25454
|
...cmdOpts.explain !== void 0 ? { explain: cmdOpts.explain } : {},
|
|
24693
|
-
...cmdOpts.stream !== void 0 ? { stream: cmdOpts.stream } : {}
|
|
25455
|
+
...cmdOpts.stream !== void 0 ? { stream: cmdOpts.stream } : {},
|
|
25456
|
+
...cmdOpts.install !== void 0 ? { install: cmdOpts.install } : {}
|
|
24694
25457
|
});
|
|
24695
25458
|
}
|
|
24696
25459
|
);
|
|
24697
25460
|
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).").
|
|
25461
|
+
"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`."
|
|
25462
|
+
).option("--dir <path>", "Project root containing `module.json` (default: current directory).").option(
|
|
25463
|
+
"--install <id>",
|
|
25464
|
+
"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.",
|
|
25465
|
+
parseIntOption
|
|
25466
|
+
).action(async (cmdArgs, cmdOpts) => {
|
|
24700
25467
|
const { runCommand: runCommand2 } = await Promise.resolve().then(() => (init_run(), run_exports));
|
|
24701
25468
|
process.exitCode = await runCommand2(cmdArgs ?? [], {
|
|
24702
|
-
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {}
|
|
25469
|
+
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
25470
|
+
...cmdOpts.install !== void 0 ? { install: cmdOpts.install } : {}
|
|
24703
25471
|
});
|
|
24704
25472
|
});
|
|
24705
25473
|
program.command("pull").description(
|
|
@@ -24826,13 +25594,17 @@ envCmd.command("export").description(
|
|
|
24826
25594
|
).option(
|
|
24827
25595
|
"--key <dwk_value>",
|
|
24828
25596
|
"Use a previously-minted API key (paste plaintext). (Mutually exclusive with --new-key.)"
|
|
25597
|
+
).option(
|
|
25598
|
+
"--with-secret-names",
|
|
25599
|
+
"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
25600
|
).action(
|
|
24830
25601
|
async (cmdOpts) => {
|
|
24831
25602
|
process.exitCode = await envExportCommand({
|
|
24832
25603
|
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
24833
25604
|
...cmdOpts.format !== void 0 ? { format: cmdOpts.format } : {},
|
|
24834
25605
|
...cmdOpts.newKey !== void 0 ? { newKey: cmdOpts.newKey } : {},
|
|
24835
|
-
...cmdOpts.key !== void 0 ? { key: cmdOpts.key } : {}
|
|
25606
|
+
...cmdOpts.key !== void 0 ? { key: cmdOpts.key } : {},
|
|
25607
|
+
...cmdOpts.withSecretNames ? { withSecretNames: true } : {}
|
|
24836
25608
|
});
|
|
24837
25609
|
}
|
|
24838
25610
|
);
|
|
@@ -24858,7 +25630,10 @@ moduleCmd.command("publish").description("Pack the current directory + upload it
|
|
|
24858
25630
|
).option(
|
|
24859
25631
|
"--timeout <seconds>",
|
|
24860
25632
|
"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.").
|
|
25633
|
+
).option("--json", "Emit the final build result as machine-readable JSON.").option(
|
|
25634
|
+
"--strict",
|
|
25635
|
+
"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."
|
|
25636
|
+
).action(
|
|
24862
25637
|
async (cmdOpts) => {
|
|
24863
25638
|
const timeoutSec = cmdOpts.timeout !== void 0 ? Number(cmdOpts.timeout) : void 0;
|
|
24864
25639
|
process.exitCode = await modulePublishCommand({
|
|
@@ -24874,19 +25649,23 @@ moduleCmd.command("publish").description("Pack the current directory + upload it
|
|
|
24874
25649
|
// commander maps `--no-wait` to `wait === false`.
|
|
24875
25650
|
...cmdOpts.wait === false ? { noWait: true } : {},
|
|
24876
25651
|
...timeoutSec !== void 0 && Number.isFinite(timeoutSec) ? { timeoutSec } : {},
|
|
24877
|
-
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
25652
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {},
|
|
25653
|
+
...cmdOpts.strict !== void 0 ? { strict: cmdOpts.strict } : {}
|
|
24878
25654
|
});
|
|
24879
25655
|
}
|
|
24880
25656
|
);
|
|
24881
|
-
moduleCmd.command("install [slug]").alias("deploy").description(
|
|
24882
|
-
|
|
25657
|
+
moduleCmd.command("install [slug]").alias("deploy").alias("upgrade").description(
|
|
25658
|
+
"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
25659
|
).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
25660
|
"--keep-dev-install",
|
|
24885
25661
|
"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
25662
|
).option(
|
|
24887
25663
|
"--replace-dev-install",
|
|
24888
25664
|
"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).
|
|
25665
|
+
).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(
|
|
25666
|
+
"--accept-data-loss <drift_hash>",
|
|
25667
|
+
"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)."
|
|
25668
|
+
).option("--json", "Emit the final result as machine-readable JSON.").action(
|
|
24890
25669
|
async (slug, cmdOpts) => {
|
|
24891
25670
|
process.exitCode = await moduleInstallCommand(slug, {
|
|
24892
25671
|
...cmdOpts.workspace !== void 0 ? { workspace: cmdOpts.workspace } : {},
|
|
@@ -24896,6 +25675,7 @@ moduleCmd.command("install [slug]").alias("deploy").description(
|
|
|
24896
25675
|
...cmdOpts.replaceDevInstall !== void 0 ? { replaceDevInstall: cmdOpts.replaceDevInstall } : {},
|
|
24897
25676
|
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
24898
25677
|
...cmdOpts.yes !== void 0 ? { yes: cmdOpts.yes } : {},
|
|
25678
|
+
...cmdOpts.acceptDataLoss !== void 0 ? { acceptDataLoss: cmdOpts.acceptDataLoss } : {},
|
|
24899
25679
|
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
24900
25680
|
});
|
|
24901
25681
|
}
|
|
@@ -25192,7 +25972,7 @@ fieldCmd.command("set-type <table-slug> <field-slug> <new-type>").description(
|
|
|
25192
25972
|
}
|
|
25193
25973
|
);
|
|
25194
25974
|
function getVersion() {
|
|
25195
|
-
return "0.
|
|
25975
|
+
return "0.26.0";
|
|
25196
25976
|
}
|
|
25197
25977
|
function parseIntOption(value) {
|
|
25198
25978
|
const n = Number.parseInt(value, 10);
|