@dashai/cli 0.30.2 → 0.31.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/bin.js +641 -408
- 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
|
});
|
|
@@ -18906,7 +18910,13 @@ async function moduleGenerateCommand(options) {
|
|
|
18906
18910
|
{
|
|
18907
18911
|
manifest,
|
|
18908
18912
|
generation: options.generation ?? 0
|
|
18909
|
-
}
|
|
18913
|
+
},
|
|
18914
|
+
// The installed @dashai/sdk decides the client's bootstrap (backend
|
|
18915
|
+
// generator 3.3.0): from 2.13.0 it reads DASHWISE_API_KEY_FILE (a
|
|
18916
|
+
// Workbench's key); older or none, the client every 2.x SDK links. A
|
|
18917
|
+
// header, not a body field: an older backend refuses unknown body
|
|
18918
|
+
// fields (forbidNonWhitelisted) but ignores an unknown header.
|
|
18919
|
+
{ headers: sdkVersionHeader(projectRoot) }
|
|
18910
18920
|
);
|
|
18911
18921
|
} catch (err) {
|
|
18912
18922
|
if (err instanceof ApiError && err.status === 401) {
|
|
@@ -18960,6 +18970,12 @@ async function moduleGenerateCommand(options) {
|
|
|
18960
18970
|
);
|
|
18961
18971
|
return 0;
|
|
18962
18972
|
}
|
|
18973
|
+
var SDK_VERSION_HEADER = "Dashwise-Sdk-Version";
|
|
18974
|
+
var SDK_VERSION_PATTERN = /^v?\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$/;
|
|
18975
|
+
function sdkVersionHeader(projectRoot) {
|
|
18976
|
+
const version = readInstalledSdk(projectRoot)?.version;
|
|
18977
|
+
return version !== void 0 && version.length <= 64 && SDK_VERSION_PATTERN.test(version) ? { [SDK_VERSION_HEADER]: version } : {};
|
|
18978
|
+
}
|
|
18963
18979
|
function declaresUses(manifest) {
|
|
18964
18980
|
const uses = manifest?.uses;
|
|
18965
18981
|
return Array.isArray(uses?.tables) && uses.tables.length > 0;
|
|
@@ -19243,6 +19259,150 @@ function agentSessionMarker(env = process.env) {
|
|
|
19243
19259
|
return null;
|
|
19244
19260
|
}
|
|
19245
19261
|
|
|
19262
|
+
// src/lib/go-live.ts
|
|
19263
|
+
init_api_client();
|
|
19264
|
+
init_output();
|
|
19265
|
+
init_permission_hint();
|
|
19266
|
+
init_scaffold();
|
|
19267
|
+
var EXIT_AWAITING_PERSON = 5;
|
|
19268
|
+
var DEFAULT_GO_LIVE_WAIT_SEC = 900;
|
|
19269
|
+
var AGENT_GO_LIVE_WAIT_SEC = 240;
|
|
19270
|
+
function isGoLiveNeedsPerson(err) {
|
|
19271
|
+
return err instanceof ApiError && err.code === "GO_LIVE_NEEDS_PERSON";
|
|
19272
|
+
}
|
|
19273
|
+
function liveTabLink(apiUrl, moduleId, installationId) {
|
|
19274
|
+
const path = `/modules?module=${moduleId}&tab=live©=${installationId}`;
|
|
19275
|
+
const origin = hostedAppOrigin(apiUrl);
|
|
19276
|
+
return origin ? `${origin}${path}` : path;
|
|
19277
|
+
}
|
|
19278
|
+
async function proposeGoLive(args, auth) {
|
|
19279
|
+
const emit = (exit2, request2, extra = {}) => {
|
|
19280
|
+
if (args.json)
|
|
19281
|
+
out(
|
|
19282
|
+
JSON.stringify({
|
|
19283
|
+
go_live_request_id: request2?.id ?? null,
|
|
19284
|
+
installation_id: args.installationId,
|
|
19285
|
+
version: args.version,
|
|
19286
|
+
status: request2?.status ?? null,
|
|
19287
|
+
outcome: request2?.outcome ?? null,
|
|
19288
|
+
reason: request2?.reason ?? null,
|
|
19289
|
+
approve_at: liveTabLink(args.apiUrl, args.moduleId, args.installationId),
|
|
19290
|
+
awaiting_person: exit2 === EXIT_AWAITING_PERSON,
|
|
19291
|
+
...extra
|
|
19292
|
+
})
|
|
19293
|
+
);
|
|
19294
|
+
return exit2;
|
|
19295
|
+
};
|
|
19296
|
+
let filed;
|
|
19297
|
+
try {
|
|
19298
|
+
filed = await apiRequest(
|
|
19299
|
+
"POST",
|
|
19300
|
+
`/api/modules/${args.moduleId}/go-live-requests`,
|
|
19301
|
+
{ installation_id: args.installationId, version: args.version },
|
|
19302
|
+
auth
|
|
19303
|
+
);
|
|
19304
|
+
} catch (err) {
|
|
19305
|
+
if (err instanceof ApiError && err.code === "ALREADY_LIVE") {
|
|
19306
|
+
success(`Installation ${args.installationId} already runs v${args.version}.`);
|
|
19307
|
+
return emit(0, null, { already_live: true });
|
|
19308
|
+
}
|
|
19309
|
+
const why = err instanceof ApiError ? `${err.code || err.status} ${err.message}` : err.message;
|
|
19310
|
+
fail(`Could not propose v${args.version} for installation ${args.installationId}: ${why}`);
|
|
19311
|
+
return emit(1, null, { error: err instanceof ApiError ? err.code || err.status : "NETWORK" });
|
|
19312
|
+
}
|
|
19313
|
+
const link = liveTabLink(args.apiUrl, args.moduleId, args.installationId);
|
|
19314
|
+
info(
|
|
19315
|
+
`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)}`
|
|
19316
|
+
);
|
|
19317
|
+
if (args.waitSec <= 0) {
|
|
19318
|
+
info(awaitingLine(filed.request.id, args.checkCommand));
|
|
19319
|
+
return emit(EXIT_AWAITING_PERSON, filed.request);
|
|
19320
|
+
}
|
|
19321
|
+
const { exit, request } = await waitForGoLive(filed.request.id, args, auth);
|
|
19322
|
+
return emit(exit, request ?? filed.request);
|
|
19323
|
+
}
|
|
19324
|
+
function awaitingLine(requestId, checkCommand) {
|
|
19325
|
+
return `Go-live request #${requestId} waits for a person; nothing was installed. ${checkAgain(checkCommand)}`;
|
|
19326
|
+
}
|
|
19327
|
+
function checkAgain(checkCommand) {
|
|
19328
|
+
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).";
|
|
19329
|
+
}
|
|
19330
|
+
function isFinalClientError(err) {
|
|
19331
|
+
return err instanceof ApiError && err.status >= 400 && err.status < 500 && err.status !== 429;
|
|
19332
|
+
}
|
|
19333
|
+
async function waitForGoLive(requestId, args, auth) {
|
|
19334
|
+
const interval = args.pollIntervalMs ?? 5e3;
|
|
19335
|
+
const deadline = Date.now() + args.waitSec * 1e3;
|
|
19336
|
+
info(`Waiting for the approval (go-live request #${requestId}, up to ${formatWait(args.waitSec)})\u2026`);
|
|
19337
|
+
let last = null;
|
|
19338
|
+
let announcedApproval = false;
|
|
19339
|
+
for (; ; ) {
|
|
19340
|
+
let request = null;
|
|
19341
|
+
try {
|
|
19342
|
+
({ request } = await apiRequest(
|
|
19343
|
+
"GET",
|
|
19344
|
+
`/api/modules/${args.moduleId}/go-live-requests/${requestId}`,
|
|
19345
|
+
void 0,
|
|
19346
|
+
auth
|
|
19347
|
+
));
|
|
19348
|
+
last = request;
|
|
19349
|
+
} catch (err) {
|
|
19350
|
+
const why = err instanceof ApiError ? `${err.code || err.status}` : err.message;
|
|
19351
|
+
if (isFinalClientError(err)) {
|
|
19352
|
+
fail(`Could not read go-live request #${requestId} (${why}). Check it on the module's Live tab.`);
|
|
19353
|
+
return { exit: 1, request: last };
|
|
19354
|
+
}
|
|
19355
|
+
warn(`Could not read go-live request #${requestId} (${why}); trying again.`);
|
|
19356
|
+
}
|
|
19357
|
+
if (request && request.status !== "pending") {
|
|
19358
|
+
if (request.status !== "approved" || request.outcome !== null) {
|
|
19359
|
+
return { exit: reportOutcome(request, args), request };
|
|
19360
|
+
}
|
|
19361
|
+
if (!announcedApproval) {
|
|
19362
|
+
info(`A person approved it; v${args.version} is going live on installation ${args.installationId}\u2026`);
|
|
19363
|
+
announcedApproval = true;
|
|
19364
|
+
}
|
|
19365
|
+
}
|
|
19366
|
+
if (Date.now() + interval > deadline) {
|
|
19367
|
+
warn(
|
|
19368
|
+
announcedApproval ? `Go-live request #${requestId} was approved and is still going live. ${checkAgain(args.checkCommand)}` : awaitingLine(requestId, args.checkCommand)
|
|
19369
|
+
);
|
|
19370
|
+
return { exit: EXIT_AWAITING_PERSON, request: last };
|
|
19371
|
+
}
|
|
19372
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
19373
|
+
}
|
|
19374
|
+
}
|
|
19375
|
+
function formatWait(sec) {
|
|
19376
|
+
return sec < 120 ? `${sec} s` : `${Math.round(sec / 60)} min`;
|
|
19377
|
+
}
|
|
19378
|
+
function parseWaitSeconds(value) {
|
|
19379
|
+
const trimmed = value.trim();
|
|
19380
|
+
if (!/^[0-9]+$/.test(trimmed)) {
|
|
19381
|
+
throw new InvalidArgumentError("Expected whole seconds, 0 or more.");
|
|
19382
|
+
}
|
|
19383
|
+
return Number(trimmed);
|
|
19384
|
+
}
|
|
19385
|
+
function reportOutcome(request, args) {
|
|
19386
|
+
switch (request.outcome) {
|
|
19387
|
+
case "live":
|
|
19388
|
+
success(`v${args.version} is live on installation ${args.installationId}.`);
|
|
19389
|
+
return 0;
|
|
19390
|
+
case "declined":
|
|
19391
|
+
fail(
|
|
19392
|
+
`A person declined v${args.version} for installation ${args.installationId}` + (request.reason ? `: ${request.reason}` : ".")
|
|
19393
|
+
);
|
|
19394
|
+
return EXIT_NEEDS_GRANT;
|
|
19395
|
+
case "superseded":
|
|
19396
|
+
warn("A newer proposal for this installation replaced this one.");
|
|
19397
|
+
return EXIT_NEEDS_GRANT;
|
|
19398
|
+
default:
|
|
19399
|
+
fail(
|
|
19400
|
+
`The update did not go through after the approval (${request.reason ?? request.outcome ?? request.status}). Nothing changed; propose again.`
|
|
19401
|
+
);
|
|
19402
|
+
return 1;
|
|
19403
|
+
}
|
|
19404
|
+
}
|
|
19405
|
+
|
|
19246
19406
|
// src/lib/drift-preview.ts
|
|
19247
19407
|
init_api_client();
|
|
19248
19408
|
function redactedConsumer(redacted) {
|
|
@@ -19578,6 +19738,17 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
19578
19738
|
);
|
|
19579
19739
|
}
|
|
19580
19740
|
}
|
|
19741
|
+
const agentMarker = agentSessionMarker();
|
|
19742
|
+
if (agentMarker !== null) {
|
|
19743
|
+
if (!existing) {
|
|
19744
|
+
fail(
|
|
19745
|
+
`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.`
|
|
19746
|
+
);
|
|
19747
|
+
return 1;
|
|
19748
|
+
}
|
|
19749
|
+
info(`AI agent session (${code(agentMarker)}): proposing instead of installing; a person approves it.`);
|
|
19750
|
+
return goLiveByProposal(mod, existing.id, targetVersion, options, auth);
|
|
19751
|
+
}
|
|
19581
19752
|
let preview;
|
|
19582
19753
|
let lossy = false;
|
|
19583
19754
|
let lossAccepted = false;
|
|
@@ -19639,6 +19810,25 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
19639
19810
|
);
|
|
19640
19811
|
break;
|
|
19641
19812
|
} catch (err) {
|
|
19813
|
+
if (isGoLiveNeedsPerson(err)) {
|
|
19814
|
+
if (!existing) {
|
|
19815
|
+
fail(
|
|
19816
|
+
`Taking ${code(mod.slug)} live in ${code(workspace2.slug)} is done by a person in Dashwise: create its first installation there.`
|
|
19817
|
+
);
|
|
19818
|
+
if (devInstallReplaced) printDevInstallAftermath(mod.slug, workspace2.slug);
|
|
19819
|
+
return 1;
|
|
19820
|
+
}
|
|
19821
|
+
info("Dashwise takes modules live through a person: proposing this update instead.");
|
|
19822
|
+
const proposed = await goLiveByProposal(
|
|
19823
|
+
mod,
|
|
19824
|
+
existing.id,
|
|
19825
|
+
confirmedPlan?.to_version ?? targetVersion,
|
|
19826
|
+
options,
|
|
19827
|
+
auth
|
|
19828
|
+
);
|
|
19829
|
+
if (devInstallReplaced) printDevInstallAftermath(mod.slug, workspace2.slug);
|
|
19830
|
+
return proposed;
|
|
19831
|
+
}
|
|
19642
19832
|
let plan = null;
|
|
19643
19833
|
if (confirmedPlan && err instanceof ApiError && err.code === "DRIFT_CHANGED" && round < MAX_PLAN_ROUNDS) {
|
|
19644
19834
|
warn(
|
|
@@ -20094,6 +20284,22 @@ function rerunLine(slug, workspaceSlug, targetVersion, installationId, options,
|
|
|
20094
20284
|
`--accept-data-loss ${hash}`
|
|
20095
20285
|
].join(" ");
|
|
20096
20286
|
}
|
|
20287
|
+
function goLiveByProposal(mod, installationId, version, options, auth) {
|
|
20288
|
+
return proposeGoLive(
|
|
20289
|
+
{
|
|
20290
|
+
moduleId: mod.id,
|
|
20291
|
+
moduleSlug: mod.slug,
|
|
20292
|
+
installationId,
|
|
20293
|
+
version,
|
|
20294
|
+
apiUrl: auth.baseUrl,
|
|
20295
|
+
waitSec: options.waitSec ?? (agentSessionMarker() !== null ? AGENT_GO_LIVE_WAIT_SEC : DEFAULT_GO_LIVE_WAIT_SEC),
|
|
20296
|
+
...options.pollIntervalMs !== void 0 ? { pollIntervalMs: options.pollIntervalMs } : {},
|
|
20297
|
+
...options.json ? { json: true } : {},
|
|
20298
|
+
...options.goLiveCheckCommand ? { checkCommand: options.goLiveCheckCommand } : {}
|
|
20299
|
+
},
|
|
20300
|
+
auth
|
|
20301
|
+
);
|
|
20302
|
+
}
|
|
20097
20303
|
|
|
20098
20304
|
// src/commands/module/list.ts
|
|
20099
20305
|
init_api_client();
|
|
@@ -21106,10 +21312,9 @@ init_permission_hint();
|
|
|
21106
21312
|
async function moduleShipCommand(slugArg, options) {
|
|
21107
21313
|
const marker = agentSessionMarker();
|
|
21108
21314
|
if (marker !== null) {
|
|
21109
|
-
|
|
21110
|
-
|
|
21315
|
+
info(
|
|
21316
|
+
`AI agent session (${code(marker)}): \`ship\` releases, then proposes the update; a person takes it live in Dashwise.`
|
|
21111
21317
|
);
|
|
21112
|
-
return 2;
|
|
21113
21318
|
}
|
|
21114
21319
|
if (options.build !== void 0 && options.commitSha !== void 0) {
|
|
21115
21320
|
fail("--build resumes an existing release; do not combine it with --commit-sha.");
|
|
@@ -21150,15 +21355,30 @@ async function moduleShipCommand(slugArg, options) {
|
|
|
21150
21355
|
return 1;
|
|
21151
21356
|
}
|
|
21152
21357
|
info(`Installing v${build.version} (build #${build.id}, revision ${build.commit_sha.slice(0, 12)}).`);
|
|
21153
|
-
|
|
21358
|
+
const checkCommand = [
|
|
21359
|
+
"dashwise module install",
|
|
21360
|
+
slugArg,
|
|
21361
|
+
`--version ${build.version}`,
|
|
21362
|
+
options.dir !== void 0 ? `--dir ${shellWord(options.dir)}` : void 0,
|
|
21363
|
+
options.workspace !== void 0 ? `--workspace ${shellWord(options.workspace)}` : void 0,
|
|
21364
|
+
options.installation !== void 0 ? `--installation ${options.installation}` : void 0
|
|
21365
|
+
].filter(Boolean).join(" ");
|
|
21366
|
+
const installExit = await moduleInstallCommand(slugArg, {
|
|
21154
21367
|
...options.dir !== void 0 ? { dir: options.dir } : {},
|
|
21155
21368
|
...options.workspace !== void 0 ? { workspace: options.workspace } : {},
|
|
21156
21369
|
...options.installation !== void 0 ? { installation: options.installation } : {},
|
|
21157
21370
|
...options.keepDevInstall !== void 0 ? { keepDevInstall: options.keepDevInstall } : {},
|
|
21158
21371
|
...options.replaceDevInstall !== void 0 ? { replaceDevInstall: options.replaceDevInstall } : {},
|
|
21159
21372
|
...options.yes !== void 0 ? { yes: options.yes } : {},
|
|
21373
|
+
...options.waitSec !== void 0 ? { waitSec: options.waitSec } : {},
|
|
21374
|
+
...options.pollIntervalMs !== void 0 ? { pollIntervalMs: options.pollIntervalMs } : {},
|
|
21375
|
+
goLiveCheckCommand: checkCommand,
|
|
21160
21376
|
version: build.version
|
|
21161
21377
|
});
|
|
21378
|
+
if (installExit === EXIT_AWAITING_PERSON) {
|
|
21379
|
+
info(`v${build.version} is released; only its go-live waits for a person (not a build: do not ship again).`);
|
|
21380
|
+
}
|
|
21381
|
+
return installExit;
|
|
21162
21382
|
}
|
|
21163
21383
|
async function defaultBranchHead(slugArg, dir) {
|
|
21164
21384
|
const cfg = readConfig();
|
|
@@ -21192,6 +21412,9 @@ async function defaultBranchHead(slugArg, dir) {
|
|
|
21192
21412
|
return void 0;
|
|
21193
21413
|
}
|
|
21194
21414
|
}
|
|
21415
|
+
function shellWord(value) {
|
|
21416
|
+
return /^[\w./@:=-]+$/.test(value) ? value : `'${value.replace(/'/g, `'\\''`)}'`;
|
|
21417
|
+
}
|
|
21195
21418
|
|
|
21196
21419
|
// src/commands/module/status.ts
|
|
21197
21420
|
init_api_client();
|
|
@@ -27156,8 +27379,12 @@ moduleCmd.command("build-log [slug] [buildId]").description(
|
|
|
27156
27379
|
}
|
|
27157
27380
|
);
|
|
27158
27381
|
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).
|
|
27382
|
+
"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."
|
|
27383
|
+
).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(
|
|
27384
|
+
"--wait <seconds>",
|
|
27385
|
+
"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).",
|
|
27386
|
+
parseWaitSeconds
|
|
27387
|
+
).action(
|
|
27161
27388
|
async (slug, opts) => {
|
|
27162
27389
|
process.exitCode = await moduleShipCommand(slug, {
|
|
27163
27390
|
...opts.dir !== void 0 ? { dir: opts.dir } : {},
|
|
@@ -27169,7 +27396,8 @@ moduleCmd.command("ship [slug]").description(
|
|
|
27169
27396
|
...opts.keepDevInstall !== void 0 ? { keepDevInstall: opts.keepDevInstall } : {},
|
|
27170
27397
|
...opts.replaceDevInstall !== void 0 ? { replaceDevInstall: opts.replaceDevInstall } : {},
|
|
27171
27398
|
...opts.yes !== void 0 ? { yes: opts.yes } : {},
|
|
27172
|
-
...opts.timeout !== void 0 ? { timeoutSec: opts.timeout } : {}
|
|
27399
|
+
...opts.timeout !== void 0 ? { timeoutSec: opts.timeout } : {},
|
|
27400
|
+
...opts.wait !== void 0 ? { waitSec: opts.wait } : {}
|
|
27173
27401
|
});
|
|
27174
27402
|
}
|
|
27175
27403
|
);
|
|
@@ -27230,7 +27458,11 @@ moduleCmd.command("install [slug]").alias("deploy").alias("upgrade").description
|
|
|
27230
27458
|
).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
27459
|
"--accept-data-loss <drift_hash>",
|
|
27232
27460
|
"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.").
|
|
27461
|
+
).option("--json", "Emit the final result as machine-readable JSON.").option(
|
|
27462
|
+
"--wait <seconds>",
|
|
27463
|
+
"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.",
|
|
27464
|
+
parseWaitSeconds
|
|
27465
|
+
).action(
|
|
27234
27466
|
async (slug, cmdOpts) => {
|
|
27235
27467
|
process.exitCode = await moduleInstallCommand(slug, {
|
|
27236
27468
|
...cmdOpts.installation !== void 0 ? { installation: cmdOpts.installation } : {},
|
|
@@ -27242,7 +27474,8 @@ moduleCmd.command("install [slug]").alias("deploy").alias("upgrade").description
|
|
|
27242
27474
|
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
27243
27475
|
...cmdOpts.yes !== void 0 ? { yes: cmdOpts.yes } : {},
|
|
27244
27476
|
...cmdOpts.acceptDataLoss !== void 0 ? { acceptDataLoss: cmdOpts.acceptDataLoss } : {},
|
|
27245
|
-
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
27477
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {},
|
|
27478
|
+
...cmdOpts.wait !== void 0 ? { waitSec: cmdOpts.wait } : {}
|
|
27246
27479
|
});
|
|
27247
27480
|
}
|
|
27248
27481
|
);
|
|
@@ -27596,7 +27829,7 @@ fieldCmd.command("set-type <table-slug> <field-slug> <new-type>").description(
|
|
|
27596
27829
|
}
|
|
27597
27830
|
);
|
|
27598
27831
|
function getVersion() {
|
|
27599
|
-
return "0.
|
|
27832
|
+
return "0.31.1";
|
|
27600
27833
|
}
|
|
27601
27834
|
function parsePositiveIdOption(value) {
|
|
27602
27835
|
if (!/^[1-9][0-9]*$/.test(value.trim())) {
|