@dashai/cli 0.30.1 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/bin.js +628 -407
- package/dist/bin.js.map +1 -1
- package/package.json +2 -2
package/dist/bin.js
CHANGED
|
@@ -5,7 +5,7 @@ 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
7
|
import { intro, log, select, isCancel, cancel, spinner, outro, confirm as confirm$1, password, text, multiselect } from '@clack/prompts';
|
|
8
|
-
import { Command, Option } from 'commander';
|
|
8
|
+
import { Command, Option, InvalidArgumentError } from 'commander';
|
|
9
9
|
import open from 'open';
|
|
10
10
|
import AdmZip from 'adm-zip';
|
|
11
11
|
import * as tar from 'tar';
|
|
@@ -13013,6 +13013,10 @@ generate\` and the schema commands. When the task needs them:
|
|
|
13013
13013
|
a command reports that the login is missing.
|
|
13014
13014
|
`;
|
|
13015
13015
|
}
|
|
13016
|
+
function hostedAppOrigin(apiUrl) {
|
|
13017
|
+
const host = companyApiHost(apiUrl);
|
|
13018
|
+
return host ? `https://${HOSTED_API_HOSTS[host]}` : null;
|
|
13019
|
+
}
|
|
13016
13020
|
function companyApiHost(apiUrl) {
|
|
13017
13021
|
if (!apiUrl) return null;
|
|
13018
13022
|
try {
|
|
@@ -13913,325 +13917,68 @@ var init_init = __esm({
|
|
|
13913
13917
|
}
|
|
13914
13918
|
});
|
|
13915
13919
|
|
|
13916
|
-
// src/lib/
|
|
13917
|
-
function
|
|
13918
|
-
|
|
13919
|
-
const additions = [];
|
|
13920
|
-
const warnings = [];
|
|
13921
|
-
const v = (rule, where, detail) => violations.push({ rule, where, detail });
|
|
13922
|
-
const baseTables = tablesOf(base);
|
|
13923
|
-
const headTables = tablesOf(head);
|
|
13924
|
-
const everTables = /* @__PURE__ */ new Set();
|
|
13925
|
-
const everFields = /* @__PURE__ */ new Set();
|
|
13926
|
-
for (const m of history) {
|
|
13927
|
-
for (const [slug, t] of tablesOf(m)) {
|
|
13928
|
-
everTables.add(slug);
|
|
13929
|
-
for (const f of t.fields ?? []) everFields.add(`${slug}.${f.slug}`);
|
|
13930
|
-
}
|
|
13931
|
-
}
|
|
13932
|
-
for (const [slug, bt] of baseTables) {
|
|
13933
|
-
const ht = headTables.get(slug);
|
|
13934
|
-
if (!ht) {
|
|
13935
|
-
v("removed_table", slug, "tables are never removed; deprecate instead");
|
|
13936
|
-
continue;
|
|
13937
|
-
}
|
|
13938
|
-
const bf = fieldsOf(bt);
|
|
13939
|
-
const hf = fieldsOf(ht);
|
|
13940
|
-
for (const [fs, f] of bf) {
|
|
13941
|
-
const g = hf.get(fs);
|
|
13942
|
-
const where = `${slug}.${fs}`;
|
|
13943
|
-
if (!g) {
|
|
13944
|
-
v("removed_field", where, "fields are never removed; stop writing it and set human_edit: open");
|
|
13945
|
-
continue;
|
|
13946
|
-
}
|
|
13947
|
-
if (f.type !== g.type) v("retyped_field", where, `${f.type} \u2192 ${g.type}`);
|
|
13948
|
-
if ((f.decimal_places ?? null) !== (g.decimal_places ?? null)) {
|
|
13949
|
-
v("retyped_field", where, `decimal_places ${f.decimal_places ?? "none"} \u2192 ${g.decimal_places ?? "none"}`);
|
|
13950
|
-
}
|
|
13951
|
-
if ((f.target ?? null) !== (g.target ?? null)) v("retyped_field", where, `link target ${f.target} \u2192 ${g.target}`);
|
|
13952
|
-
if (f.required !== true && g.required === true) {
|
|
13953
|
-
v("required_tightened", where, "old rows are empty; enforce it in the tool layer instead");
|
|
13954
|
-
}
|
|
13955
|
-
const bo = f.options ?? [];
|
|
13956
|
-
const ho = g.options ?? [];
|
|
13957
|
-
for (const [i, o] of bo.entries()) {
|
|
13958
|
-
if (!ho.includes(o)) v("removed_option", where, `option "${String(o)}" removed`);
|
|
13959
|
-
else if (ho[i] !== o) {
|
|
13960
|
-
v("reordered_option", where, `option "${String(o)}" moved from ${i} to ${ho.indexOf(o)}; options are append-only`);
|
|
13961
|
-
}
|
|
13962
|
-
}
|
|
13963
|
-
for (const o of ho.slice(bo.length)) if (!bo.includes(o)) additions.push(`option ${where} "${String(o)}"`);
|
|
13964
|
-
}
|
|
13965
|
-
for (const [fs] of hf) {
|
|
13966
|
-
if (bf.has(fs)) continue;
|
|
13967
|
-
if (everFields.has(`${slug}.${fs}`)) {
|
|
13968
|
-
v("slug_reused", `${slug}.${fs}`, "this slug was removed in an earlier version and may not come back");
|
|
13969
|
-
} else additions.push(`field ${slug}.${fs}`);
|
|
13970
|
-
}
|
|
13971
|
-
const hu = new Set(uniqueIndexes(ht));
|
|
13972
|
-
for (const idx of uniqueIndexes(bt)) {
|
|
13973
|
-
if (!hu.has(idx)) v("dropped_unique_index", `${slug}(${idx})`, "a unique index is a protocol lock; it may not be dropped or changed");
|
|
13974
|
-
}
|
|
13975
|
-
const bu = new Set(uniqueIndexes(bt));
|
|
13976
|
-
for (const idx of hu) {
|
|
13977
|
-
if (bu.has(idx)) continue;
|
|
13978
|
-
const overExisting = idx.split(",").some((f) => f.length > 0 && bf.has(f));
|
|
13979
|
-
if (overExisting) {
|
|
13980
|
-
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");
|
|
13981
|
-
} else additions.push(`unique index ${slug}(${idx})`);
|
|
13982
|
-
}
|
|
13983
|
-
}
|
|
13984
|
-
for (const [slug] of headTables) {
|
|
13985
|
-
if (baseTables.has(slug)) continue;
|
|
13986
|
-
if (everTables.has(slug)) v("slug_reused", slug, "this table slug was removed in an earlier version and may not come back");
|
|
13987
|
-
else additions.push(`table ${slug}`);
|
|
13988
|
-
}
|
|
13989
|
-
const baseViews = new Set((base?.owns?.views ?? []).map((x) => x.slug));
|
|
13990
|
-
const headViews = new Set((head?.owns?.views ?? []).map((x) => x.slug));
|
|
13991
|
-
for (const s of baseViews) if (!headViews.has(s)) warnings.push(`view ${s} removed (callers of qbViews.${s} break)`);
|
|
13992
|
-
for (const s of headViews) if (!baseViews.has(s)) additions.push(`view ${s}`);
|
|
13993
|
-
return { violations, additions, warnings };
|
|
13920
|
+
// src/lib/permission-hint.ts
|
|
13921
|
+
function str(v) {
|
|
13922
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
13994
13923
|
}
|
|
13995
|
-
|
|
13996
|
-
|
|
13997
|
-
"src/lib/manifest-evolution.ts"() {
|
|
13998
|
-
tablesOf = (m) => new Map((m?.owns?.tables ?? []).map((t) => [t.slug, t]));
|
|
13999
|
-
fieldsOf = (t) => new Map((t?.fields ?? []).map((f) => [f.slug, f]));
|
|
14000
|
-
uniqueIndexes = (t) => (t?.indexes ?? []).filter((i) => i.unique === true).map((i) => (i.fields ?? []).join(","));
|
|
14001
|
-
}
|
|
14002
|
-
});
|
|
14003
|
-
|
|
14004
|
-
// src/commands/module/diff-check.ts
|
|
14005
|
-
var diff_check_exports = {};
|
|
14006
|
-
__export(diff_check_exports, {
|
|
14007
|
-
EvolutionCheckError: () => EvolutionCheckError,
|
|
14008
|
-
moduleDiffCheckCommand: () => moduleDiffCheckCommand,
|
|
14009
|
-
printEvolutionReport: () => printEvolutionReport,
|
|
14010
|
-
publishEvolutionGate: () => publishEvolutionGate,
|
|
14011
|
-
runEvolutionCheck: () => runEvolutionCheck
|
|
14012
|
-
});
|
|
14013
|
-
function readJson(path) {
|
|
14014
|
-
try {
|
|
14015
|
-
return JSON.parse(readFileSync(path, "utf8"));
|
|
14016
|
-
} catch (err) {
|
|
14017
|
-
throw new EvolutionCheckError(`could not read ${path}: ${err.message}`);
|
|
14018
|
-
}
|
|
13924
|
+
function num(v) {
|
|
13925
|
+
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
14019
13926
|
}
|
|
14020
|
-
function
|
|
14021
|
-
|
|
14022
|
-
|
|
14023
|
-
encoding: "utf8",
|
|
14024
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
14025
|
-
maxBuffer: 64 * 1024 * 1024
|
|
14026
|
-
});
|
|
14027
|
-
} catch (err) {
|
|
14028
|
-
const stderr = err.stderr?.trim();
|
|
14029
|
-
throw new EvolutionCheckError(`git ${args.join(" ")} failed${stderr ? `: ${stderr}` : ""}`);
|
|
14030
|
-
}
|
|
13927
|
+
function featureFromPermission(permission) {
|
|
13928
|
+
const m = permission ? /^workspace\.feature\.([a-z0-9_]+)\.use$/.exec(permission) : null;
|
|
13929
|
+
return m?.[1];
|
|
14031
13930
|
}
|
|
14032
|
-
|
|
14033
|
-
const
|
|
14034
|
-
|
|
14035
|
-
|
|
14036
|
-
|
|
14037
|
-
|
|
14038
|
-
|
|
14039
|
-
|
|
14040
|
-
|
|
14041
|
-
|
|
14042
|
-
|
|
14043
|
-
|
|
14044
|
-
|
|
14045
|
-
|
|
14046
|
-
|
|
14047
|
-
|
|
14048
|
-
|
|
14049
|
-
}
|
|
13931
|
+
function featureSubject(ctx) {
|
|
13932
|
+
const feature = str(ctx.feature) ?? featureFromPermission(str(ctx.permission));
|
|
13933
|
+
if (feature && FEATURE_SUBJECTS[feature]) return FEATURE_SUBJECTS[feature];
|
|
13934
|
+
return str(ctx.feature_label) ?? (feature ? `"${feature}"` : void 0);
|
|
13935
|
+
}
|
|
13936
|
+
function needsAdmin(what, module, ask) {
|
|
13937
|
+
const scope = module ? ` for ${module}` : "";
|
|
13938
|
+
return `${what}${scope} needs a Dashwise admin. Ask your Dashwise admin to ${ask}.`;
|
|
13939
|
+
}
|
|
13940
|
+
function refusalHint(code2, context2, subject = {}) {
|
|
13941
|
+
const ctx = context2 ?? {};
|
|
13942
|
+
const module = subject.module ?? str(ctx.module_slug);
|
|
13943
|
+
const installationId = subject.installationId ?? num(ctx.installation_id);
|
|
13944
|
+
const installLabel = installationId !== void 0 ? ` (install #${installationId})` : "";
|
|
13945
|
+
const named = module ?? "this module";
|
|
13946
|
+
const reason = str(ctx.reason);
|
|
13947
|
+
if (code2 === "MODULE_CAPABILITY_DENIED" && reason === "authoring_access_required") {
|
|
14050
13948
|
return {
|
|
14051
|
-
|
|
14052
|
-
|
|
14053
|
-
head: headPath,
|
|
14054
|
-
history: history2.length,
|
|
14055
|
-
nothingReleased: false
|
|
13949
|
+
message: "You need authoring access to export development data; ask someone who manages module access.",
|
|
13950
|
+
needsGrant: true
|
|
14056
13951
|
};
|
|
14057
13952
|
}
|
|
14058
|
-
|
|
14059
|
-
|
|
14060
|
-
let mod;
|
|
14061
|
-
let versions;
|
|
14062
|
-
try {
|
|
14063
|
-
mod = await apiRequest(
|
|
14064
|
-
"GET",
|
|
14065
|
-
`/api/modules/by-slug/${encodeURIComponent(slug)}`
|
|
14066
|
-
);
|
|
14067
|
-
if (!mod.current_version) {
|
|
13953
|
+
if (code2 === "RAW_DATA_NOT_ALLOWED") {
|
|
13954
|
+
if (reason === "raw_data_write_required") {
|
|
14068
13955
|
return {
|
|
14069
|
-
|
|
14070
|
-
|
|
14071
|
-
warnings: [],
|
|
14072
|
-
base: "(nothing released)",
|
|
14073
|
-
head: headPath,
|
|
14074
|
-
history: 0,
|
|
14075
|
-
nothingReleased: true
|
|
13956
|
+
message: "You don't have access to change this data. Ask a data list Admin for Editor access in the Data Hub Permissions tab.",
|
|
13957
|
+
needsGrant: true
|
|
14076
13958
|
};
|
|
14077
13959
|
}
|
|
14078
|
-
|
|
14079
|
-
|
|
14080
|
-
|
|
14081
|
-
|
|
14082
|
-
|
|
14083
|
-
);
|
|
14084
|
-
}
|
|
14085
|
-
const released = versions.find((v) => v.version === mod.current_version);
|
|
14086
|
-
if (!released?.manifest_jsonb) {
|
|
14087
|
-
throw new EvolutionCheckError(
|
|
14088
|
-
`the platform returned no manifest for ${slug} v${mod.current_version}; pass --base <ref|file> to check offline`
|
|
14089
|
-
);
|
|
14090
|
-
}
|
|
14091
|
-
const releasedAt = released.created_on ? Date.parse(released.created_on) : Number.POSITIVE_INFINITY;
|
|
14092
|
-
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);
|
|
14093
|
-
return {
|
|
14094
|
-
...compareManifests(released.manifest_jsonb, head, history),
|
|
14095
|
-
base: `${slug} v${mod.current_version} (released)`,
|
|
14096
|
-
head: headPath,
|
|
14097
|
-
history: history.length,
|
|
14098
|
-
nothingReleased: false
|
|
14099
|
-
};
|
|
14100
|
-
}
|
|
14101
|
-
function printEvolutionReport(r, print = out) {
|
|
14102
|
-
print(`module diff --check: ${r.head} against ${r.base} (${r.history} older version${r.history === 1 ? "" : "s"} scanned)`);
|
|
14103
|
-
for (const a of r.additions) print(` + ${a}`);
|
|
14104
|
-
for (const w of r.warnings) print(` ! ${w}`);
|
|
14105
|
-
for (const x of r.violations) print(` \u2717 ${x.rule}: ${x.where}: ${x.detail}`);
|
|
14106
|
-
}
|
|
14107
|
-
async function moduleDiffCheckCommand(options) {
|
|
14108
|
-
let r;
|
|
14109
|
-
try {
|
|
14110
|
-
r = await runEvolutionCheck(options);
|
|
14111
|
-
} catch (err) {
|
|
14112
|
-
if (err instanceof EvolutionCheckError) {
|
|
14113
|
-
if (options.json) out(JSON.stringify({ ok: false, error: { code: "CHECK_FAILED", message: err.message } }));
|
|
14114
|
-
else fail(`module diff --check: ${err.message}`);
|
|
14115
|
-
return 2;
|
|
13960
|
+
if (reason === "installation_access_required") {
|
|
13961
|
+
return {
|
|
13962
|
+
message: "You cannot open this installation with your current access; ask someone who manages installation access.",
|
|
13963
|
+
needsGrant: true
|
|
13964
|
+
};
|
|
14116
13965
|
}
|
|
14117
|
-
|
|
14118
|
-
|
|
14119
|
-
|
|
14120
|
-
|
|
14121
|
-
return r.violations.length === 0 ? 0 : 1;
|
|
13966
|
+
return {
|
|
13967
|
+
message: "You don't have access to view this data. Ask a data list Admin for Viewer access in the Data Hub Permissions tab.",
|
|
13968
|
+
needsGrant: true
|
|
13969
|
+
};
|
|
14122
13970
|
}
|
|
14123
|
-
if (
|
|
14124
|
-
|
|
14125
|
-
|
|
13971
|
+
if (code2 === "VALIDATION_ERROR" && reason === "module_context_required") {
|
|
13972
|
+
return {
|
|
13973
|
+
message: "Open the intended module project and select the company and installation.",
|
|
13974
|
+
needsGrant: false
|
|
13975
|
+
};
|
|
14126
13976
|
}
|
|
14127
|
-
|
|
14128
|
-
|
|
14129
|
-
|
|
14130
|
-
|
|
14131
|
-
|
|
14132
|
-
fail(`${r.violations.length} violation(s): not additive. Released copies would lose data or fail to upgrade.`);
|
|
14133
|
-
info(`Add a new field or table instead, and stop writing the old one (${code('human_edit: "open"')}).`);
|
|
14134
|
-
return 1;
|
|
14135
|
-
}
|
|
14136
|
-
async function publishEvolutionGate(projectRoot, strict) {
|
|
14137
|
-
let r;
|
|
14138
|
-
try {
|
|
14139
|
-
r = await runEvolutionCheck({ dir: projectRoot });
|
|
14140
|
-
} catch (err) {
|
|
14141
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
14142
|
-
if (strict) {
|
|
14143
|
-
fail(`Evolution check could not run (${msg}). Refusing with --strict. Nothing was published.`);
|
|
14144
|
-
return 1;
|
|
14145
|
-
}
|
|
14146
|
-
warn(`Evolution check skipped: ${msg}.`);
|
|
14147
|
-
return 0;
|
|
14148
|
-
}
|
|
14149
|
-
if (r.nothingReleased || r.violations.length === 0) return 0;
|
|
14150
|
-
printEvolutionReport(r, (s) => (strict ? fail : warn)(s));
|
|
14151
|
-
if (strict) {
|
|
14152
|
-
fail(
|
|
14153
|
-
`module.json is not an additive evolution of the released version (${r.violations.length} violation(s)). Refusing with --strict. Nothing was published.`
|
|
14154
|
-
);
|
|
14155
|
-
return 1;
|
|
14156
|
-
}
|
|
14157
|
-
warn(
|
|
14158
|
-
`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.`
|
|
14159
|
-
);
|
|
14160
|
-
return 0;
|
|
14161
|
-
}
|
|
14162
|
-
var EvolutionCheckError;
|
|
14163
|
-
var init_diff_check = __esm({
|
|
14164
|
-
"src/commands/module/diff-check.ts"() {
|
|
14165
|
-
init_api_client();
|
|
14166
|
-
init_manifest_evolution();
|
|
14167
|
-
init_output();
|
|
14168
|
-
EvolutionCheckError = class extends Error {
|
|
14169
|
-
};
|
|
14170
|
-
}
|
|
14171
|
-
});
|
|
14172
|
-
|
|
14173
|
-
// src/lib/permission-hint.ts
|
|
14174
|
-
function str(v) {
|
|
14175
|
-
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
14176
|
-
}
|
|
14177
|
-
function num(v) {
|
|
14178
|
-
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
14179
|
-
}
|
|
14180
|
-
function featureFromPermission(permission) {
|
|
14181
|
-
const m = permission ? /^workspace\.feature\.([a-z0-9_]+)\.use$/.exec(permission) : null;
|
|
14182
|
-
return m?.[1];
|
|
14183
|
-
}
|
|
14184
|
-
function featureSubject(ctx) {
|
|
14185
|
-
const feature = str(ctx.feature) ?? featureFromPermission(str(ctx.permission));
|
|
14186
|
-
if (feature && FEATURE_SUBJECTS[feature]) return FEATURE_SUBJECTS[feature];
|
|
14187
|
-
return str(ctx.feature_label) ?? (feature ? `"${feature}"` : void 0);
|
|
14188
|
-
}
|
|
14189
|
-
function needsAdmin(what, module, ask) {
|
|
14190
|
-
const scope = module ? ` for ${module}` : "";
|
|
14191
|
-
return `${what}${scope} needs a Dashwise admin. Ask your Dashwise admin to ${ask}.`;
|
|
14192
|
-
}
|
|
14193
|
-
function refusalHint(code2, context2, subject = {}) {
|
|
14194
|
-
const ctx = context2 ?? {};
|
|
14195
|
-
const module = subject.module ?? str(ctx.module_slug);
|
|
14196
|
-
const installationId = subject.installationId ?? num(ctx.installation_id);
|
|
14197
|
-
const installLabel = installationId !== void 0 ? ` (install #${installationId})` : "";
|
|
14198
|
-
const named = module ?? "this module";
|
|
14199
|
-
const reason = str(ctx.reason);
|
|
14200
|
-
if (code2 === "MODULE_CAPABILITY_DENIED" && reason === "authoring_access_required") {
|
|
14201
|
-
return {
|
|
14202
|
-
message: "You need authoring access to export development data; ask someone who manages module access.",
|
|
14203
|
-
needsGrant: true
|
|
14204
|
-
};
|
|
14205
|
-
}
|
|
14206
|
-
if (code2 === "RAW_DATA_NOT_ALLOWED") {
|
|
14207
|
-
if (reason === "raw_data_write_required") {
|
|
14208
|
-
return {
|
|
14209
|
-
message: "You don't have access to change this data. Ask a data list Admin for Editor access in the Data Hub Permissions tab.",
|
|
14210
|
-
needsGrant: true
|
|
14211
|
-
};
|
|
14212
|
-
}
|
|
14213
|
-
if (reason === "installation_access_required") {
|
|
14214
|
-
return {
|
|
14215
|
-
message: "You cannot open this installation with your current access; ask someone who manages installation access.",
|
|
14216
|
-
needsGrant: true
|
|
14217
|
-
};
|
|
14218
|
-
}
|
|
14219
|
-
return {
|
|
14220
|
-
message: "You don't have access to view this data. Ask a data list Admin for Viewer access in the Data Hub Permissions tab.",
|
|
14221
|
-
needsGrant: true
|
|
14222
|
-
};
|
|
14223
|
-
}
|
|
14224
|
-
if (code2 === "VALIDATION_ERROR" && reason === "module_context_required") {
|
|
14225
|
-
return {
|
|
14226
|
-
message: "Open the intended module project and select the company and installation.",
|
|
14227
|
-
needsGrant: false
|
|
14228
|
-
};
|
|
14229
|
-
}
|
|
14230
|
-
if (code2 === "VALIDATION_ERROR" && reason === "context_mismatch") {
|
|
14231
|
-
return {
|
|
14232
|
-
message: "The selected company and installation do not match; check your selection.",
|
|
14233
|
-
needsGrant: false
|
|
14234
|
-
};
|
|
13977
|
+
if (code2 === "VALIDATION_ERROR" && reason === "context_mismatch") {
|
|
13978
|
+
return {
|
|
13979
|
+
message: "The selected company and installation do not match; check your selection.",
|
|
13980
|
+
needsGrant: false
|
|
13981
|
+
};
|
|
14235
13982
|
}
|
|
14236
13983
|
if (code2 === "UNAUTHENTICATED" && reason === "module_api_credential_missing") {
|
|
14237
13984
|
return {
|
|
@@ -14398,107 +14145,364 @@ function reportFailedOutcome(failure, hint, opts) {
|
|
|
14398
14145
|
...opts.extra
|
|
14399
14146
|
})
|
|
14400
14147
|
);
|
|
14401
|
-
} else {
|
|
14402
|
-
fail(hint?.message ?? opts.fallback);
|
|
14148
|
+
} else {
|
|
14149
|
+
fail(hint?.message ?? opts.fallback);
|
|
14150
|
+
}
|
|
14151
|
+
return hint?.needsGrant ? EXIT_NEEDS_GRANT : 1;
|
|
14152
|
+
}
|
|
14153
|
+
function isTransientPollError(err) {
|
|
14154
|
+
if (!(err instanceof ApiError)) return true;
|
|
14155
|
+
return err.status === 0 || err.status === 429 || err.status >= 500;
|
|
14156
|
+
}
|
|
14157
|
+
function isTypedRequestError(err) {
|
|
14158
|
+
if (err instanceof ApiError) return true;
|
|
14159
|
+
if (!(err instanceof Error)) return false;
|
|
14160
|
+
const candidate = err;
|
|
14161
|
+
return typeof candidate.code === "string" && typeof candidate.status === "number" && (candidate.context === void 0 || candidate.context !== null && typeof candidate.context === "object" && !Array.isArray(candidate.context));
|
|
14162
|
+
}
|
|
14163
|
+
function reportRefusal(err, subject, opts) {
|
|
14164
|
+
const writeJson = (message) => {
|
|
14165
|
+
if (opts.stderr) process.stderr.write(`${message}
|
|
14166
|
+
`);
|
|
14167
|
+
else out(message);
|
|
14168
|
+
};
|
|
14169
|
+
if (!isTypedRequestError(err)) {
|
|
14170
|
+
const message = err?.message ?? String(err);
|
|
14171
|
+
if (opts.json) {
|
|
14172
|
+
writeJson(
|
|
14173
|
+
JSON.stringify({
|
|
14174
|
+
error: {
|
|
14175
|
+
code: "CLI_ERROR",
|
|
14176
|
+
message: message.replace(ANSI, ""),
|
|
14177
|
+
status: null,
|
|
14178
|
+
context: null
|
|
14179
|
+
},
|
|
14180
|
+
hint: null,
|
|
14181
|
+
needs_grant: false
|
|
14182
|
+
})
|
|
14183
|
+
);
|
|
14184
|
+
} else {
|
|
14185
|
+
fail(`${opts.action} failed: ${message}`);
|
|
14186
|
+
}
|
|
14187
|
+
return 1;
|
|
14188
|
+
}
|
|
14189
|
+
const hint = err.status === 401 && err.context?.reason !== "module_api_credential_missing" ? {
|
|
14190
|
+
message: opts.moduleKey ? MODULE_KEY_REFUSED[opts.moduleKey] : "Your login has expired. Run: dashwise login",
|
|
14191
|
+
needsGrant: false
|
|
14192
|
+
} : refusalHint(err.code, err.context, subject);
|
|
14193
|
+
if (opts.json) {
|
|
14194
|
+
writeJson(
|
|
14195
|
+
JSON.stringify({
|
|
14196
|
+
error: {
|
|
14197
|
+
code: err.code,
|
|
14198
|
+
message: err.message,
|
|
14199
|
+
status: err.status,
|
|
14200
|
+
context: err.context ?? null
|
|
14201
|
+
},
|
|
14202
|
+
hint: hint?.message ?? null,
|
|
14203
|
+
needs_grant: hint?.needsGrant ?? false
|
|
14204
|
+
})
|
|
14205
|
+
);
|
|
14206
|
+
} else if (hint) {
|
|
14207
|
+
fail(
|
|
14208
|
+
`${hint.message} (${err.code})${err.context ? ` context=${JSON.stringify(err.context)}` : ""}`
|
|
14209
|
+
);
|
|
14210
|
+
} else {
|
|
14211
|
+
fail(
|
|
14212
|
+
`${opts.action} failed (${err.code}): ${err.message}${err.context ? ` context=${JSON.stringify(err.context)}` : ""}`
|
|
14213
|
+
);
|
|
14214
|
+
}
|
|
14215
|
+
return hint?.needsGrant ? EXIT_NEEDS_GRANT : 1;
|
|
14216
|
+
}
|
|
14217
|
+
var EXIT_NEEDS_GRANT, EXIT_TIMED_OUT, REBUILD_BUMP_OPERATOR_ONLY, FEATURE_SUBJECTS, OPTION_SUBJECTS, MODULE_RIGHT_CODES, ANSI, MODULE_KEY_REFUSED;
|
|
14218
|
+
var init_permission_hint = __esm({
|
|
14219
|
+
"src/lib/permission-hint.ts"() {
|
|
14220
|
+
init_api_client();
|
|
14221
|
+
init_output();
|
|
14222
|
+
EXIT_NEEDS_GRANT = 3;
|
|
14223
|
+
EXIT_TIMED_OUT = 4;
|
|
14224
|
+
REBUILD_BUMP_OPERATOR_ONLY = "REBUILD_BUMP_OPERATOR_ONLY";
|
|
14225
|
+
FEATURE_SUBJECTS = {
|
|
14226
|
+
module_runtime_streaming: "Streaming",
|
|
14227
|
+
module_rebuild: "Rebuild",
|
|
14228
|
+
module_building: "Building modules",
|
|
14229
|
+
module_runtime_sizing: "Memory size"
|
|
14230
|
+
};
|
|
14231
|
+
OPTION_SUBJECTS = {
|
|
14232
|
+
streaming: "Streaming",
|
|
14233
|
+
memory_mb: "Memory size",
|
|
14234
|
+
reapply: "Re-applying the runtime",
|
|
14235
|
+
timeout: "The timeout",
|
|
14236
|
+
reserved_concurrency: "Reserved concurrency",
|
|
14237
|
+
log_retention: "Log retention"
|
|
14238
|
+
};
|
|
14239
|
+
MODULE_RIGHT_CODES = /* @__PURE__ */ new Set([
|
|
14240
|
+
"MODULE_CAPABILITY_DENIED",
|
|
14241
|
+
"PUBLISH_REQUIRES_ADMIN",
|
|
14242
|
+
"NOT_COPY_OWNER"
|
|
14243
|
+
]);
|
|
14244
|
+
ANSI = /\x1b\[[0-9;]*m/g;
|
|
14245
|
+
MODULE_KEY_REFUSED = {
|
|
14246
|
+
env: "The module key in DASHWISE_API_KEY was refused (expired or revoked). Replace it with a current key, or unset it to use this module's saved key. dashwise login does not renew module keys.",
|
|
14247
|
+
env_file: "The module key in .env.local (DASHWISE_API_KEY) was refused (expired or revoked). Replace it, or remove that line to use this module's saved key. dashwise login does not renew module keys.",
|
|
14248
|
+
saved: `This module's saved key was refused (expired or revoked). In your CLI auth.json, set only modules[<slug>].apiKey to an empty string (""), keeping installationId and all other fields. Then run dashwise dev from this module's directory with DASHWISE_API_KEY unset to mint and save a replacement key for the same installation. dashwise login does not renew module keys.`
|
|
14249
|
+
};
|
|
14250
|
+
}
|
|
14251
|
+
});
|
|
14252
|
+
|
|
14253
|
+
// src/lib/manifest-evolution.ts
|
|
14254
|
+
function compareManifests(base, head, history = []) {
|
|
14255
|
+
const violations = [];
|
|
14256
|
+
const additions = [];
|
|
14257
|
+
const warnings = [];
|
|
14258
|
+
const v = (rule, where, detail) => violations.push({ rule, where, detail });
|
|
14259
|
+
const baseTables = tablesOf(base);
|
|
14260
|
+
const headTables = tablesOf(head);
|
|
14261
|
+
const everTables = /* @__PURE__ */ new Set();
|
|
14262
|
+
const everFields = /* @__PURE__ */ new Set();
|
|
14263
|
+
for (const m of history) {
|
|
14264
|
+
for (const [slug, t] of tablesOf(m)) {
|
|
14265
|
+
everTables.add(slug);
|
|
14266
|
+
for (const f of t.fields ?? []) everFields.add(`${slug}.${f.slug}`);
|
|
14267
|
+
}
|
|
14268
|
+
}
|
|
14269
|
+
for (const [slug, bt] of baseTables) {
|
|
14270
|
+
const ht = headTables.get(slug);
|
|
14271
|
+
if (!ht) {
|
|
14272
|
+
v("removed_table", slug, "tables are never removed; deprecate instead");
|
|
14273
|
+
continue;
|
|
14274
|
+
}
|
|
14275
|
+
const bf = fieldsOf(bt);
|
|
14276
|
+
const hf = fieldsOf(ht);
|
|
14277
|
+
for (const [fs, f] of bf) {
|
|
14278
|
+
const g = hf.get(fs);
|
|
14279
|
+
const where = `${slug}.${fs}`;
|
|
14280
|
+
if (!g) {
|
|
14281
|
+
v("removed_field", where, "fields are never removed; stop writing it and set human_edit: open");
|
|
14282
|
+
continue;
|
|
14283
|
+
}
|
|
14284
|
+
if (f.type !== g.type) v("retyped_field", where, `${f.type} \u2192 ${g.type}`);
|
|
14285
|
+
if ((f.decimal_places ?? null) !== (g.decimal_places ?? null)) {
|
|
14286
|
+
v("retyped_field", where, `decimal_places ${f.decimal_places ?? "none"} \u2192 ${g.decimal_places ?? "none"}`);
|
|
14287
|
+
}
|
|
14288
|
+
if ((f.target ?? null) !== (g.target ?? null)) v("retyped_field", where, `link target ${f.target} \u2192 ${g.target}`);
|
|
14289
|
+
if (f.required !== true && g.required === true) {
|
|
14290
|
+
v("required_tightened", where, "old rows are empty; enforce it in the tool layer instead");
|
|
14291
|
+
}
|
|
14292
|
+
const bo = f.options ?? [];
|
|
14293
|
+
const ho = g.options ?? [];
|
|
14294
|
+
for (const [i, o] of bo.entries()) {
|
|
14295
|
+
if (!ho.includes(o)) v("removed_option", where, `option "${String(o)}" removed`);
|
|
14296
|
+
else if (ho[i] !== o) {
|
|
14297
|
+
v("reordered_option", where, `option "${String(o)}" moved from ${i} to ${ho.indexOf(o)}; options are append-only`);
|
|
14298
|
+
}
|
|
14299
|
+
}
|
|
14300
|
+
for (const o of ho.slice(bo.length)) if (!bo.includes(o)) additions.push(`option ${where} "${String(o)}"`);
|
|
14301
|
+
}
|
|
14302
|
+
for (const [fs] of hf) {
|
|
14303
|
+
if (bf.has(fs)) continue;
|
|
14304
|
+
if (everFields.has(`${slug}.${fs}`)) {
|
|
14305
|
+
v("slug_reused", `${slug}.${fs}`, "this slug was removed in an earlier version and may not come back");
|
|
14306
|
+
} else additions.push(`field ${slug}.${fs}`);
|
|
14307
|
+
}
|
|
14308
|
+
const hu = new Set(uniqueIndexes(ht));
|
|
14309
|
+
for (const idx of uniqueIndexes(bt)) {
|
|
14310
|
+
if (!hu.has(idx)) v("dropped_unique_index", `${slug}(${idx})`, "a unique index is a protocol lock; it may not be dropped or changed");
|
|
14311
|
+
}
|
|
14312
|
+
const bu = new Set(uniqueIndexes(bt));
|
|
14313
|
+
for (const idx of hu) {
|
|
14314
|
+
if (bu.has(idx)) continue;
|
|
14315
|
+
const overExisting = idx.split(",").some((f) => f.length > 0 && bf.has(f));
|
|
14316
|
+
if (overExisting) {
|
|
14317
|
+
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");
|
|
14318
|
+
} else additions.push(`unique index ${slug}(${idx})`);
|
|
14319
|
+
}
|
|
14320
|
+
}
|
|
14321
|
+
for (const [slug] of headTables) {
|
|
14322
|
+
if (baseTables.has(slug)) continue;
|
|
14323
|
+
if (everTables.has(slug)) v("slug_reused", slug, "this table slug was removed in an earlier version and may not come back");
|
|
14324
|
+
else additions.push(`table ${slug}`);
|
|
14325
|
+
}
|
|
14326
|
+
const baseViews = new Set((base?.owns?.views ?? []).map((x) => x.slug));
|
|
14327
|
+
const headViews = new Set((head?.owns?.views ?? []).map((x) => x.slug));
|
|
14328
|
+
for (const s of baseViews) if (!headViews.has(s)) warnings.push(`view ${s} removed (callers of qbViews.${s} break)`);
|
|
14329
|
+
for (const s of headViews) if (!baseViews.has(s)) additions.push(`view ${s}`);
|
|
14330
|
+
return { violations, additions, warnings };
|
|
14331
|
+
}
|
|
14332
|
+
var tablesOf, fieldsOf, uniqueIndexes;
|
|
14333
|
+
var init_manifest_evolution = __esm({
|
|
14334
|
+
"src/lib/manifest-evolution.ts"() {
|
|
14335
|
+
tablesOf = (m) => new Map((m?.owns?.tables ?? []).map((t) => [t.slug, t]));
|
|
14336
|
+
fieldsOf = (t) => new Map((t?.fields ?? []).map((f) => [f.slug, f]));
|
|
14337
|
+
uniqueIndexes = (t) => (t?.indexes ?? []).filter((i) => i.unique === true).map((i) => (i.fields ?? []).join(","));
|
|
14338
|
+
}
|
|
14339
|
+
});
|
|
14340
|
+
|
|
14341
|
+
// src/commands/module/diff-check.ts
|
|
14342
|
+
var diff_check_exports = {};
|
|
14343
|
+
__export(diff_check_exports, {
|
|
14344
|
+
EvolutionCheckError: () => EvolutionCheckError,
|
|
14345
|
+
moduleDiffCheckCommand: () => moduleDiffCheckCommand,
|
|
14346
|
+
printEvolutionReport: () => printEvolutionReport,
|
|
14347
|
+
publishEvolutionGate: () => publishEvolutionGate,
|
|
14348
|
+
runEvolutionCheck: () => runEvolutionCheck
|
|
14349
|
+
});
|
|
14350
|
+
function readJson(path) {
|
|
14351
|
+
try {
|
|
14352
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
14353
|
+
} catch (err) {
|
|
14354
|
+
throw new EvolutionCheckError(`could not read ${path}: ${err.message}`);
|
|
14355
|
+
}
|
|
14356
|
+
}
|
|
14357
|
+
function git(cwd, args) {
|
|
14358
|
+
try {
|
|
14359
|
+
return execFileSync("git", ["-C", cwd, ...args], {
|
|
14360
|
+
encoding: "utf8",
|
|
14361
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
14362
|
+
maxBuffer: 64 * 1024 * 1024
|
|
14363
|
+
});
|
|
14364
|
+
} catch (err) {
|
|
14365
|
+
const stderr = err.stderr?.trim();
|
|
14366
|
+
throw new EvolutionCheckError(`git ${args.join(" ")} failed${stderr ? `: ${stderr}` : ""}`);
|
|
14367
|
+
}
|
|
14368
|
+
}
|
|
14369
|
+
async function runEvolutionCheck(options) {
|
|
14370
|
+
const projectRoot = resolve(options.dir ?? process.cwd());
|
|
14371
|
+
const headPath = options.head ? resolve(options.head) : join(projectRoot, "module.json");
|
|
14372
|
+
if (!existsSync(headPath)) throw new EvolutionCheckError(`no manifest at ${headPath}`);
|
|
14373
|
+
const head = readJson(headPath);
|
|
14374
|
+
const baseFile = options.baseFile ?? (options.base && existsSync(options.base) ? options.base : void 0);
|
|
14375
|
+
if (baseFile) {
|
|
14376
|
+
const base = readJson(baseFile);
|
|
14377
|
+
return { ...compareManifests(base, head), base: baseFile, head: headPath, history: 0, nothingReleased: false };
|
|
14378
|
+
}
|
|
14379
|
+
if (options.base) {
|
|
14380
|
+
const ref = options.base;
|
|
14381
|
+
const base = JSON.parse(git(projectRoot, ["show", `${ref}:./module.json`]));
|
|
14382
|
+
let history2 = [];
|
|
14383
|
+
if (!options.noHistory) {
|
|
14384
|
+
const shas = git(projectRoot, ["log", "--format=%H", "--reverse", ref, "--", "module.json"]).split("\n").filter(Boolean);
|
|
14385
|
+
history2 = shas.slice(0, -1).map((sha) => JSON.parse(git(projectRoot, ["show", `${sha}:./module.json`])));
|
|
14386
|
+
}
|
|
14387
|
+
return {
|
|
14388
|
+
...compareManifests(base, head, history2),
|
|
14389
|
+
base: `${ref}:module.json`,
|
|
14390
|
+
head: headPath,
|
|
14391
|
+
history: history2.length,
|
|
14392
|
+
nothingReleased: false
|
|
14393
|
+
};
|
|
14394
|
+
}
|
|
14395
|
+
const slug = head.module?.slug;
|
|
14396
|
+
if (!slug) throw new EvolutionCheckError(`${headPath} has no module.slug`);
|
|
14397
|
+
let mod;
|
|
14398
|
+
let versions;
|
|
14399
|
+
try {
|
|
14400
|
+
mod = await apiRequest(
|
|
14401
|
+
"GET",
|
|
14402
|
+
`/api/modules/by-slug/${encodeURIComponent(slug)}`
|
|
14403
|
+
);
|
|
14404
|
+
if (!mod.current_version) {
|
|
14405
|
+
return {
|
|
14406
|
+
violations: [],
|
|
14407
|
+
additions: [],
|
|
14408
|
+
warnings: [],
|
|
14409
|
+
base: "(nothing released)",
|
|
14410
|
+
head: headPath,
|
|
14411
|
+
history: 0,
|
|
14412
|
+
nothingReleased: true
|
|
14413
|
+
};
|
|
14414
|
+
}
|
|
14415
|
+
versions = await apiRequest("GET", `/api/modules/${mod.id}/versions`);
|
|
14416
|
+
} catch (err) {
|
|
14417
|
+
const why = err instanceof ApiError ? `${err.code || err.status} ${err.message}` : err.message;
|
|
14418
|
+
throw new EvolutionCheckError(
|
|
14419
|
+
`could not read the released manifest of ${slug} (${why}); pass --base <ref|file> to check offline`
|
|
14420
|
+
);
|
|
14403
14421
|
}
|
|
14404
|
-
|
|
14422
|
+
const released = versions.find((v) => v.version === mod.current_version);
|
|
14423
|
+
if (!released?.manifest_jsonb) {
|
|
14424
|
+
throw new EvolutionCheckError(
|
|
14425
|
+
`the platform returned no manifest for ${slug} v${mod.current_version}; pass --base <ref|file> to check offline`
|
|
14426
|
+
);
|
|
14427
|
+
}
|
|
14428
|
+
const releasedAt = released.created_on ? Date.parse(released.created_on) : Number.POSITIVE_INFINITY;
|
|
14429
|
+
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);
|
|
14430
|
+
return {
|
|
14431
|
+
...compareManifests(released.manifest_jsonb, head, history),
|
|
14432
|
+
base: `${slug} v${mod.current_version} (released)`,
|
|
14433
|
+
head: headPath,
|
|
14434
|
+
history: history.length,
|
|
14435
|
+
nothingReleased: false
|
|
14436
|
+
};
|
|
14405
14437
|
}
|
|
14406
|
-
function
|
|
14407
|
-
|
|
14408
|
-
|
|
14438
|
+
function printEvolutionReport(r, print = out) {
|
|
14439
|
+
print(`module diff --check: ${r.head} against ${r.base} (${r.history} older version${r.history === 1 ? "" : "s"} scanned)`);
|
|
14440
|
+
for (const a of r.additions) print(` + ${a}`);
|
|
14441
|
+
for (const w of r.warnings) print(` ! ${w}`);
|
|
14442
|
+
for (const x of r.violations) print(` \u2717 ${x.rule}: ${x.where}: ${x.detail}`);
|
|
14409
14443
|
}
|
|
14410
|
-
function
|
|
14411
|
-
|
|
14412
|
-
|
|
14413
|
-
|
|
14414
|
-
|
|
14444
|
+
async function moduleDiffCheckCommand(options) {
|
|
14445
|
+
let r;
|
|
14446
|
+
try {
|
|
14447
|
+
r = await runEvolutionCheck(options);
|
|
14448
|
+
} catch (err) {
|
|
14449
|
+
if (err instanceof EvolutionCheckError) {
|
|
14450
|
+
if (options.json) out(JSON.stringify({ ok: false, error: { code: "CHECK_FAILED", message: err.message } }));
|
|
14451
|
+
else fail(`module diff --check: ${err.message}`);
|
|
14452
|
+
return 2;
|
|
14453
|
+
}
|
|
14454
|
+
throw err;
|
|
14455
|
+
}
|
|
14456
|
+
if (options.json) {
|
|
14457
|
+
out(JSON.stringify({ ok: r.violations.length === 0, ...r }, null, 2));
|
|
14458
|
+
return r.violations.length === 0 ? 0 : 1;
|
|
14459
|
+
}
|
|
14460
|
+
if (r.nothingReleased) {
|
|
14461
|
+
success("Nothing released yet: every change is additive.");
|
|
14462
|
+
return 0;
|
|
14463
|
+
}
|
|
14464
|
+
printEvolutionReport(r);
|
|
14465
|
+
if (r.violations.length === 0) {
|
|
14466
|
+
success("additive");
|
|
14467
|
+
return 0;
|
|
14468
|
+
}
|
|
14469
|
+
fail(`${r.violations.length} violation(s): not additive. Released copies would lose data or fail to upgrade.`);
|
|
14470
|
+
info(`Add a new field or table instead, and stop writing the old one (${code('human_edit: "open"')}).`);
|
|
14471
|
+
return 1;
|
|
14415
14472
|
}
|
|
14416
|
-
function
|
|
14417
|
-
|
|
14418
|
-
|
|
14419
|
-
|
|
14420
|
-
|
|
14421
|
-
|
|
14422
|
-
|
|
14423
|
-
|
|
14424
|
-
|
|
14425
|
-
writeJson(
|
|
14426
|
-
JSON.stringify({
|
|
14427
|
-
error: {
|
|
14428
|
-
code: "CLI_ERROR",
|
|
14429
|
-
message: message.replace(ANSI, ""),
|
|
14430
|
-
status: null,
|
|
14431
|
-
context: null
|
|
14432
|
-
},
|
|
14433
|
-
hint: null,
|
|
14434
|
-
needs_grant: false
|
|
14435
|
-
})
|
|
14436
|
-
);
|
|
14437
|
-
} else {
|
|
14438
|
-
fail(`${opts.action} failed: ${message}`);
|
|
14473
|
+
async function publishEvolutionGate(projectRoot, strict) {
|
|
14474
|
+
let r;
|
|
14475
|
+
try {
|
|
14476
|
+
r = await runEvolutionCheck({ dir: projectRoot });
|
|
14477
|
+
} catch (err) {
|
|
14478
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
14479
|
+
if (strict) {
|
|
14480
|
+
fail(`Evolution check could not run (${msg}). Refusing with --strict. Nothing was published.`);
|
|
14481
|
+
return 1;
|
|
14439
14482
|
}
|
|
14440
|
-
|
|
14483
|
+
warn(`Evolution check skipped: ${msg}.`);
|
|
14484
|
+
return 0;
|
|
14441
14485
|
}
|
|
14442
|
-
|
|
14443
|
-
|
|
14444
|
-
|
|
14445
|
-
} : refusalHint(err.code, err.context, subject);
|
|
14446
|
-
if (opts.json) {
|
|
14447
|
-
writeJson(
|
|
14448
|
-
JSON.stringify({
|
|
14449
|
-
error: {
|
|
14450
|
-
code: err.code,
|
|
14451
|
-
message: err.message,
|
|
14452
|
-
status: err.status,
|
|
14453
|
-
context: err.context ?? null
|
|
14454
|
-
},
|
|
14455
|
-
hint: hint?.message ?? null,
|
|
14456
|
-
needs_grant: hint?.needsGrant ?? false
|
|
14457
|
-
})
|
|
14458
|
-
);
|
|
14459
|
-
} else if (hint) {
|
|
14460
|
-
fail(
|
|
14461
|
-
`${hint.message} (${err.code})${err.context ? ` context=${JSON.stringify(err.context)}` : ""}`
|
|
14462
|
-
);
|
|
14463
|
-
} else {
|
|
14486
|
+
if (r.nothingReleased || r.violations.length === 0) return 0;
|
|
14487
|
+
printEvolutionReport(r, (s) => (strict ? fail : warn)(s));
|
|
14488
|
+
if (strict) {
|
|
14464
14489
|
fail(
|
|
14465
|
-
|
|
14490
|
+
`module.json is not an additive evolution of the released version (${r.violations.length} violation(s)). Refusing with --strict. Nothing was published.`
|
|
14466
14491
|
);
|
|
14492
|
+
return 1;
|
|
14467
14493
|
}
|
|
14468
|
-
|
|
14494
|
+
warn(
|
|
14495
|
+
`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.`
|
|
14496
|
+
);
|
|
14497
|
+
return 0;
|
|
14469
14498
|
}
|
|
14470
|
-
var
|
|
14471
|
-
var
|
|
14472
|
-
"src/
|
|
14499
|
+
var EvolutionCheckError;
|
|
14500
|
+
var init_diff_check = __esm({
|
|
14501
|
+
"src/commands/module/diff-check.ts"() {
|
|
14473
14502
|
init_api_client();
|
|
14503
|
+
init_manifest_evolution();
|
|
14474
14504
|
init_output();
|
|
14475
|
-
|
|
14476
|
-
EXIT_TIMED_OUT = 4;
|
|
14477
|
-
REBUILD_BUMP_OPERATOR_ONLY = "REBUILD_BUMP_OPERATOR_ONLY";
|
|
14478
|
-
FEATURE_SUBJECTS = {
|
|
14479
|
-
module_runtime_streaming: "Streaming",
|
|
14480
|
-
module_rebuild: "Rebuild",
|
|
14481
|
-
module_building: "Building modules",
|
|
14482
|
-
module_runtime_sizing: "Memory size"
|
|
14483
|
-
};
|
|
14484
|
-
OPTION_SUBJECTS = {
|
|
14485
|
-
streaming: "Streaming",
|
|
14486
|
-
memory_mb: "Memory size",
|
|
14487
|
-
reapply: "Re-applying the runtime",
|
|
14488
|
-
timeout: "The timeout",
|
|
14489
|
-
reserved_concurrency: "Reserved concurrency",
|
|
14490
|
-
log_retention: "Log retention"
|
|
14491
|
-
};
|
|
14492
|
-
MODULE_RIGHT_CODES = /* @__PURE__ */ new Set([
|
|
14493
|
-
"MODULE_CAPABILITY_DENIED",
|
|
14494
|
-
"PUBLISH_REQUIRES_ADMIN",
|
|
14495
|
-
"NOT_COPY_OWNER"
|
|
14496
|
-
]);
|
|
14497
|
-
ANSI = /\x1b\[[0-9;]*m/g;
|
|
14498
|
-
MODULE_KEY_REFUSED = {
|
|
14499
|
-
env: "The module key in DASHWISE_API_KEY was refused (expired or revoked). Replace it with a current key, or unset it to use this module's saved key. dashwise login does not renew module keys.",
|
|
14500
|
-
env_file: "The module key in .env.local (DASHWISE_API_KEY) was refused (expired or revoked). Replace it, or remove that line to use this module's saved key. dashwise login does not renew module keys.",
|
|
14501
|
-
saved: `This module's saved key was refused (expired or revoked). In your CLI auth.json, set only modules[<slug>].apiKey to an empty string (""), keeping installationId and all other fields. Then run dashwise dev from this module's directory with DASHWISE_API_KEY unset to mint and save a replacement key for the same installation. dashwise login does not renew module keys.`
|
|
14505
|
+
EvolutionCheckError = class extends Error {
|
|
14502
14506
|
};
|
|
14503
14507
|
}
|
|
14504
14508
|
});
|
|
@@ -19243,6 +19247,150 @@ function agentSessionMarker(env = process.env) {
|
|
|
19243
19247
|
return null;
|
|
19244
19248
|
}
|
|
19245
19249
|
|
|
19250
|
+
// src/lib/go-live.ts
|
|
19251
|
+
init_api_client();
|
|
19252
|
+
init_output();
|
|
19253
|
+
init_permission_hint();
|
|
19254
|
+
init_scaffold();
|
|
19255
|
+
var EXIT_AWAITING_PERSON = 5;
|
|
19256
|
+
var DEFAULT_GO_LIVE_WAIT_SEC = 900;
|
|
19257
|
+
var AGENT_GO_LIVE_WAIT_SEC = 240;
|
|
19258
|
+
function isGoLiveNeedsPerson(err) {
|
|
19259
|
+
return err instanceof ApiError && err.code === "GO_LIVE_NEEDS_PERSON";
|
|
19260
|
+
}
|
|
19261
|
+
function liveTabLink(apiUrl, moduleId, installationId) {
|
|
19262
|
+
const path = `/modules?module=${moduleId}&tab=live©=${installationId}`;
|
|
19263
|
+
const origin = hostedAppOrigin(apiUrl);
|
|
19264
|
+
return origin ? `${origin}${path}` : path;
|
|
19265
|
+
}
|
|
19266
|
+
async function proposeGoLive(args, auth) {
|
|
19267
|
+
const emit = (exit2, request2, extra = {}) => {
|
|
19268
|
+
if (args.json)
|
|
19269
|
+
out(
|
|
19270
|
+
JSON.stringify({
|
|
19271
|
+
go_live_request_id: request2?.id ?? null,
|
|
19272
|
+
installation_id: args.installationId,
|
|
19273
|
+
version: args.version,
|
|
19274
|
+
status: request2?.status ?? null,
|
|
19275
|
+
outcome: request2?.outcome ?? null,
|
|
19276
|
+
reason: request2?.reason ?? null,
|
|
19277
|
+
approve_at: liveTabLink(args.apiUrl, args.moduleId, args.installationId),
|
|
19278
|
+
awaiting_person: exit2 === EXIT_AWAITING_PERSON,
|
|
19279
|
+
...extra
|
|
19280
|
+
})
|
|
19281
|
+
);
|
|
19282
|
+
return exit2;
|
|
19283
|
+
};
|
|
19284
|
+
let filed;
|
|
19285
|
+
try {
|
|
19286
|
+
filed = await apiRequest(
|
|
19287
|
+
"POST",
|
|
19288
|
+
`/api/modules/${args.moduleId}/go-live-requests`,
|
|
19289
|
+
{ installation_id: args.installationId, version: args.version },
|
|
19290
|
+
auth
|
|
19291
|
+
);
|
|
19292
|
+
} catch (err) {
|
|
19293
|
+
if (err instanceof ApiError && err.code === "ALREADY_LIVE") {
|
|
19294
|
+
success(`Installation ${args.installationId} already runs v${args.version}.`);
|
|
19295
|
+
return emit(0, null, { already_live: true });
|
|
19296
|
+
}
|
|
19297
|
+
const why = err instanceof ApiError ? `${err.code || err.status} ${err.message}` : err.message;
|
|
19298
|
+
fail(`Could not propose v${args.version} for installation ${args.installationId}: ${why}`);
|
|
19299
|
+
return emit(1, null, { error: err instanceof ApiError ? err.code || err.status : "NETWORK" });
|
|
19300
|
+
}
|
|
19301
|
+
const link = liveTabLink(args.apiUrl, args.moduleId, args.installationId);
|
|
19302
|
+
info(
|
|
19303
|
+
`Proposed v${args.version} for installation ${args.installationId}` + (filed.from_version ? ` (runs v${filed.from_version} now)` : "") + `. A person takes it live in Dashwise: ${code(link)}`
|
|
19304
|
+
);
|
|
19305
|
+
if (args.waitSec <= 0) {
|
|
19306
|
+
info(awaitingLine(filed.request.id, args.checkCommand));
|
|
19307
|
+
return emit(EXIT_AWAITING_PERSON, filed.request);
|
|
19308
|
+
}
|
|
19309
|
+
const { exit, request } = await waitForGoLive(filed.request.id, args, auth);
|
|
19310
|
+
return emit(exit, request ?? filed.request);
|
|
19311
|
+
}
|
|
19312
|
+
function awaitingLine(requestId, checkCommand) {
|
|
19313
|
+
return `Go-live request #${requestId} waits for a person; nothing was installed. ${checkAgain(checkCommand)}`;
|
|
19314
|
+
}
|
|
19315
|
+
function checkAgain(checkCommand) {
|
|
19316
|
+
return checkCommand ? `Check again with ${code(checkCommand)} (it files nothing new while this one is open).` : "Run the same command again to check (it files nothing new while this one is open).";
|
|
19317
|
+
}
|
|
19318
|
+
function isFinalClientError(err) {
|
|
19319
|
+
return err instanceof ApiError && err.status >= 400 && err.status < 500 && err.status !== 429;
|
|
19320
|
+
}
|
|
19321
|
+
async function waitForGoLive(requestId, args, auth) {
|
|
19322
|
+
const interval = args.pollIntervalMs ?? 5e3;
|
|
19323
|
+
const deadline = Date.now() + args.waitSec * 1e3;
|
|
19324
|
+
info(`Waiting for the approval (go-live request #${requestId}, up to ${formatWait(args.waitSec)})\u2026`);
|
|
19325
|
+
let last = null;
|
|
19326
|
+
let announcedApproval = false;
|
|
19327
|
+
for (; ; ) {
|
|
19328
|
+
let request = null;
|
|
19329
|
+
try {
|
|
19330
|
+
({ request } = await apiRequest(
|
|
19331
|
+
"GET",
|
|
19332
|
+
`/api/modules/${args.moduleId}/go-live-requests/${requestId}`,
|
|
19333
|
+
void 0,
|
|
19334
|
+
auth
|
|
19335
|
+
));
|
|
19336
|
+
last = request;
|
|
19337
|
+
} catch (err) {
|
|
19338
|
+
const why = err instanceof ApiError ? `${err.code || err.status}` : err.message;
|
|
19339
|
+
if (isFinalClientError(err)) {
|
|
19340
|
+
fail(`Could not read go-live request #${requestId} (${why}). Check it on the module's Live tab.`);
|
|
19341
|
+
return { exit: 1, request: last };
|
|
19342
|
+
}
|
|
19343
|
+
warn(`Could not read go-live request #${requestId} (${why}); trying again.`);
|
|
19344
|
+
}
|
|
19345
|
+
if (request && request.status !== "pending") {
|
|
19346
|
+
if (request.status !== "approved" || request.outcome !== null) {
|
|
19347
|
+
return { exit: reportOutcome(request, args), request };
|
|
19348
|
+
}
|
|
19349
|
+
if (!announcedApproval) {
|
|
19350
|
+
info(`A person approved it; v${args.version} is going live on installation ${args.installationId}\u2026`);
|
|
19351
|
+
announcedApproval = true;
|
|
19352
|
+
}
|
|
19353
|
+
}
|
|
19354
|
+
if (Date.now() + interval > deadline) {
|
|
19355
|
+
warn(
|
|
19356
|
+
announcedApproval ? `Go-live request #${requestId} was approved and is still going live. ${checkAgain(args.checkCommand)}` : awaitingLine(requestId, args.checkCommand)
|
|
19357
|
+
);
|
|
19358
|
+
return { exit: EXIT_AWAITING_PERSON, request: last };
|
|
19359
|
+
}
|
|
19360
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
19361
|
+
}
|
|
19362
|
+
}
|
|
19363
|
+
function formatWait(sec) {
|
|
19364
|
+
return sec < 120 ? `${sec} s` : `${Math.round(sec / 60)} min`;
|
|
19365
|
+
}
|
|
19366
|
+
function parseWaitSeconds(value) {
|
|
19367
|
+
const trimmed = value.trim();
|
|
19368
|
+
if (!/^[0-9]+$/.test(trimmed)) {
|
|
19369
|
+
throw new InvalidArgumentError("Expected whole seconds, 0 or more.");
|
|
19370
|
+
}
|
|
19371
|
+
return Number(trimmed);
|
|
19372
|
+
}
|
|
19373
|
+
function reportOutcome(request, args) {
|
|
19374
|
+
switch (request.outcome) {
|
|
19375
|
+
case "live":
|
|
19376
|
+
success(`v${args.version} is live on installation ${args.installationId}.`);
|
|
19377
|
+
return 0;
|
|
19378
|
+
case "declined":
|
|
19379
|
+
fail(
|
|
19380
|
+
`A person declined v${args.version} for installation ${args.installationId}` + (request.reason ? `: ${request.reason}` : ".")
|
|
19381
|
+
);
|
|
19382
|
+
return EXIT_NEEDS_GRANT;
|
|
19383
|
+
case "superseded":
|
|
19384
|
+
warn("A newer proposal for this installation replaced this one.");
|
|
19385
|
+
return EXIT_NEEDS_GRANT;
|
|
19386
|
+
default:
|
|
19387
|
+
fail(
|
|
19388
|
+
`The update did not go through after the approval (${request.reason ?? request.outcome ?? request.status}). Nothing changed; propose again.`
|
|
19389
|
+
);
|
|
19390
|
+
return 1;
|
|
19391
|
+
}
|
|
19392
|
+
}
|
|
19393
|
+
|
|
19246
19394
|
// src/lib/drift-preview.ts
|
|
19247
19395
|
init_api_client();
|
|
19248
19396
|
function redactedConsumer(redacted) {
|
|
@@ -19578,6 +19726,17 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
19578
19726
|
);
|
|
19579
19727
|
}
|
|
19580
19728
|
}
|
|
19729
|
+
const agentMarker = agentSessionMarker();
|
|
19730
|
+
if (agentMarker !== null) {
|
|
19731
|
+
if (!existing) {
|
|
19732
|
+
fail(
|
|
19733
|
+
`This is an AI agent session (${code(agentMarker)} is set), and ${code(mod.slug)} has no production installation in ${code(workspace2.slug)} yet: a person creates the first one in Dashwise.`
|
|
19734
|
+
);
|
|
19735
|
+
return 1;
|
|
19736
|
+
}
|
|
19737
|
+
info(`AI agent session (${code(agentMarker)}): proposing instead of installing; a person approves it.`);
|
|
19738
|
+
return goLiveByProposal(mod, existing.id, targetVersion, options, auth);
|
|
19739
|
+
}
|
|
19581
19740
|
let preview;
|
|
19582
19741
|
let lossy = false;
|
|
19583
19742
|
let lossAccepted = false;
|
|
@@ -19639,6 +19798,25 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
19639
19798
|
);
|
|
19640
19799
|
break;
|
|
19641
19800
|
} catch (err) {
|
|
19801
|
+
if (isGoLiveNeedsPerson(err)) {
|
|
19802
|
+
if (!existing) {
|
|
19803
|
+
fail(
|
|
19804
|
+
`Taking ${code(mod.slug)} live in ${code(workspace2.slug)} is done by a person in Dashwise: create its first installation there.`
|
|
19805
|
+
);
|
|
19806
|
+
if (devInstallReplaced) printDevInstallAftermath(mod.slug, workspace2.slug);
|
|
19807
|
+
return 1;
|
|
19808
|
+
}
|
|
19809
|
+
info("Dashwise takes modules live through a person: proposing this update instead.");
|
|
19810
|
+
const proposed = await goLiveByProposal(
|
|
19811
|
+
mod,
|
|
19812
|
+
existing.id,
|
|
19813
|
+
confirmedPlan?.to_version ?? targetVersion,
|
|
19814
|
+
options,
|
|
19815
|
+
auth
|
|
19816
|
+
);
|
|
19817
|
+
if (devInstallReplaced) printDevInstallAftermath(mod.slug, workspace2.slug);
|
|
19818
|
+
return proposed;
|
|
19819
|
+
}
|
|
19642
19820
|
let plan = null;
|
|
19643
19821
|
if (confirmedPlan && err instanceof ApiError && err.code === "DRIFT_CHANGED" && round < MAX_PLAN_ROUNDS) {
|
|
19644
19822
|
warn(
|
|
@@ -20094,6 +20272,22 @@ function rerunLine(slug, workspaceSlug, targetVersion, installationId, options,
|
|
|
20094
20272
|
`--accept-data-loss ${hash}`
|
|
20095
20273
|
].join(" ");
|
|
20096
20274
|
}
|
|
20275
|
+
function goLiveByProposal(mod, installationId, version, options, auth) {
|
|
20276
|
+
return proposeGoLive(
|
|
20277
|
+
{
|
|
20278
|
+
moduleId: mod.id,
|
|
20279
|
+
moduleSlug: mod.slug,
|
|
20280
|
+
installationId,
|
|
20281
|
+
version,
|
|
20282
|
+
apiUrl: auth.baseUrl,
|
|
20283
|
+
waitSec: options.waitSec ?? (agentSessionMarker() !== null ? AGENT_GO_LIVE_WAIT_SEC : DEFAULT_GO_LIVE_WAIT_SEC),
|
|
20284
|
+
...options.pollIntervalMs !== void 0 ? { pollIntervalMs: options.pollIntervalMs } : {},
|
|
20285
|
+
...options.json ? { json: true } : {},
|
|
20286
|
+
...options.goLiveCheckCommand ? { checkCommand: options.goLiveCheckCommand } : {}
|
|
20287
|
+
},
|
|
20288
|
+
auth
|
|
20289
|
+
);
|
|
20290
|
+
}
|
|
20097
20291
|
|
|
20098
20292
|
// src/commands/module/list.ts
|
|
20099
20293
|
init_api_client();
|
|
@@ -21106,10 +21300,9 @@ init_permission_hint();
|
|
|
21106
21300
|
async function moduleShipCommand(slugArg, options) {
|
|
21107
21301
|
const marker = agentSessionMarker();
|
|
21108
21302
|
if (marker !== null) {
|
|
21109
|
-
|
|
21110
|
-
|
|
21303
|
+
info(
|
|
21304
|
+
`AI agent session (${code(marker)}): \`ship\` releases, then proposes the update; a person takes it live in Dashwise.`
|
|
21111
21305
|
);
|
|
21112
|
-
return 2;
|
|
21113
21306
|
}
|
|
21114
21307
|
if (options.build !== void 0 && options.commitSha !== void 0) {
|
|
21115
21308
|
fail("--build resumes an existing release; do not combine it with --commit-sha.");
|
|
@@ -21150,15 +21343,30 @@ async function moduleShipCommand(slugArg, options) {
|
|
|
21150
21343
|
return 1;
|
|
21151
21344
|
}
|
|
21152
21345
|
info(`Installing v${build.version} (build #${build.id}, revision ${build.commit_sha.slice(0, 12)}).`);
|
|
21153
|
-
|
|
21346
|
+
const checkCommand = [
|
|
21347
|
+
"dashwise module install",
|
|
21348
|
+
slugArg,
|
|
21349
|
+
`--version ${build.version}`,
|
|
21350
|
+
options.dir !== void 0 ? `--dir ${shellWord(options.dir)}` : void 0,
|
|
21351
|
+
options.workspace !== void 0 ? `--workspace ${shellWord(options.workspace)}` : void 0,
|
|
21352
|
+
options.installation !== void 0 ? `--installation ${options.installation}` : void 0
|
|
21353
|
+
].filter(Boolean).join(" ");
|
|
21354
|
+
const installExit = await moduleInstallCommand(slugArg, {
|
|
21154
21355
|
...options.dir !== void 0 ? { dir: options.dir } : {},
|
|
21155
21356
|
...options.workspace !== void 0 ? { workspace: options.workspace } : {},
|
|
21156
21357
|
...options.installation !== void 0 ? { installation: options.installation } : {},
|
|
21157
21358
|
...options.keepDevInstall !== void 0 ? { keepDevInstall: options.keepDevInstall } : {},
|
|
21158
21359
|
...options.replaceDevInstall !== void 0 ? { replaceDevInstall: options.replaceDevInstall } : {},
|
|
21159
21360
|
...options.yes !== void 0 ? { yes: options.yes } : {},
|
|
21361
|
+
...options.waitSec !== void 0 ? { waitSec: options.waitSec } : {},
|
|
21362
|
+
...options.pollIntervalMs !== void 0 ? { pollIntervalMs: options.pollIntervalMs } : {},
|
|
21363
|
+
goLiveCheckCommand: checkCommand,
|
|
21160
21364
|
version: build.version
|
|
21161
21365
|
});
|
|
21366
|
+
if (installExit === EXIT_AWAITING_PERSON) {
|
|
21367
|
+
info(`v${build.version} is released; only its go-live waits for a person (not a build: do not ship again).`);
|
|
21368
|
+
}
|
|
21369
|
+
return installExit;
|
|
21162
21370
|
}
|
|
21163
21371
|
async function defaultBranchHead(slugArg, dir) {
|
|
21164
21372
|
const cfg = readConfig();
|
|
@@ -21192,6 +21400,9 @@ async function defaultBranchHead(slugArg, dir) {
|
|
|
21192
21400
|
return void 0;
|
|
21193
21401
|
}
|
|
21194
21402
|
}
|
|
21403
|
+
function shellWord(value) {
|
|
21404
|
+
return /^[\w./@:=-]+$/.test(value) ? value : `'${value.replace(/'/g, `'\\''`)}'`;
|
|
21405
|
+
}
|
|
21195
21406
|
|
|
21196
21407
|
// src/commands/module/status.ts
|
|
21197
21408
|
init_api_client();
|
|
@@ -27156,8 +27367,12 @@ moduleCmd.command("build-log [slug] [buildId]").description(
|
|
|
27156
27367
|
}
|
|
27157
27368
|
);
|
|
27158
27369
|
moduleCmd.command("ship [slug]").description(
|
|
27159
|
-
"Release and install in one command
|
|
27160
|
-
).option("--dir <path>", "Project root for module.json slug lookup (default: current directory).").option("--commit-sha <sha>", "Release this pushed revision (full 40-character SHA) instead of the default-branch head.").option("--build <id>", "Resume a ship whose release was still building: wait for that build, then install it. Never queues a release.", parsePositiveIdOption).option("--bump <direction>", "Semver bump: patch | minor | major (default minor).").option("--workspace <slug-or-id>", "Target workspace (as for module install).").option("--installation <id>", "Update exactly this production installation.", parsePositiveIdOption).option("--keep-dev-install", "Install alongside the workspace's dev install (as for module install).").option("--replace-dev-install", "Replace the workspace's dev install (as for module install).").option("-y, --yes", "Skip the install question when nothing is lost. Never accepts data loss.").option("--timeout <seconds>", "Max seconds to wait for the build (default 360); exit 4 means still building.", Number).
|
|
27370
|
+
"Release and install in one command: releases the latest push to the default branch (or --commit-sha), waits for the build, then installs that version, showing what the update does to the data and asking once. Inside an AI agent session (or when Dashwise requires it) it proposes the update instead and waits for a person to take it live on the Live tab."
|
|
27371
|
+
).option("--dir <path>", "Project root for module.json slug lookup (default: current directory).").option("--commit-sha <sha>", "Release this pushed revision (full 40-character SHA) instead of the default-branch head.").option("--build <id>", "Resume a ship whose release was still building: wait for that build, then install it. Never queues a release.", parsePositiveIdOption).option("--bump <direction>", "Semver bump: patch | minor | major (default minor).").option("--workspace <slug-or-id>", "Target workspace (as for module install).").option("--installation <id>", "Update exactly this production installation.", parsePositiveIdOption).option("--keep-dev-install", "Install alongside the workspace's dev install (as for module install).").option("--replace-dev-install", "Replace the workspace's dev install (as for module install).").option("-y, --yes", "Skip the install question when nothing is lost. Never accepts data loss.").option("--timeout <seconds>", "Max seconds to wait for the build (default 360); exit 4 means still building.", Number).option(
|
|
27372
|
+
"--wait <seconds>",
|
|
27373
|
+
"Max seconds to wait for a person to approve a go-live proposal (default 900, 240 inside an AI agent session; 0 proposes and returns). Exit 5: the proposal still waits; run the same command again to check (exit 4 is a release still building).",
|
|
27374
|
+
parseWaitSeconds
|
|
27375
|
+
).action(
|
|
27161
27376
|
async (slug, opts) => {
|
|
27162
27377
|
process.exitCode = await moduleShipCommand(slug, {
|
|
27163
27378
|
...opts.dir !== void 0 ? { dir: opts.dir } : {},
|
|
@@ -27169,7 +27384,8 @@ moduleCmd.command("ship [slug]").description(
|
|
|
27169
27384
|
...opts.keepDevInstall !== void 0 ? { keepDevInstall: opts.keepDevInstall } : {},
|
|
27170
27385
|
...opts.replaceDevInstall !== void 0 ? { replaceDevInstall: opts.replaceDevInstall } : {},
|
|
27171
27386
|
...opts.yes !== void 0 ? { yes: opts.yes } : {},
|
|
27172
|
-
...opts.timeout !== void 0 ? { timeoutSec: opts.timeout } : {}
|
|
27387
|
+
...opts.timeout !== void 0 ? { timeoutSec: opts.timeout } : {},
|
|
27388
|
+
...opts.wait !== void 0 ? { waitSec: opts.wait } : {}
|
|
27173
27389
|
});
|
|
27174
27390
|
}
|
|
27175
27391
|
);
|
|
@@ -27230,7 +27446,11 @@ moduleCmd.command("install [slug]").alias("deploy").alias("upgrade").description
|
|
|
27230
27446
|
).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(
|
|
27231
27447
|
"--accept-data-loss <drift_hash>",
|
|
27232
27448
|
"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)."
|
|
27233
|
-
).option("--json", "Emit the final result as machine-readable JSON.").
|
|
27449
|
+
).option("--json", "Emit the final result as machine-readable JSON.").option(
|
|
27450
|
+
"--wait <seconds>",
|
|
27451
|
+
"Max seconds to wait for a person to approve a go-live proposal (made inside an AI agent session or when Dashwise requires a person; default 900, 240 inside an AI agent session; 0 proposes and returns). Exit 5: the proposal still waits; run the same command again to check, it files nothing new.",
|
|
27452
|
+
parseWaitSeconds
|
|
27453
|
+
).action(
|
|
27234
27454
|
async (slug, cmdOpts) => {
|
|
27235
27455
|
process.exitCode = await moduleInstallCommand(slug, {
|
|
27236
27456
|
...cmdOpts.installation !== void 0 ? { installation: cmdOpts.installation } : {},
|
|
@@ -27242,7 +27462,8 @@ moduleCmd.command("install [slug]").alias("deploy").alias("upgrade").description
|
|
|
27242
27462
|
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
27243
27463
|
...cmdOpts.yes !== void 0 ? { yes: cmdOpts.yes } : {},
|
|
27244
27464
|
...cmdOpts.acceptDataLoss !== void 0 ? { acceptDataLoss: cmdOpts.acceptDataLoss } : {},
|
|
27245
|
-
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
27465
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {},
|
|
27466
|
+
...cmdOpts.wait !== void 0 ? { waitSec: cmdOpts.wait } : {}
|
|
27246
27467
|
});
|
|
27247
27468
|
}
|
|
27248
27469
|
);
|
|
@@ -27596,7 +27817,7 @@ fieldCmd.command("set-type <table-slug> <field-slug> <new-type>").description(
|
|
|
27596
27817
|
}
|
|
27597
27818
|
);
|
|
27598
27819
|
function getVersion() {
|
|
27599
|
-
return "0.
|
|
27820
|
+
return "0.31.0";
|
|
27600
27821
|
}
|
|
27601
27822
|
function parsePositiveIdOption(value) {
|
|
27602
27823
|
if (!/^[1-9][0-9]*$/.test(value.trim())) {
|