@dashai/cli 0.24.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/bin.js +1063 -90
- package/dist/bin.js.map +1 -1
- package/package.json +4 -3
package/dist/bin.js
CHANGED
|
@@ -112,6 +112,12 @@ function resolveApiUrl() {
|
|
|
112
112
|
if (cfg?.apiUrl) return cfg.apiUrl.replace(/\/+$/, "");
|
|
113
113
|
return DEFAULT_API_URL;
|
|
114
114
|
}
|
|
115
|
+
function resolveExplicitApiUrl() {
|
|
116
|
+
const fromEnv = process.env.DASHWISE_API_URL?.trim();
|
|
117
|
+
if (fromEnv) return fromEnv.replace(/\/+$/, "");
|
|
118
|
+
const cfg = safeReadConfig();
|
|
119
|
+
return cfg?.apiUrl ? cfg.apiUrl.replace(/\/+$/, "") : null;
|
|
120
|
+
}
|
|
115
121
|
function readSavedToken() {
|
|
116
122
|
const cfg = safeReadConfig();
|
|
117
123
|
return cfg?.token ?? null;
|
|
@@ -822,6 +828,74 @@ var init_connect_status = __esm({
|
|
|
822
828
|
SETUP_SETTINGS_PATH = "Modules \u2192 Installed modules \u2192 Technical \u2192 Data connections";
|
|
823
829
|
}
|
|
824
830
|
});
|
|
831
|
+
function declaredSecretNames(manifest) {
|
|
832
|
+
const names = [];
|
|
833
|
+
for (const s of manifest?.runtime?.secrets ?? []) {
|
|
834
|
+
if (typeof s?.key === "string" && SECRET_KEY.test(s.key) && !names.includes(s.key)) {
|
|
835
|
+
names.push(s.key);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
return names;
|
|
839
|
+
}
|
|
840
|
+
function envLocalSetNames(projectRoot) {
|
|
841
|
+
const set = /* @__PURE__ */ new Set();
|
|
842
|
+
const path = join(projectRoot, ".env.local");
|
|
843
|
+
if (!existsSync(path)) return set;
|
|
844
|
+
for (const line of readFileSync(path, "utf-8").split(/\r?\n/)) {
|
|
845
|
+
const m = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(line);
|
|
846
|
+
if (!m) continue;
|
|
847
|
+
const value = m[2].trim().replace(/^(['"])(.*)\1$/, "$2");
|
|
848
|
+
if (value.length > 0) set.add(m[1]);
|
|
849
|
+
}
|
|
850
|
+
return set;
|
|
851
|
+
}
|
|
852
|
+
function missingSecretNames(declared, env, envLocal2) {
|
|
853
|
+
return declared.filter((k) => !(env[k] !== void 0 && env[k] !== "") && !envLocal2.has(k));
|
|
854
|
+
}
|
|
855
|
+
function secretsBannerLine(declared, missing) {
|
|
856
|
+
if (declared.length === 0) return null;
|
|
857
|
+
if (missing.length === 0) return `${declared.length} declared, all set`;
|
|
858
|
+
return `${declared.length} declared, ${missing.length} missing: ${missing.join(", ")} (add ${missing.length === 1 ? "it" : "them"} to .env.local)`;
|
|
859
|
+
}
|
|
860
|
+
function withSecretLines(content, names) {
|
|
861
|
+
const mentioned = (k) => new RegExp(`^\\s*#?\\s*(?:export\\s+)?${k}\\s*=`, "m").test(content);
|
|
862
|
+
const add = names.filter((k) => !mentioned(k));
|
|
863
|
+
if (add.length === 0) return content;
|
|
864
|
+
const hasHeader = content.includes(ENV_LOCAL_SECRETS_HEADER[0]);
|
|
865
|
+
const block = [...hasHeader ? [] : ENV_LOCAL_SECRETS_HEADER, ...add.map((k) => `# ${k}=`)].join("\n");
|
|
866
|
+
const base = content.length === 0 || content.endsWith("\n") ? content : `${content}
|
|
867
|
+
`;
|
|
868
|
+
return `${base}${base.length > 0 && !hasHeader ? "\n" : ""}${block}
|
|
869
|
+
`;
|
|
870
|
+
}
|
|
871
|
+
function syncEnvLocalSecretLines(projectRoot, names) {
|
|
872
|
+
if (names.length === 0) return [];
|
|
873
|
+
const path = join(projectRoot, ".env.local");
|
|
874
|
+
const before = existsSync(path) ? readFileSync(path, "utf-8") : "";
|
|
875
|
+
const after = withSecretLines(before, names);
|
|
876
|
+
if (after === before) return [];
|
|
877
|
+
writeFileSync(path, after, "utf-8");
|
|
878
|
+
return names.filter((k) => !new RegExp(`^\\s*#?\\s*(?:export\\s+)?${k}\\s*=`, "m").test(before));
|
|
879
|
+
}
|
|
880
|
+
function reportSecretLines(added) {
|
|
881
|
+
if (added.length === 0) return;
|
|
882
|
+
info(
|
|
883
|
+
`Added ${added.map((k) => code(`# ${k}=`)).join(", ")} to ${code(".env.local")}: set dev values there. Hosted modules do not receive declared secrets yet.`
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
var SECRET_KEY, ENV_LOCAL_SECRETS_HEADER;
|
|
887
|
+
var init_declared_secrets = __esm({
|
|
888
|
+
"src/lib/declared-secrets.ts"() {
|
|
889
|
+
init_output();
|
|
890
|
+
SECRET_KEY = /^[A-Z][A-Z0-9_]*$/;
|
|
891
|
+
ENV_LOCAL_SECRETS_HEADER = [
|
|
892
|
+
"# Declared runtime secrets (module.json runtime.secrets[]). Set a dev value",
|
|
893
|
+
"# after the `=` and remove the `#`; `dashwise dev` names any that are missing.",
|
|
894
|
+
"# Hosted modules do not receive declared secrets yet (platform item U1);",
|
|
895
|
+
"# locally `dashwise dev` runs `next dev`, which reads them from this file."
|
|
896
|
+
];
|
|
897
|
+
}
|
|
898
|
+
});
|
|
825
899
|
function manifestPath(dir) {
|
|
826
900
|
return join(resolve(dir ?? process.cwd()), "module.json");
|
|
827
901
|
}
|
|
@@ -1166,6 +1240,12 @@ async function devCommand(options) {
|
|
|
1166
1240
|
(Ensure you're logged into the right backend: ${code(`dashwise login --api-url ${apiUrl}`)}.)`
|
|
1167
1241
|
);
|
|
1168
1242
|
}
|
|
1243
|
+
{
|
|
1244
|
+
const declared = declaredSecretNames(readManifest(projectRoot));
|
|
1245
|
+
const missing = missingSecretNames(declared, childEnv, envLocalSetNames(projectRoot));
|
|
1246
|
+
const line = secretsBannerLine(declared, missing);
|
|
1247
|
+
if (line !== null) (missing.length > 0 ? warn : info)(` ${dim("secrets")} ${line}`);
|
|
1248
|
+
}
|
|
1169
1249
|
info("");
|
|
1170
1250
|
if (moduleEntry !== null && manifestSlug !== void 0) {
|
|
1171
1251
|
await reportConnectStatus({
|
|
@@ -1406,13 +1486,13 @@ async function resolveDevPort(requested) {
|
|
|
1406
1486
|
}
|
|
1407
1487
|
async function isPortFree(port) {
|
|
1408
1488
|
const { createServer } = await import('net');
|
|
1409
|
-
return new Promise((
|
|
1489
|
+
return new Promise((resolve16) => {
|
|
1410
1490
|
const server = createServer();
|
|
1411
1491
|
server.once("error", () => {
|
|
1412
|
-
|
|
1492
|
+
resolve16(false);
|
|
1413
1493
|
});
|
|
1414
1494
|
server.once("listening", () => {
|
|
1415
|
-
server.close(() =>
|
|
1495
|
+
server.close(() => resolve16(true));
|
|
1416
1496
|
});
|
|
1417
1497
|
server.listen(port, "0.0.0.0");
|
|
1418
1498
|
});
|
|
@@ -1480,6 +1560,7 @@ var init_dev = __esm({
|
|
|
1480
1560
|
init_api_client();
|
|
1481
1561
|
init_connect_status();
|
|
1482
1562
|
init_config();
|
|
1563
|
+
init_declared_secrets();
|
|
1483
1564
|
init_manifest_io();
|
|
1484
1565
|
init_manifest_poll();
|
|
1485
1566
|
init_output();
|
|
@@ -1634,7 +1715,7 @@ var init_simple = __esm({
|
|
|
1634
1715
|
typescript: "^5.5.0"
|
|
1635
1716
|
},
|
|
1636
1717
|
engines: {
|
|
1637
|
-
node: "22.x"
|
|
1718
|
+
node: "22.x || 24.x"
|
|
1638
1719
|
}
|
|
1639
1720
|
},
|
|
1640
1721
|
"node_modules/@dashai/sdk": {
|
|
@@ -2679,7 +2760,7 @@ var init_custom = __esm({
|
|
|
2679
2760
|
typescript: "^5.5.0"
|
|
2680
2761
|
},
|
|
2681
2762
|
engines: {
|
|
2682
|
-
node: "22.x"
|
|
2763
|
+
node: "22.x || 24.x"
|
|
2683
2764
|
}
|
|
2684
2765
|
},
|
|
2685
2766
|
"node_modules/@alloc/quick-lru": {
|
|
@@ -10017,9 +10098,10 @@ function packageJson(opts) {
|
|
|
10017
10098
|
version: "0.0.1",
|
|
10018
10099
|
private: true,
|
|
10019
10100
|
type: "module",
|
|
10020
|
-
engines: { node:
|
|
10101
|
+
engines: { node: NODE_ENGINES },
|
|
10021
10102
|
scripts: {
|
|
10022
|
-
dev: "dashwise
|
|
10103
|
+
dev: "dashwise dev",
|
|
10104
|
+
"dev:watch": "dashwise module dev",
|
|
10023
10105
|
"dev:next": "next dev",
|
|
10024
10106
|
"dashwise:generate": "dashwise module generate",
|
|
10025
10107
|
typecheck: "tsc --noEmit",
|
|
@@ -10235,7 +10317,7 @@ function appPageTsx(opts) {
|
|
|
10235
10317
|
Module slug: <code>${escapeHtml(opts.slug)}</code>
|
|
10236
10318
|
</p>
|
|
10237
10319
|
<p style={{ marginTop: 24, color: '#64748b', fontSize: 14 }}>
|
|
10238
|
-
Run <code>
|
|
10320
|
+
Run <code>npm run dev</code> to start the app and the watch loop.
|
|
10239
10321
|
<br />
|
|
10240
10322
|
Edit <code>module.json</code> to add tables; regenerated types
|
|
10241
10323
|
land in <code>@dashai/generated</code>.
|
|
@@ -10269,7 +10351,7 @@ debugger to Next), use the legacy two-terminal flow:
|
|
|
10269
10351
|
|
|
10270
10352
|
\`\`\`bash
|
|
10271
10353
|
dashwise module generate # produces node_modules/@dashai/generated/
|
|
10272
|
-
|
|
10354
|
+
npm run dev:watch # watch module.json + auto-regenerate
|
|
10273
10355
|
npm run dev:next # plain Next.js dev server (in another terminal)
|
|
10274
10356
|
\`\`\`
|
|
10275
10357
|
|
|
@@ -10314,7 +10396,7 @@ debugger to Next), use the legacy two-terminal flow:
|
|
|
10314
10396
|
|
|
10315
10397
|
\`\`\`bash
|
|
10316
10398
|
dashwise module generate # produces node_modules/@dashai/generated/
|
|
10317
|
-
|
|
10399
|
+
npm run dev:watch # watch + regen on module.json save (terminal 1)
|
|
10318
10400
|
npm run dev:next # next dev on :3000 (terminal 2)
|
|
10319
10401
|
\`\`\`
|
|
10320
10402
|
|
|
@@ -11119,9 +11201,10 @@ function packageJsonCustom(opts) {
|
|
|
11119
11201
|
version: "0.0.1",
|
|
11120
11202
|
private: true,
|
|
11121
11203
|
type: "module",
|
|
11122
|
-
engines: { node:
|
|
11204
|
+
engines: { node: NODE_ENGINES },
|
|
11123
11205
|
scripts: {
|
|
11124
|
-
dev: "dashwise
|
|
11206
|
+
dev: "dashwise dev",
|
|
11207
|
+
"dev:watch": "dashwise module dev",
|
|
11125
11208
|
"dev:next": "next dev",
|
|
11126
11209
|
"dashwise:generate": "dashwise module generate",
|
|
11127
11210
|
typecheck: "tsc --noEmit",
|
|
@@ -12208,7 +12291,7 @@ in this Dashwise module. Read this before making changes.
|
|
|
12208
12291
|
|
|
12209
12292
|
Every table declares exactly one primary field (the row's name everywhere in Dashwise).
|
|
12210
12293
|
|
|
12211
|
-
${opts.cloudReady === false ? "" : cloudAgentInstructions()}
|
|
12294
|
+
${opts.cloudReady === false ? "" : cloudAgentInstructions() + "\n" + claudeCloudNote(opts.apiUrl)}
|
|
12212
12295
|
## What this module is
|
|
12213
12296
|
|
|
12214
12297
|
A Dashwise ${isCustom ? "**custom Next.js** module (full app runtime, machine-class)" : "**hand-authored simple** module (isolate runtime)"} authored against \`@dashai/sdk\`.
|
|
@@ -12587,9 +12670,14 @@ dashwise module destroy-dev-install # tear down when done (cleans backend + aut
|
|
|
12587
12670
|
- **No untyped \`any\`.** The generated SDK gives you types \u2014 use
|
|
12588
12671
|
them. If you need an escape hatch, add a \`@ts-expect-error\` with
|
|
12589
12672
|
a comment explaining why.
|
|
12590
|
-
- **No secrets in code or env files.**
|
|
12591
|
-
\`module.json
|
|
12592
|
-
|
|
12673
|
+
- **No secrets in code or committed env files.** Declare each one in
|
|
12674
|
+
\`runtime.secrets[]\` in \`module.json\` (by name; values never go in the
|
|
12675
|
+
repo). Hosted modules do not receive declared secrets yet (platform item
|
|
12676
|
+
U1): until that lands, a hosted module cannot read them. Locally, put dev
|
|
12677
|
+
values in the gitignored \`.env.local\` (\`dashwise init\` and
|
|
12678
|
+
\`dashwise module generate\` add a commented \`# KEY=\` line per declared
|
|
12679
|
+
secret); \`dashwise dev\` runs \`next dev\`, which loads them, and names
|
|
12680
|
+
any that are missing.
|
|
12593
12681
|
|
|
12594
12682
|
## Pointers
|
|
12595
12683
|
|
|
@@ -12745,15 +12833,145 @@ primary_region = "iad"
|
|
|
12745
12833
|
`;
|
|
12746
12834
|
}
|
|
12747
12835
|
function cloudAgentInstructions() {
|
|
12748
|
-
return `##
|
|
12836
|
+
return `## Default way of working
|
|
12837
|
+
|
|
12838
|
+
These are defaults. When the person you work for says otherwise (in the chat,
|
|
12839
|
+
or in their own \`CLAUDE.md\` / \`AGENTS.md\`), follow them instead.
|
|
12840
|
+
|
|
12841
|
+
- Assume the person is not a developer. Before and after each step, say in one
|
|
12842
|
+
or two plain sentences what you are doing and why, for example "I'm saving
|
|
12843
|
+
your changes so Dashwise can see them." When a term like branch or pull
|
|
12844
|
+
request is unavoidable, explain it in a few words.
|
|
12845
|
+
- You handle git. Get the finished work onto the repository's default branch
|
|
12846
|
+
(usually \`main\`) yourself, so the person, Dashwise and the next agent all
|
|
12847
|
+
see the latest version. The person needs no git, branch or pull request
|
|
12848
|
+
knowledge:
|
|
12849
|
+
- Working locally on the person's computer: commit on the default branch and push it.
|
|
12850
|
+
- Working in the cloud (Codex cloud, Claude Code on the web): if your tool
|
|
12851
|
+
lets you push to the default branch or merge your own pull request, do it
|
|
12852
|
+
once the checks pass. If it only lets you push a branch, push it and tell the
|
|
12853
|
+
person in plain words that the changes are saved and that Release in
|
|
12854
|
+
Dashwise includes them. Don't ask them to merge anything.
|
|
12855
|
+
- Never force-push and never rewrite the default branch's history.
|
|
12856
|
+
- The person only confirms Release and Take live in Dashwise. When the work is
|
|
12857
|
+
pushed, tell them it is ready: in Dashwise open the module, click Release,
|
|
12858
|
+
then Take live.
|
|
12859
|
+
|
|
12860
|
+
## Cloud workflow
|
|
12749
12861
|
|
|
12750
12862
|
- Read \`module.json\`, the task and \`spec.md\` first. Preserve module identity and permission checks.
|
|
12751
|
-
- Use Node 22
|
|
12863
|
+
- Use Node 22 or 24 (\`.nvmrc\` prefers 22; Codex's default 24 works too). From the repository root run \`bash scripts/cloud-setup.sh\`.
|
|
12752
12864
|
- After edits run \`npm run typecheck\`, \`npm test\`, \`npm run build\`, then \`npm run smoke:production\`. Add behavior tests for your changes. A dev server does not prove production readiness.
|
|
12753
12865
|
- Update \`spec.md\` for behavior changes and \`agent-log.md\` in the same commit. Read \`CLAUDE.md\` for SDK, authorization and UI conventions.
|
|
12754
|
-
-
|
|
12755
|
-
-
|
|
12866
|
+
- \`npm run smoke:production\` starts the built app on a local loopback port. If a cloud sandbox refuses that port, rerun the command with the network permission; never skip the check.
|
|
12867
|
+
- Report what you changed, the pushed SHA and the checks you ran, including failures, in plain words.
|
|
12868
|
+
- Never print, persist or commit secrets. Repository code delivery needs no Dashwise token. Do not log in or access company data for setup or checks; see "Dashwise login" for full development.
|
|
12756
12869
|
- Never automatically release, publish, deploy or take an installation live. Release and Live require an explicit human action.
|
|
12870
|
+
- Even when the person says you may release, you can't do it for them: tell them in one sentence where to click (in Dashwise open the module, click Release, then Take live). Never ask for a \`dashwise login\` or a token just to release.
|
|
12871
|
+
|
|
12872
|
+
## If you can't see the repository
|
|
12873
|
+
|
|
12874
|
+
Connecting GitHub in the AI tool is not enough for a private repository. Tell
|
|
12875
|
+
the person, in plain words, to also install the tool's GitHub app on the GitHub
|
|
12876
|
+
organisation that owns this repository and give it access to this repository
|
|
12877
|
+
(an organisation owner may have to approve it), then start the task again:
|
|
12878
|
+
|
|
12879
|
+
- Claude: the Claude GitHub app, https://github.com/apps/claude
|
|
12880
|
+
- Codex: the ChatGPT Codex Connector, https://github.com/apps/chatgpt-codex-connector
|
|
12881
|
+
|
|
12882
|
+
## Dashwise login (only for full development)
|
|
12883
|
+
|
|
12884
|
+
The checks (\`npm run typecheck\`, \`npm test\`, \`npm run build\`,
|
|
12885
|
+
\`npm run smoke:production\`) need no login. Full Dashwise development does:
|
|
12886
|
+
\`npm run dev\` (\`dashwise dev\`: the app plus the watcher), \`dashwise module
|
|
12887
|
+
generate\` and the schema commands. When the task needs them:
|
|
12888
|
+
|
|
12889
|
+
- If \`dashwise\` is not installed, run \`npm install -g @dashai/cli@latest\`.
|
|
12890
|
+
- Ask the person once, in plain words, for example: "To run your module
|
|
12891
|
+
against Dashwise I need to sign in as you. I'll show you a code and a link;
|
|
12892
|
+
open the link and approve." Then run \`dashwise login --no-prompt\` and show
|
|
12893
|
+
them the code and link it prints. The login acts with the person's own rights.
|
|
12894
|
+
- Keep the login outside the repository. In the cloud, run CLI commands with
|
|
12895
|
+
\`XDG_CONFIG_HOME=/tmp/dashwise-config\` so the saved login lands in
|
|
12896
|
+
\`/tmp\`, never in the checkout.
|
|
12897
|
+
- Never write a token into a script, a file in the repository, a commit, an
|
|
12898
|
+
environment setup script or snapshot, or a log.
|
|
12899
|
+
- In the cloud, run CLI commands with \`NODE_USE_ENV_PROXY=1\` so the CLI
|
|
12900
|
+
uses the environment's network proxy (newer CLI versions do this on their own).
|
|
12901
|
+
- A cloud environment forgets the login when it restarts; ask again only when
|
|
12902
|
+
a command reports that the login is missing.
|
|
12903
|
+
`;
|
|
12904
|
+
}
|
|
12905
|
+
function companyApiHost(apiUrl) {
|
|
12906
|
+
if (!apiUrl) return null;
|
|
12907
|
+
try {
|
|
12908
|
+
const host = new URL(apiUrl).host;
|
|
12909
|
+
return host in HOSTED_API_HOSTS ? host : null;
|
|
12910
|
+
} catch {
|
|
12911
|
+
return null;
|
|
12912
|
+
}
|
|
12913
|
+
}
|
|
12914
|
+
function apiHostLine(apiUrl) {
|
|
12915
|
+
const host = companyApiHost(apiUrl);
|
|
12916
|
+
return host ? `\`${host}\` (this company's Dashwise API; the company uses Dashwise at ${HOSTED_API_HOSTS[host]})` : "the company's Dashwise API host: `api.dashwise.net` if the person opens Dashwise at app.dashwise.net, `api.dashwise.dev` if at app.dashwise.dev. If you can't tell, ask them which address they use";
|
|
12917
|
+
}
|
|
12918
|
+
function codexEnvironmentInstructions(apiUrl) {
|
|
12919
|
+
const host = companyApiHost(apiUrl);
|
|
12920
|
+
const apiUrlValue = host ? `https://${host}` : "https://<the company API host above>";
|
|
12921
|
+
return `## Codex cloud environment
|
|
12922
|
+
|
|
12923
|
+
When the person asks you to set up the Codex cloud environment for this
|
|
12924
|
+
repository, use exactly these settings. Show each one to the person and say in
|
|
12925
|
+
one sentence what it is for.
|
|
12926
|
+
|
|
12927
|
+
| Setting | Value |
|
|
12928
|
+
|---|---|
|
|
12929
|
+
| Node.js | 22 or 24 (24 is Codex's default and works; \`.nvmrc\` prefers 22) |
|
|
12930
|
+
| Setup script | \`if [ -f scripts/cloud-setup.sh ]; then bash scripts/cloud-setup.sh; else echo "No Dashwise template yet, setup skipped"; fi\` |
|
|
12931
|
+
| Agent internet access | On: keep the "package managers" preset and add only the company's Dashwise API host (below) |
|
|
12932
|
+
| Environment variables | \`DASHWISE_API_URL=${apiUrlValue}\`, \`NODE_USE_ENV_PROXY=1\` |
|
|
12933
|
+
| Secrets | none |
|
|
12934
|
+
|
|
12935
|
+
Set the network access before you start the setup, so the first run can
|
|
12936
|
+
already install packages. The preset covers npm and GitHub; the one added
|
|
12937
|
+
domain is:
|
|
12938
|
+
|
|
12939
|
+
- ${apiHostLine(apiUrl)}
|
|
12940
|
+
|
|
12941
|
+
The module and the CLI talk to the company's Dashwise API. Do not add other
|
|
12942
|
+
Dashwise hosts. If the environment offers no preset, allow exactly
|
|
12943
|
+
${CLOUD_ALLOWED_HOSTS.map((h) => `\`${h}\``).join(", ")} plus that host.
|
|
12944
|
+
|
|
12945
|
+
The setup script checks Node 22 or 24 and installs the exact dependencies with
|
|
12946
|
+
\`npm ci\`. The guard lets a first run pass while the repository holds only a
|
|
12947
|
+
README.
|
|
12948
|
+
|
|
12949
|
+
Never add secrets: no \`dwk_\` module key, no \`dwc_\` CLI token and no password,
|
|
12950
|
+
and never put a \`dashwise login\` into the setup script. Delivering code needs
|
|
12951
|
+
none of them; for full development see "Dashwise login".
|
|
12952
|
+
|
|
12953
|
+
To check the environment, run \`npm run typecheck\`, \`npm test\`,
|
|
12954
|
+
\`npm run build\` and \`npm run smoke:production\`. If one fails because of a
|
|
12955
|
+
setting, fix the setting; otherwise report the failure in plain words.
|
|
12956
|
+
|
|
12957
|
+
If the repository has no \`package.json\` yet (only a README), skip these
|
|
12958
|
+
checks: the setup script already skipped installing, and the first task adds
|
|
12959
|
+
the template. Tell the person the settings are ready.
|
|
12960
|
+
|
|
12961
|
+
When the checks pass, or were skipped for a README-only repository, tell the
|
|
12962
|
+
person: "The environment works. Click Publish to save it for your next tasks."
|
|
12963
|
+
Publishing is their click.
|
|
12964
|
+
`;
|
|
12965
|
+
}
|
|
12966
|
+
function claudeCloudNote(apiUrl) {
|
|
12967
|
+
const host = companyApiHost(apiUrl);
|
|
12968
|
+
return `## Claude cloud
|
|
12969
|
+
|
|
12970
|
+
Claude Code on the web has no environment setup chat. It reads this repository
|
|
12971
|
+
and \`CLAUDE.md\`, and \`.claude/settings.json\` runs \`scripts/cloud-setup.sh\`
|
|
12972
|
+
when a cloud session starts. Node 22 or 24 both work. If
|
|
12973
|
+
the environment has a network setting, allow the same domains as for Codex:
|
|
12974
|
+
${CLOUD_ALLOWED_HOSTS.map((h) => `\`${h}\``).join(", ")} and ${host ? `\`${host}\`` : "the company's Dashwise API host (`api.dashwise.net` for app.dashwise.net, `api.dashwise.dev` for app.dashwise.dev)"}. No secrets.
|
|
12757
12975
|
`;
|
|
12758
12976
|
}
|
|
12759
12977
|
function agentsMd(opts) {
|
|
@@ -12761,7 +12979,7 @@ function agentsMd(opts) {
|
|
|
12761
12979
|
|
|
12762
12980
|
Every table declares exactly one primary field (the row's name everywhere in Dashwise).
|
|
12763
12981
|
|
|
12764
|
-
` + cloudAgentInstructions();
|
|
12982
|
+
` + cloudAgentInstructions() + "\n" + codexEnvironmentInstructions(opts.apiUrl) + "\n" + claudeCloudNote(opts.apiUrl);
|
|
12765
12983
|
}
|
|
12766
12984
|
function nodeVersion() {
|
|
12767
12985
|
return "22\n";
|
|
@@ -12783,9 +13001,12 @@ fi
|
|
|
12783
13001
|
repo_root="$(cd -- "$(dirname -- "\${BASH_SOURCE[0]}")/.." && pwd)"
|
|
12784
13002
|
cd "$repo_root"
|
|
12785
13003
|
fail() { printf 'cloud-setup: %s\\n' "$1" >&2; exit 1; }
|
|
12786
|
-
command -v node >/dev/null 2>&1 || fail 'Node 22 is required. Select Node 22 in your cloud environment, then retry.'
|
|
12787
|
-
|
|
12788
|
-
|
|
13004
|
+
command -v node >/dev/null 2>&1 || fail 'Node 22 or 24 is required. Select Node 22 or 24 in your cloud environment, then retry.'
|
|
13005
|
+
case "$(node -p 'process.versions.node.split(".")[0]')" in
|
|
13006
|
+
22|24) ;;
|
|
13007
|
+
*) fail 'Node 22 or 24 is required. Select Node 22 or 24 in your cloud environment, then retry. A subshell-only runtime change will not persist into agent commands.' ;;
|
|
13008
|
+
esac
|
|
13009
|
+
command -v npm >/dev/null 2>&1 || fail 'npm is required with Node 22 or 24.'
|
|
12789
13010
|
[[ -f package-lock.json && -f package.json ]] || fail 'Missing package.json or committed package-lock.json. Run this from a complete repository checkout.'
|
|
12790
13011
|
|
|
12791
13012
|
# Only the Claude startup/resume path reuses an unchanged install. Explicit
|
|
@@ -12793,15 +13014,21 @@ command -v npm >/dev/null 2>&1 || fail 'npm is required with Node 22.'
|
|
|
12793
13014
|
stamp="$(node --input-type=module -e 'import {createHash} from "node:crypto"; import {readFileSync} from "node:fs"; const hash=createHash("sha256"); for (const path of ["package.json", "package-lock.json", "scripts/cloud-setup.sh"]) hash.update(readFileSync(path)); hash.update(process.version); hash.update(process.platform); hash.update(process.arch); process.stdout.write(hash.digest("hex"));')"
|
|
12794
13015
|
stamp_file="node_modules/.dashwise-cloud-setup"
|
|
12795
13016
|
if [[ "\${1:-}" == "--claude" && -f "$stamp_file" && "$(cat "$stamp_file")" == "$stamp" && -x node_modules/.bin/next && -x node_modules/.bin/tsc && -f node_modules/@dashai/sdk/package.json ]]; then
|
|
12796
|
-
printf 'cloud-setup: dependencies already match Node
|
|
13017
|
+
printf 'cloud-setup: dependencies already match this Node version and the lockfile.\\n'
|
|
12797
13018
|
exit 0
|
|
12798
13019
|
fi
|
|
12799
13020
|
# Keep install output out of session logs: third-party lifecycle scripts may
|
|
12800
13021
|
# echo environment variables. npm's own detailed logs stay in its local cache.
|
|
12801
13022
|
rm -f "$stamp_file"
|
|
12802
|
-
|
|
13023
|
+
# Some sandboxes mount HOME read-only; npm then cannot write its cache.
|
|
13024
|
+
npm_cache="\${npm_config_cache:-\${HOME:-}/.npm}"
|
|
13025
|
+
if ! { mkdir -p "$npm_cache" 2>/dev/null && [[ -w "$npm_cache" ]]; }; then
|
|
13026
|
+
export npm_config_cache="\${TMPDIR:-/tmp}/npm-cache"
|
|
13027
|
+
printf 'cloud-setup: %s is not writable, using %s for the npm cache.\\n' "$npm_cache" "$npm_config_cache"
|
|
13028
|
+
fi
|
|
13029
|
+
npm ci --include=dev --no-audit --no-fund > /dev/null 2>&1 || fail 'npm ci failed. Check the committed lockfile, Node 22 or 24 and network access to registry.npmjs.org. Review npm logs privately; never paste credentials.'
|
|
12803
13030
|
printf '%s' "$stamp" > "$stamp_file"
|
|
12804
|
-
printf 'cloud-setup: dependencies installed with Node
|
|
13031
|
+
printf 'cloud-setup: dependencies installed with Node %s.\\n' "$(node -p 'process.versions.node')"
|
|
12805
13032
|
`;
|
|
12806
13033
|
}
|
|
12807
13034
|
function claudeSettingsJson() {
|
|
@@ -12957,7 +13184,7 @@ function cloudReadme() {
|
|
|
12957
13184
|
return `
|
|
12958
13185
|
## Cloud builders
|
|
12959
13186
|
|
|
12960
|
-
Use Node 22 (the root runtime pins) and run \`bash scripts/cloud-setup.sh\`.
|
|
13187
|
+
Use Node 22 (the root runtime pins; 24 also works) and run \`bash scripts/cloud-setup.sh\`.
|
|
12961
13188
|
Commit \`package-lock.json\` after any dependency change; setup uses \`npm ci\`.
|
|
12962
13189
|
Checks: \`npm run typecheck\`, \`npm test\`, \`npm run build\`, \`npm run smoke:production\`.
|
|
12963
13190
|
The smoke command starts the built server, probes anonymous API denial and stops
|
|
@@ -12966,33 +13193,49 @@ The example uses \`lib/items.ts\` directly against the SDK, so a fresh checkout
|
|
|
12966
13193
|
can build without authenticated code generation. Keep it aligned with the
|
|
12967
13194
|
example table, or replace it when you author your schema.
|
|
12968
13195
|
|
|
12969
|
-
Codex:
|
|
12970
|
-
|
|
12971
|
-
|
|
12972
|
-
|
|
13196
|
+
Codex: \`AGENTS.md\` \u2192 "Codex cloud environment" lists the exact settings
|
|
13197
|
+
(Node 22 or 24, setup script, network, \`DASHWISE_API_URL\`, no secrets);
|
|
13198
|
+
Codex's environment setup chat can follow it, then you click Publish. GitHub
|
|
13199
|
+
authorization is configured in the provider: install the ChatGPT Codex
|
|
13200
|
+
Connector on your GitHub organisation so Codex can see a private repository.
|
|
12973
13201
|
|
|
12974
13202
|
Claude Code web: \`.claude/settings.json\` ships a cloud-only SessionStart
|
|
12975
13203
|
hook for startup/resume. It calls \`bash "$CLAUDE_PROJECT_DIR"/scripts/cloud-setup.sh --claude\`;
|
|
12976
13204
|
local sessions skip installation and unchanged cloud installs reuse dependencies.
|
|
12977
|
-
Select Node 22 in the environment; do not rely on a setup subshell changing the
|
|
13205
|
+
Select Node 22 or 24 in the environment; do not rely on a setup subshell changing the
|
|
12978
13206
|
runtime of later agent commands. The same script can be used in the environment
|
|
12979
13207
|
setup field. Allow \`registry.npmjs.org\` for installs (Claude's Trusted list).
|
|
12980
13208
|
The [official hook recipe](https://code.claude.com/docs/en/cloud-environments#install-dependencies-with-a-sessionstart-hook)
|
|
12981
13209
|
explains startup and cache behavior.
|
|
12982
13210
|
|
|
12983
|
-
Read \`AGENTS.md\` / \`CLAUDE.md
|
|
12984
|
-
|
|
13211
|
+
Read \`AGENTS.md\` / \`CLAUDE.md\`. By default agents get the work onto the
|
|
13212
|
+
default branch themselves and explain each step in plain words; Release in
|
|
13213
|
+
Dashwise includes work an agent could only push to a branch. Release and Live
|
|
13214
|
+
remain human actions. No Dashwise token is needed for
|
|
13215
|
+
repository code delivery.
|
|
12985
13216
|
`;
|
|
12986
13217
|
}
|
|
12987
|
-
var DEPLOY_TARGETS, SCAFFOLD_DEFAULT_API_URL, MODULE_SLUG_REGEX;
|
|
13218
|
+
var NODE_ENGINES, DEPLOY_TARGETS, SCAFFOLD_DEFAULT_API_URL, MODULE_SLUG_REGEX, CLOUD_ALLOWED_HOSTS, HOSTED_API_HOSTS;
|
|
12988
13219
|
var init_scaffold = __esm({
|
|
12989
13220
|
"src/lib/scaffold.ts"() {
|
|
12990
13221
|
init_sdk_version();
|
|
12991
13222
|
init_simple();
|
|
12992
13223
|
init_custom();
|
|
13224
|
+
NODE_ENGINES = "22.x || 24.x";
|
|
12993
13225
|
DEPLOY_TARGETS = ["railway", "vercel", "fly"];
|
|
12994
13226
|
SCAFFOLD_DEFAULT_API_URL = "http://localhost:3000";
|
|
12995
13227
|
MODULE_SLUG_REGEX = /^[a-z]([a-z0-9-]*[a-z0-9])?$/;
|
|
13228
|
+
CLOUD_ALLOWED_HOSTS = [
|
|
13229
|
+
"registry.npmjs.org",
|
|
13230
|
+
"github.com",
|
|
13231
|
+
"api.github.com",
|
|
13232
|
+
"raw.githubusercontent.com",
|
|
13233
|
+
"codeload.github.com"
|
|
13234
|
+
];
|
|
13235
|
+
HOSTED_API_HOSTS = {
|
|
13236
|
+
"api.dashwise.dev": "app.dashwise.dev",
|
|
13237
|
+
"api.dashwise.net": "app.dashwise.net"
|
|
13238
|
+
};
|
|
12996
13239
|
}
|
|
12997
13240
|
});
|
|
12998
13241
|
function isFreshCloneEntry(name) {
|
|
@@ -13070,7 +13313,7 @@ function canAdoptCloudFiles(dir, templatePackage) {
|
|
|
13070
13313
|
if (lock.lockfileVersion !== 2 && lock.lockfileVersion !== 3 || !root) {
|
|
13071
13314
|
return false;
|
|
13072
13315
|
}
|
|
13073
|
-
if (pkg.engines?.node !== "22.x" || !pkg.scripts || !expected.scripts) {
|
|
13316
|
+
if (pkg.engines?.node !== "22.x" && pkg.engines?.node !== expected.engines?.node || !pkg.scripts || !expected.scripts) {
|
|
13074
13317
|
return false;
|
|
13075
13318
|
}
|
|
13076
13319
|
for (const script of ["typecheck", "test", "build", "start", "smoke:production"]) {
|
|
@@ -13173,12 +13416,14 @@ async function moduleInitCommand(slugArg, options) {
|
|
|
13173
13416
|
const templatePackage = (kind === "custom" ? packageJsonCustom : packageJson)({
|
|
13174
13417
|
slug});
|
|
13175
13418
|
const cloudReady = writeMode === "all" || canAdoptCloudFiles(dir, templatePackage);
|
|
13419
|
+
const companyApiUrl = resolveExplicitApiUrl();
|
|
13176
13420
|
const scaffoldOpts = {
|
|
13177
13421
|
cloudReady,
|
|
13178
13422
|
slug,
|
|
13179
13423
|
name,
|
|
13180
13424
|
kind,
|
|
13181
|
-
...options.git !== void 0 ? { repositoryUrl: options.git } : {}
|
|
13425
|
+
...options.git !== void 0 ? { repositoryUrl: options.git } : {},
|
|
13426
|
+
...companyApiUrl !== null ? { apiUrl: companyApiUrl } : {}
|
|
13182
13427
|
};
|
|
13183
13428
|
const scaffoldApiUrl = resolveApiUrl();
|
|
13184
13429
|
const files = [
|
|
@@ -13303,6 +13548,9 @@ async function moduleInitCommand(slugArg, options) {
|
|
|
13303
13548
|
writeFileSync(absPath, content, "utf-8");
|
|
13304
13549
|
written++;
|
|
13305
13550
|
}
|
|
13551
|
+
reportSecretLines(
|
|
13552
|
+
syncEnvLocalSecretLines(dir, declaredSecretNames(readManifest(dir)))
|
|
13553
|
+
);
|
|
13306
13554
|
}
|
|
13307
13555
|
if (writeMode !== "none" && !cloudReady) {
|
|
13308
13556
|
process.stdout.write(
|
|
@@ -13376,7 +13624,7 @@ async function moduleInitCommand(slugArg, options) {
|
|
|
13376
13624
|
|
|
13377
13625
|
${dim(" (or, for the two-terminal flow:")}
|
|
13378
13626
|
${dim(" dashwise module generate")} ${dim("# materialize typed wrappers")}
|
|
13379
|
-
${dim("
|
|
13627
|
+
${dim(" npm run dev:watch")} ${dim("# watch + auto-regenerate")}
|
|
13380
13628
|
${dim(" npm run dev:next # next dev on :3000)")}
|
|
13381
13629
|
${devInstall ? `
|
|
13382
13630
|
${dim(`Dev installation id=${devInstall.installation.id}; API key expires ${devInstall.api_key_expires_at}.`)}
|
|
@@ -13545,10 +13793,297 @@ var init_init = __esm({
|
|
|
13545
13793
|
init_output();
|
|
13546
13794
|
init_api_client();
|
|
13547
13795
|
init_config();
|
|
13796
|
+
init_declared_secrets();
|
|
13548
13797
|
init_manifest_io();
|
|
13549
13798
|
init_workspaces();
|
|
13550
13799
|
}
|
|
13551
13800
|
});
|
|
13801
|
+
|
|
13802
|
+
// src/lib/manifest-evolution.ts
|
|
13803
|
+
function compareManifests(base, head, history = []) {
|
|
13804
|
+
const violations = [];
|
|
13805
|
+
const additions = [];
|
|
13806
|
+
const warnings = [];
|
|
13807
|
+
const v = (rule, where, detail) => violations.push({ rule, where, detail });
|
|
13808
|
+
const baseTables = tablesOf(base);
|
|
13809
|
+
const headTables = tablesOf(head);
|
|
13810
|
+
const everTables = /* @__PURE__ */ new Set();
|
|
13811
|
+
const everFields = /* @__PURE__ */ new Set();
|
|
13812
|
+
for (const m of history) {
|
|
13813
|
+
for (const [slug, t] of tablesOf(m)) {
|
|
13814
|
+
everTables.add(slug);
|
|
13815
|
+
for (const f of t.fields ?? []) everFields.add(`${slug}.${f.slug}`);
|
|
13816
|
+
}
|
|
13817
|
+
}
|
|
13818
|
+
for (const [slug, bt] of baseTables) {
|
|
13819
|
+
const ht = headTables.get(slug);
|
|
13820
|
+
if (!ht) {
|
|
13821
|
+
v("removed_table", slug, "tables are never removed; deprecate instead");
|
|
13822
|
+
continue;
|
|
13823
|
+
}
|
|
13824
|
+
const bf = fieldsOf(bt);
|
|
13825
|
+
const hf = fieldsOf(ht);
|
|
13826
|
+
for (const [fs, f] of bf) {
|
|
13827
|
+
const g = hf.get(fs);
|
|
13828
|
+
const where = `${slug}.${fs}`;
|
|
13829
|
+
if (!g) {
|
|
13830
|
+
v("removed_field", where, "fields are never removed; stop writing it and set human_edit: open");
|
|
13831
|
+
continue;
|
|
13832
|
+
}
|
|
13833
|
+
if (f.type !== g.type) v("retyped_field", where, `${f.type} \u2192 ${g.type}`);
|
|
13834
|
+
if ((f.decimal_places ?? null) !== (g.decimal_places ?? null)) {
|
|
13835
|
+
v("retyped_field", where, `decimal_places ${f.decimal_places ?? "none"} \u2192 ${g.decimal_places ?? "none"}`);
|
|
13836
|
+
}
|
|
13837
|
+
if ((f.target ?? null) !== (g.target ?? null)) v("retyped_field", where, `link target ${f.target} \u2192 ${g.target}`);
|
|
13838
|
+
if (f.required !== true && g.required === true) {
|
|
13839
|
+
v("required_tightened", where, "old rows are empty; enforce it in the tool layer instead");
|
|
13840
|
+
}
|
|
13841
|
+
const bo = f.options ?? [];
|
|
13842
|
+
const ho = g.options ?? [];
|
|
13843
|
+
for (const [i, o] of bo.entries()) {
|
|
13844
|
+
if (!ho.includes(o)) v("removed_option", where, `option "${String(o)}" removed`);
|
|
13845
|
+
else if (ho[i] !== o) {
|
|
13846
|
+
v("reordered_option", where, `option "${String(o)}" moved from ${i} to ${ho.indexOf(o)}; options are append-only`);
|
|
13847
|
+
}
|
|
13848
|
+
}
|
|
13849
|
+
for (const o of ho.slice(bo.length)) if (!bo.includes(o)) additions.push(`option ${where} "${String(o)}"`);
|
|
13850
|
+
}
|
|
13851
|
+
for (const [fs] of hf) {
|
|
13852
|
+
if (bf.has(fs)) continue;
|
|
13853
|
+
if (everFields.has(`${slug}.${fs}`)) {
|
|
13854
|
+
v("slug_reused", `${slug}.${fs}`, "this slug was removed in an earlier version and may not come back");
|
|
13855
|
+
} else additions.push(`field ${slug}.${fs}`);
|
|
13856
|
+
}
|
|
13857
|
+
const hu = new Set(uniqueIndexes(ht));
|
|
13858
|
+
for (const idx of uniqueIndexes(bt)) {
|
|
13859
|
+
if (!hu.has(idx)) v("dropped_unique_index", `${slug}(${idx})`, "a unique index is a protocol lock; it may not be dropped or changed");
|
|
13860
|
+
}
|
|
13861
|
+
const bu = new Set(uniqueIndexes(bt));
|
|
13862
|
+
for (const idx of hu) {
|
|
13863
|
+
if (bu.has(idx)) continue;
|
|
13864
|
+
const overExisting = idx.split(",").some((f) => f.length > 0 && bf.has(f));
|
|
13865
|
+
if (overExisting) {
|
|
13866
|
+
v("unique_on_populated", `${slug}(${idx})`, "a new unique index (indexes[].unique) over existing fields fails on duplicates already stored; add a new field instead");
|
|
13867
|
+
} else additions.push(`unique index ${slug}(${idx})`);
|
|
13868
|
+
}
|
|
13869
|
+
}
|
|
13870
|
+
for (const [slug] of headTables) {
|
|
13871
|
+
if (baseTables.has(slug)) continue;
|
|
13872
|
+
if (everTables.has(slug)) v("slug_reused", slug, "this table slug was removed in an earlier version and may not come back");
|
|
13873
|
+
else additions.push(`table ${slug}`);
|
|
13874
|
+
}
|
|
13875
|
+
const baseViews = new Set((base?.owns?.views ?? []).map((x) => x.slug));
|
|
13876
|
+
const headViews = new Set((head?.owns?.views ?? []).map((x) => x.slug));
|
|
13877
|
+
for (const s of baseViews) if (!headViews.has(s)) warnings.push(`view ${s} removed (callers of qbViews.${s} break)`);
|
|
13878
|
+
for (const s of headViews) if (!baseViews.has(s)) additions.push(`view ${s}`);
|
|
13879
|
+
return { violations, additions, warnings };
|
|
13880
|
+
}
|
|
13881
|
+
var tablesOf, fieldsOf, uniqueIndexes;
|
|
13882
|
+
var init_manifest_evolution = __esm({
|
|
13883
|
+
"src/lib/manifest-evolution.ts"() {
|
|
13884
|
+
tablesOf = (m) => new Map((m?.owns?.tables ?? []).map((t) => [t.slug, t]));
|
|
13885
|
+
fieldsOf = (t) => new Map((t?.fields ?? []).map((f) => [f.slug, f]));
|
|
13886
|
+
uniqueIndexes = (t) => (t?.indexes ?? []).filter((i) => i.unique === true).map((i) => (i.fields ?? []).join(","));
|
|
13887
|
+
}
|
|
13888
|
+
});
|
|
13889
|
+
|
|
13890
|
+
// src/commands/module/diff-check.ts
|
|
13891
|
+
var diff_check_exports = {};
|
|
13892
|
+
__export(diff_check_exports, {
|
|
13893
|
+
EvolutionCheckError: () => EvolutionCheckError,
|
|
13894
|
+
moduleDiffCheckCommand: () => moduleDiffCheckCommand,
|
|
13895
|
+
printEvolutionReport: () => printEvolutionReport,
|
|
13896
|
+
publishEvolutionGate: () => publishEvolutionGate,
|
|
13897
|
+
runEvolutionCheck: () => runEvolutionCheck
|
|
13898
|
+
});
|
|
13899
|
+
function readJson(path) {
|
|
13900
|
+
try {
|
|
13901
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
13902
|
+
} catch (err) {
|
|
13903
|
+
throw new EvolutionCheckError(`could not read ${path}: ${err.message}`);
|
|
13904
|
+
}
|
|
13905
|
+
}
|
|
13906
|
+
function git(cwd, args) {
|
|
13907
|
+
try {
|
|
13908
|
+
return execFileSync("git", ["-C", cwd, ...args], {
|
|
13909
|
+
encoding: "utf8",
|
|
13910
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
13911
|
+
maxBuffer: 64 * 1024 * 1024
|
|
13912
|
+
});
|
|
13913
|
+
} catch (err) {
|
|
13914
|
+
const stderr = err.stderr?.trim();
|
|
13915
|
+
throw new EvolutionCheckError(`git ${args.join(" ")} failed${stderr ? `: ${stderr}` : ""}`);
|
|
13916
|
+
}
|
|
13917
|
+
}
|
|
13918
|
+
async function runEvolutionCheck(options) {
|
|
13919
|
+
const projectRoot = resolve(options.dir ?? process.cwd());
|
|
13920
|
+
const headPath = options.head ? resolve(options.head) : join(projectRoot, "module.json");
|
|
13921
|
+
if (!existsSync(headPath)) throw new EvolutionCheckError(`no manifest at ${headPath}`);
|
|
13922
|
+
const head = readJson(headPath);
|
|
13923
|
+
const baseFile = options.baseFile ?? (options.base && existsSync(options.base) ? options.base : void 0);
|
|
13924
|
+
if (baseFile) {
|
|
13925
|
+
const base = readJson(baseFile);
|
|
13926
|
+
return { ...compareManifests(base, head), base: baseFile, head: headPath, history: 0, nothingReleased: false };
|
|
13927
|
+
}
|
|
13928
|
+
if (options.base) {
|
|
13929
|
+
const ref = options.base;
|
|
13930
|
+
const base = JSON.parse(git(projectRoot, ["show", `${ref}:./module.json`]));
|
|
13931
|
+
let history2 = [];
|
|
13932
|
+
if (!options.noHistory) {
|
|
13933
|
+
const shas = git(projectRoot, ["log", "--format=%H", "--reverse", ref, "--", "module.json"]).split("\n").filter(Boolean);
|
|
13934
|
+
history2 = shas.slice(0, -1).map((sha) => JSON.parse(git(projectRoot, ["show", `${sha}:./module.json`])));
|
|
13935
|
+
}
|
|
13936
|
+
return {
|
|
13937
|
+
...compareManifests(base, head, history2),
|
|
13938
|
+
base: `${ref}:module.json`,
|
|
13939
|
+
head: headPath,
|
|
13940
|
+
history: history2.length,
|
|
13941
|
+
nothingReleased: false
|
|
13942
|
+
};
|
|
13943
|
+
}
|
|
13944
|
+
const slug = head.module?.slug;
|
|
13945
|
+
if (!slug) throw new EvolutionCheckError(`${headPath} has no module.slug`);
|
|
13946
|
+
let mod;
|
|
13947
|
+
let versions;
|
|
13948
|
+
try {
|
|
13949
|
+
mod = await apiRequest(
|
|
13950
|
+
"GET",
|
|
13951
|
+
`/api/modules/by-slug/${encodeURIComponent(slug)}`
|
|
13952
|
+
);
|
|
13953
|
+
if (!mod.current_version) {
|
|
13954
|
+
return {
|
|
13955
|
+
violations: [],
|
|
13956
|
+
additions: [],
|
|
13957
|
+
warnings: [],
|
|
13958
|
+
base: "(nothing released)",
|
|
13959
|
+
head: headPath,
|
|
13960
|
+
history: 0,
|
|
13961
|
+
nothingReleased: true
|
|
13962
|
+
};
|
|
13963
|
+
}
|
|
13964
|
+
versions = await apiRequest("GET", `/api/modules/${mod.id}/versions`);
|
|
13965
|
+
} catch (err) {
|
|
13966
|
+
const why = err instanceof ApiError ? `${err.code || err.status} ${err.message}` : err.message;
|
|
13967
|
+
throw new EvolutionCheckError(
|
|
13968
|
+
`could not read the released manifest of ${slug} (${why}); pass --base <ref|file> to check offline`
|
|
13969
|
+
);
|
|
13970
|
+
}
|
|
13971
|
+
const released = versions.find((v) => v.version === mod.current_version);
|
|
13972
|
+
if (!released?.manifest_jsonb) {
|
|
13973
|
+
throw new EvolutionCheckError(
|
|
13974
|
+
`the platform returned no manifest for ${slug} v${mod.current_version}; pass --base <ref|file> to check offline`
|
|
13975
|
+
);
|
|
13976
|
+
}
|
|
13977
|
+
const releasedAt = released.created_on ? Date.parse(released.created_on) : Number.POSITIVE_INFINITY;
|
|
13978
|
+
const history = options.noHistory ? [] : versions.filter((v) => v !== released && v.manifest_jsonb && v.created_on && Date.parse(v.created_on) < releasedAt).sort((a, b) => Date.parse(a.created_on) - Date.parse(b.created_on)).map((v) => v.manifest_jsonb);
|
|
13979
|
+
return {
|
|
13980
|
+
...compareManifests(released.manifest_jsonb, head, history),
|
|
13981
|
+
base: `${slug} v${mod.current_version} (released)`,
|
|
13982
|
+
head: headPath,
|
|
13983
|
+
history: history.length,
|
|
13984
|
+
nothingReleased: false
|
|
13985
|
+
};
|
|
13986
|
+
}
|
|
13987
|
+
function printEvolutionReport(r, print = out) {
|
|
13988
|
+
print(`module diff --check: ${r.head} against ${r.base} (${r.history} older version${r.history === 1 ? "" : "s"} scanned)`);
|
|
13989
|
+
for (const a of r.additions) print(` + ${a}`);
|
|
13990
|
+
for (const w of r.warnings) print(` ! ${w}`);
|
|
13991
|
+
for (const x of r.violations) print(` \u2717 ${x.rule}: ${x.where}: ${x.detail}`);
|
|
13992
|
+
}
|
|
13993
|
+
async function moduleDiffCheckCommand(options) {
|
|
13994
|
+
let r;
|
|
13995
|
+
try {
|
|
13996
|
+
r = await runEvolutionCheck(options);
|
|
13997
|
+
} catch (err) {
|
|
13998
|
+
if (err instanceof EvolutionCheckError) {
|
|
13999
|
+
if (options.json) out(JSON.stringify({ ok: false, error: { code: "CHECK_FAILED", message: err.message } }));
|
|
14000
|
+
else fail(`module diff --check: ${err.message}`);
|
|
14001
|
+
return 2;
|
|
14002
|
+
}
|
|
14003
|
+
throw err;
|
|
14004
|
+
}
|
|
14005
|
+
if (options.json) {
|
|
14006
|
+
out(JSON.stringify({ ok: r.violations.length === 0, ...r }, null, 2));
|
|
14007
|
+
return r.violations.length === 0 ? 0 : 1;
|
|
14008
|
+
}
|
|
14009
|
+
if (r.nothingReleased) {
|
|
14010
|
+
success("Nothing released yet: every change is additive.");
|
|
14011
|
+
return 0;
|
|
14012
|
+
}
|
|
14013
|
+
printEvolutionReport(r);
|
|
14014
|
+
if (r.violations.length === 0) {
|
|
14015
|
+
success("additive");
|
|
14016
|
+
return 0;
|
|
14017
|
+
}
|
|
14018
|
+
fail(`${r.violations.length} violation(s): not additive. Released copies would lose data or fail to upgrade.`);
|
|
14019
|
+
info(`Add a new field or table instead, and stop writing the old one (${code('human_edit: "open"')}).`);
|
|
14020
|
+
return 1;
|
|
14021
|
+
}
|
|
14022
|
+
async function publishEvolutionGate(projectRoot, strict) {
|
|
14023
|
+
let r;
|
|
14024
|
+
try {
|
|
14025
|
+
r = await runEvolutionCheck({ dir: projectRoot });
|
|
14026
|
+
} catch (err) {
|
|
14027
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
14028
|
+
if (strict) {
|
|
14029
|
+
fail(`Evolution check could not run (${msg}). Refusing with --strict. Nothing was published.`);
|
|
14030
|
+
return 1;
|
|
14031
|
+
}
|
|
14032
|
+
warn(`Evolution check skipped: ${msg}.`);
|
|
14033
|
+
return 0;
|
|
14034
|
+
}
|
|
14035
|
+
if (r.nothingReleased || r.violations.length === 0) return 0;
|
|
14036
|
+
printEvolutionReport(r, (s) => (strict ? fail : warn)(s));
|
|
14037
|
+
if (strict) {
|
|
14038
|
+
fail(
|
|
14039
|
+
`module.json is not an additive evolution of the released version (${r.violations.length} violation(s)). Refusing with --strict. Nothing was published.`
|
|
14040
|
+
);
|
|
14041
|
+
return 1;
|
|
14042
|
+
}
|
|
14043
|
+
warn(
|
|
14044
|
+
`module.json is not an additive evolution of the released version: installs that upgrade lose the data above. ${code("--strict")} turns this into a refusal; ${code("dashwise module diff --check")} shows it on its own.`
|
|
14045
|
+
);
|
|
14046
|
+
return 0;
|
|
14047
|
+
}
|
|
14048
|
+
var EvolutionCheckError;
|
|
14049
|
+
var init_diff_check = __esm({
|
|
14050
|
+
"src/commands/module/diff-check.ts"() {
|
|
14051
|
+
init_api_client();
|
|
14052
|
+
init_manifest_evolution();
|
|
14053
|
+
init_output();
|
|
14054
|
+
EvolutionCheckError = class extends Error {
|
|
14055
|
+
};
|
|
14056
|
+
}
|
|
14057
|
+
});
|
|
14058
|
+
|
|
14059
|
+
// src/lib/manifest-reference-lint.ts
|
|
14060
|
+
function referenceFieldWarnings(manifest) {
|
|
14061
|
+
const tables = manifest?.owns?.tables;
|
|
14062
|
+
if (!Array.isArray(tables)) return [];
|
|
14063
|
+
const warnings = [];
|
|
14064
|
+
for (const table of tables) {
|
|
14065
|
+
if (typeof table?.slug !== "string" || !Array.isArray(table.fields)) continue;
|
|
14066
|
+
for (const field of table.fields) {
|
|
14067
|
+
if (typeof field?.slug !== "string" || typeof field.type !== "string") continue;
|
|
14068
|
+
if (field.slug === "id" || !REFERENCE_NAME.test(field.slug)) continue;
|
|
14069
|
+
if (!PLAIN_TYPES.has(field.type)) continue;
|
|
14070
|
+
warnings.push({
|
|
14071
|
+
table: table.slug,
|
|
14072
|
+
field: field.slug,
|
|
14073
|
+
type: field.type,
|
|
14074
|
+
message: `${table.slug}.${field.slug} is a ${field.type} field named like a reference. If it points at a row in Dashwise, use a link_row field: plain ids break silently when data is copied to another installation. Ignore this for ids from outside systems.`
|
|
14075
|
+
});
|
|
14076
|
+
}
|
|
14077
|
+
}
|
|
14078
|
+
return warnings;
|
|
14079
|
+
}
|
|
14080
|
+
var REFERENCE_NAME, PLAIN_TYPES;
|
|
14081
|
+
var init_manifest_reference_lint = __esm({
|
|
14082
|
+
"src/lib/manifest-reference-lint.ts"() {
|
|
14083
|
+
REFERENCE_NAME = /(?:_id|_ref|Id|Ref)$/;
|
|
14084
|
+
PLAIN_TYPES = /* @__PURE__ */ new Set(["number", "text", "autonumber"]);
|
|
14085
|
+
}
|
|
14086
|
+
});
|
|
13552
14087
|
function loadSchemaCommandContext(opts) {
|
|
13553
14088
|
const projectRoot = resolve(opts.dir ?? process.cwd());
|
|
13554
14089
|
const manifestPath2 = join(projectRoot, "module.json");
|
|
@@ -13621,6 +14156,89 @@ var init_dotenv_local = __esm({
|
|
|
13621
14156
|
}
|
|
13622
14157
|
});
|
|
13623
14158
|
|
|
14159
|
+
// src/lib/install-target.ts
|
|
14160
|
+
function keyDisplayPrefix(apiKey) {
|
|
14161
|
+
if (!apiKey.startsWith(KEY_SCHEME)) return null;
|
|
14162
|
+
const prefix = apiKey.slice(KEY_SCHEME.length, KEY_SCHEME.length + KEY_PREFIX_CHARS);
|
|
14163
|
+
return prefix.length === KEY_PREFIX_CHARS ? prefix : null;
|
|
14164
|
+
}
|
|
14165
|
+
async function keyBelongsToInstall(apiKey, installationId, options, now = /* @__PURE__ */ new Date()) {
|
|
14166
|
+
const prefix = keyDisplayPrefix(apiKey);
|
|
14167
|
+
if (prefix === null) return false;
|
|
14168
|
+
const { api_keys } = await listApiKeys(installationId, options);
|
|
14169
|
+
return api_keys.some(
|
|
14170
|
+
(k) => k.installation_id === installationId && k.key_prefix === prefix && (k.status === void 0 || k.status === "active") && k.revoked_at === null && (k.expires_at === null || new Date(k.expires_at) > now)
|
|
14171
|
+
);
|
|
14172
|
+
}
|
|
14173
|
+
function formatInstallTarget(id, detail) {
|
|
14174
|
+
return detail ? `\u2192 install #${id} (${detail})` : `\u2192 install #${id}`;
|
|
14175
|
+
}
|
|
14176
|
+
async function nameKeyTarget(args) {
|
|
14177
|
+
const { apiKey, keySource, entry, cfg, apiUrl, install } = args;
|
|
14178
|
+
const request = cfg ? { baseUrl: apiUrl, auth: { token: cfg.token }, signal: AbortSignal.timeout(LOOKUP_TIMEOUT_MS) } : null;
|
|
14179
|
+
if (install === void 0) {
|
|
14180
|
+
if (keySource !== "cached" || !entry) {
|
|
14181
|
+
info(
|
|
14182
|
+
`\u2192 install not checked: ${code("DASHWISE_API_KEY")} comes from ${keySource === "environment" ? "the environment" : code(".env.local")}. Pass ${code("--install <id>")} to name its target and refuse a mismatch.`
|
|
14183
|
+
);
|
|
14184
|
+
return 0;
|
|
14185
|
+
}
|
|
14186
|
+
if (!request || entry.workspaceId === void 0) {
|
|
14187
|
+
info(formatInstallTarget(entry.installationId, "the cached dev install"));
|
|
14188
|
+
return 0;
|
|
14189
|
+
}
|
|
14190
|
+
try {
|
|
14191
|
+
const workspaces = await apiRequest("GET", "/api/workspaces", void 0, request);
|
|
14192
|
+
const ws = workspaces.find((w) => w.id === entry.workspaceId);
|
|
14193
|
+
info(formatInstallTarget(entry.installationId, `${ws ? ws.slug : `workspace #${entry.workspaceId}`}, local`));
|
|
14194
|
+
} catch {
|
|
14195
|
+
info(formatInstallTarget(entry.installationId));
|
|
14196
|
+
}
|
|
14197
|
+
return 0;
|
|
14198
|
+
}
|
|
14199
|
+
const source = keySource === "cached" ? "the cached key" : `${code("DASHWISE_API_KEY")} (from ${keySource === "environment" ? "the environment" : code(".env.local")})`;
|
|
14200
|
+
if (!request) {
|
|
14201
|
+
fail(
|
|
14202
|
+
`${code("--install")} looks the install up with your CLI login, and there is none. Run ${code("dashwise login")} first. Nothing was run.`
|
|
14203
|
+
);
|
|
14204
|
+
return 1;
|
|
14205
|
+
}
|
|
14206
|
+
let belongs;
|
|
14207
|
+
try {
|
|
14208
|
+
belongs = await keyBelongsToInstall(apiKey, install, request);
|
|
14209
|
+
} catch (err) {
|
|
14210
|
+
fail(
|
|
14211
|
+
`Could not read the keys of install #${install} to confirm ${source} (${describe(err)}). Refusing to run against an unconfirmed install. Nothing was run.`
|
|
14212
|
+
);
|
|
14213
|
+
return 1;
|
|
14214
|
+
}
|
|
14215
|
+
const cachedDev = entry?.installationId === install ? "the cached dev install, " : "";
|
|
14216
|
+
if (!belongs) {
|
|
14217
|
+
const other = entry && entry.installationId !== install && entry.apiKey === apiKey ? ` It is the cached key of install #${entry.installationId}.` : "";
|
|
14218
|
+
fail(`${source} is not a live key of install #${install}.${other} Nothing was run.`);
|
|
14219
|
+
info(
|
|
14220
|
+
`Use a key of install #${install} (mint one with ${code(`dashwise api-keys create <name> --install ${install}`)}), or drop ${code("--install")} to run against the key's own install.`
|
|
14221
|
+
);
|
|
14222
|
+
return 1;
|
|
14223
|
+
}
|
|
14224
|
+
info(formatInstallTarget(install, `${cachedDev}key checked`));
|
|
14225
|
+
return 0;
|
|
14226
|
+
}
|
|
14227
|
+
function describe(err) {
|
|
14228
|
+
if (err instanceof ApiError) return `${err.code || err.status} ${err.message}`;
|
|
14229
|
+
return err.message;
|
|
14230
|
+
}
|
|
14231
|
+
var KEY_PREFIX_CHARS, KEY_SCHEME, LOOKUP_TIMEOUT_MS;
|
|
14232
|
+
var init_install_target = __esm({
|
|
14233
|
+
"src/lib/install-target.ts"() {
|
|
14234
|
+
init_api_client();
|
|
14235
|
+
init_output();
|
|
14236
|
+
KEY_PREFIX_CHARS = 8;
|
|
14237
|
+
KEY_SCHEME = "dwk_";
|
|
14238
|
+
LOOKUP_TIMEOUT_MS = 1500;
|
|
14239
|
+
}
|
|
14240
|
+
});
|
|
14241
|
+
|
|
13624
14242
|
// src/commands/run.ts
|
|
13625
14243
|
var run_exports = {};
|
|
13626
14244
|
__export(run_exports, {
|
|
@@ -13633,6 +14251,10 @@ async function runCommand(cmdArgs, options) {
|
|
|
13633
14251
|
);
|
|
13634
14252
|
return 1;
|
|
13635
14253
|
}
|
|
14254
|
+
if (options.install !== void 0 && (!Number.isInteger(options.install) || options.install <= 0)) {
|
|
14255
|
+
fail(`--install expects a positive integer installation id (got "${options.install}").`);
|
|
14256
|
+
return 1;
|
|
14257
|
+
}
|
|
13636
14258
|
const projectRoot = resolve(options.dir ?? process.cwd());
|
|
13637
14259
|
let manifestSlug;
|
|
13638
14260
|
try {
|
|
@@ -13645,6 +14267,7 @@ async function runCommand(cmdArgs, options) {
|
|
|
13645
14267
|
fail(`module.json has no ${code("module.slug")} \u2014 the manifest is malformed.`);
|
|
13646
14268
|
return 1;
|
|
13647
14269
|
}
|
|
14270
|
+
const keyInShell = process.env.DASHWISE_API_KEY !== void 0;
|
|
13648
14271
|
hydrateEnvFromDotenvLocal(projectRoot);
|
|
13649
14272
|
const cfg = (() => {
|
|
13650
14273
|
try {
|
|
@@ -13655,8 +14278,19 @@ async function runCommand(cmdArgs, options) {
|
|
|
13655
14278
|
})();
|
|
13656
14279
|
const apiUrl = process.env.DASHWISE_API_URL ?? cfg?.apiUrl ?? "http://localhost:3000";
|
|
13657
14280
|
let apiKey = process.env.DASHWISE_API_KEY;
|
|
14281
|
+
let keySource = keyInShell ? "environment" : ".env.local";
|
|
14282
|
+
let entry = null;
|
|
13658
14283
|
if (!apiKey) {
|
|
13659
|
-
|
|
14284
|
+
entry = readModuleEntry(manifestSlug);
|
|
14285
|
+
if (entry && options.install !== void 0 && entry.installationId !== options.install) {
|
|
14286
|
+
fail(
|
|
14287
|
+
`No key for install #${options.install}: the cached ${code("dwk_")} key of ${code(manifestSlug)} belongs to install #${entry.installationId}. Nothing was run.`
|
|
14288
|
+
);
|
|
14289
|
+
info(
|
|
14290
|
+
`Set ${code("DASHWISE_API_KEY")} to a key of install #${options.install} (mint one with ${code(`dashwise api-keys create <name> --install ${options.install}`)}).`
|
|
14291
|
+
);
|
|
14292
|
+
return 1;
|
|
14293
|
+
}
|
|
13660
14294
|
if (cfg) {
|
|
13661
14295
|
const healed = await resolveModuleApiKey(
|
|
13662
14296
|
manifestSlug,
|
|
@@ -13667,6 +14301,7 @@ async function runCommand(cmdArgs, options) {
|
|
|
13667
14301
|
if (healed) entry = healed;
|
|
13668
14302
|
}
|
|
13669
14303
|
apiKey = entry?.apiKey;
|
|
14304
|
+
keySource = "cached";
|
|
13670
14305
|
}
|
|
13671
14306
|
if (!apiKey) {
|
|
13672
14307
|
fail(
|
|
@@ -13677,6 +14312,15 @@ async function runCommand(cmdArgs, options) {
|
|
|
13677
14312
|
);
|
|
13678
14313
|
return 1;
|
|
13679
14314
|
}
|
|
14315
|
+
const target = await nameKeyTarget({
|
|
14316
|
+
apiKey,
|
|
14317
|
+
keySource,
|
|
14318
|
+
entry: entry ?? readModuleEntry(manifestSlug),
|
|
14319
|
+
cfg,
|
|
14320
|
+
apiUrl,
|
|
14321
|
+
...options.install !== void 0 ? { install: options.install } : {}
|
|
14322
|
+
});
|
|
14323
|
+
if (target !== 0) return target;
|
|
13680
14324
|
const [cmd, ...rest] = cmdArgs;
|
|
13681
14325
|
info(
|
|
13682
14326
|
`${dim("run")} ${cmd} ${rest.join(" ")} ${dim(`(DASHWISE_API_URL=${apiUrl}, DASHWISE_API_KEY injected)`)}`
|
|
@@ -13706,6 +14350,7 @@ var init_run = __esm({
|
|
|
13706
14350
|
"src/commands/run.ts"() {
|
|
13707
14351
|
init_config();
|
|
13708
14352
|
init_dotenv_local();
|
|
14353
|
+
init_install_target();
|
|
13709
14354
|
init_manifest_io();
|
|
13710
14355
|
init_output();
|
|
13711
14356
|
init_dev();
|
|
@@ -13996,6 +14641,7 @@ async function schemaPushCommand(options) {
|
|
|
13996
14641
|
return surfaceSchemaPushError(err, slug);
|
|
13997
14642
|
}
|
|
13998
14643
|
printApplySummary(applied);
|
|
14644
|
+
for (const w of referenceFieldWarnings(manifest)) warn(w.message);
|
|
13999
14645
|
if (dwkAuth) {
|
|
14000
14646
|
try {
|
|
14001
14647
|
const { manifest: canonical } = await getInstallationSchema(dwkAuth);
|
|
@@ -14144,6 +14790,7 @@ var init_push = __esm({
|
|
|
14144
14790
|
init_manifest_io();
|
|
14145
14791
|
init_manifest_merge();
|
|
14146
14792
|
init_output();
|
|
14793
|
+
init_manifest_reference_lint();
|
|
14147
14794
|
}
|
|
14148
14795
|
});
|
|
14149
14796
|
|
|
@@ -14721,6 +15368,26 @@ var init_set_type = __esm({
|
|
|
14721
15368
|
}
|
|
14722
15369
|
});
|
|
14723
15370
|
|
|
15371
|
+
// src/lib/env-proxy.ts
|
|
15372
|
+
var PROXY_VARS = ["HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy"];
|
|
15373
|
+
function hasProxyEnv(env = process.env) {
|
|
15374
|
+
return PROXY_VARS.some((name) => Boolean(env[name]?.trim()));
|
|
15375
|
+
}
|
|
15376
|
+
async function installEnvProxy(env = process.env) {
|
|
15377
|
+
if (!hasProxyEnv(env)) return false;
|
|
15378
|
+
const undici = await import('undici');
|
|
15379
|
+
undici.setGlobalDispatcher(new undici.EnvHttpProxyAgent());
|
|
15380
|
+
Object.assign(globalThis, {
|
|
15381
|
+
fetch: undici.fetch,
|
|
15382
|
+
FormData: undici.FormData,
|
|
15383
|
+
Headers: undici.Headers,
|
|
15384
|
+
Request: undici.Request,
|
|
15385
|
+
Response: undici.Response
|
|
15386
|
+
});
|
|
15387
|
+
env.NODE_USE_ENV_PROXY ??= "1";
|
|
15388
|
+
return true;
|
|
15389
|
+
}
|
|
15390
|
+
|
|
14724
15391
|
// src/lib/excess-args.ts
|
|
14725
15392
|
init_output();
|
|
14726
15393
|
var SMART_DASH_RE = /^[‒–—―−]/;
|
|
@@ -15128,7 +15795,7 @@ init_api_client();
|
|
|
15128
15795
|
init_config();
|
|
15129
15796
|
init_output();
|
|
15130
15797
|
init_sdk_version();
|
|
15131
|
-
var
|
|
15798
|
+
var MIN_NODE = [20, 18, 1];
|
|
15132
15799
|
var CLOUD_FILES = [
|
|
15133
15800
|
"scripts/cloud-setup.sh",
|
|
15134
15801
|
".claude/settings.json",
|
|
@@ -15348,13 +16015,20 @@ function unreachable(apiUrl) {
|
|
|
15348
16015
|
};
|
|
15349
16016
|
}
|
|
15350
16017
|
function nodeCheck(version) {
|
|
15351
|
-
const
|
|
15352
|
-
|
|
16018
|
+
const parts = version.replace(/^v/, "").split(".").map((n) => Number.parseInt(n, 10));
|
|
16019
|
+
let supported = parts.length === 3 && parts.every(Number.isFinite);
|
|
16020
|
+
for (let i = 0; supported && i < 3; i++) {
|
|
16021
|
+
if (parts[i] !== MIN_NODE[i]) {
|
|
16022
|
+
supported = parts[i] > MIN_NODE[i];
|
|
16023
|
+
break;
|
|
16024
|
+
}
|
|
16025
|
+
}
|
|
16026
|
+
return supported ? { id: "node", label: "Node", status: "ok", detail: version } : {
|
|
15353
16027
|
id: "node",
|
|
15354
16028
|
label: "Node",
|
|
15355
16029
|
status: "fail",
|
|
15356
16030
|
detail: `${version} is too old`,
|
|
15357
|
-
fix: `Install Node ${
|
|
16031
|
+
fix: `Install Node ${MIN_NODE.join(".")} or newer (the template pins Node 22).`
|
|
15358
16032
|
};
|
|
15359
16033
|
}
|
|
15360
16034
|
async function versionCheck(id, label, installed, latest, fix) {
|
|
@@ -15674,8 +16348,8 @@ function startMcpProxy(opts) {
|
|
|
15674
16348
|
let streamUnavailable = false;
|
|
15675
16349
|
let streamStarted = false;
|
|
15676
16350
|
let resolveDone;
|
|
15677
|
-
const done = new Promise((
|
|
15678
|
-
resolveDone =
|
|
16351
|
+
const done = new Promise((resolve16) => {
|
|
16352
|
+
resolveDone = resolve16;
|
|
15679
16353
|
});
|
|
15680
16354
|
const trace = (line) => {
|
|
15681
16355
|
if (verbose) log2(line);
|
|
@@ -16161,7 +16835,7 @@ async function revokeSession(endpointId) {
|
|
|
16161
16835
|
() => false
|
|
16162
16836
|
);
|
|
16163
16837
|
const timeout = new Promise(
|
|
16164
|
-
(
|
|
16838
|
+
(resolve16) => setTimeout(() => resolve16(false), REVOKE_TIMEOUT_MS).unref()
|
|
16165
16839
|
);
|
|
16166
16840
|
await Promise.race([attempt, timeout]);
|
|
16167
16841
|
}
|
|
@@ -16339,7 +17013,7 @@ async function mcpAddCommand(options = {}) {
|
|
|
16339
17013
|
fail(`Could not run \`claude\`: ${target.refused} Run the command above yourself.`);
|
|
16340
17014
|
return 1;
|
|
16341
17015
|
}
|
|
16342
|
-
return await new Promise((
|
|
17016
|
+
return await new Promise((resolve16) => {
|
|
16343
17017
|
const child = spawn(target.command, target.args, { shell: target.shell, stdio: "inherit" });
|
|
16344
17018
|
let settled = false;
|
|
16345
17019
|
child.on("error", (err) => {
|
|
@@ -16349,16 +17023,16 @@ async function mcpAddCommand(options = {}) {
|
|
|
16349
17023
|
fail(
|
|
16350
17024
|
`Claude Code is not on your PATH, so nothing was registered. Run the command above once ${code("claude")} is installed.`
|
|
16351
17025
|
);
|
|
16352
|
-
|
|
17026
|
+
resolve16(1);
|
|
16353
17027
|
return;
|
|
16354
17028
|
}
|
|
16355
17029
|
fail(`Could not run \`claude\`: ${err.message}`);
|
|
16356
|
-
|
|
17030
|
+
resolve16(1);
|
|
16357
17031
|
});
|
|
16358
17032
|
child.on("close", (codeOut) => {
|
|
16359
17033
|
if (settled) return;
|
|
16360
17034
|
settled = true;
|
|
16361
|
-
|
|
17035
|
+
resolve16(codeOut ?? 1);
|
|
16362
17036
|
});
|
|
16363
17037
|
});
|
|
16364
17038
|
}
|
|
@@ -16477,10 +17151,10 @@ function upgradeCommand(plugin, marketplace) {
|
|
|
16477
17151
|
var PLUGIN_NAME = "dashwise";
|
|
16478
17152
|
var MARKETPLACE_NAME = "dashwise";
|
|
16479
17153
|
var DEFAULT_MARKETPLACE_SOURCE = "Intuize/dashwise-cli";
|
|
16480
|
-
var defaultRunner = (cmd, args, opts) => new Promise((
|
|
17154
|
+
var defaultRunner = (cmd, args, opts) => new Promise((resolve16) => {
|
|
16481
17155
|
const target = spawnTarget(cmd, args);
|
|
16482
17156
|
if ("refused" in target) {
|
|
16483
|
-
|
|
17157
|
+
resolve16({ code: null, stdout: "", stderr: target.refused, missing: false });
|
|
16484
17158
|
return;
|
|
16485
17159
|
}
|
|
16486
17160
|
const child = spawn(target.command, target.args, {
|
|
@@ -16495,7 +17169,7 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolve15) => {
|
|
|
16495
17169
|
child.on("error", (err) => {
|
|
16496
17170
|
if (settled) return;
|
|
16497
17171
|
settled = true;
|
|
16498
|
-
|
|
17172
|
+
resolve16({
|
|
16499
17173
|
code: null,
|
|
16500
17174
|
stdout,
|
|
16501
17175
|
stderr: stderr || err.message,
|
|
@@ -16505,7 +17179,7 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolve15) => {
|
|
|
16505
17179
|
child.on("close", (exitCode) => {
|
|
16506
17180
|
if (settled) return;
|
|
16507
17181
|
settled = true;
|
|
16508
|
-
|
|
17182
|
+
resolve16({ code: exitCode, stdout, stderr, missing: false });
|
|
16509
17183
|
});
|
|
16510
17184
|
});
|
|
16511
17185
|
function readSkillsOnDisk() {
|
|
@@ -17746,6 +18420,7 @@ function judgeGeneratorStamp(stamp, opts = {}) {
|
|
|
17746
18420
|
// src/commands/module/generate.ts
|
|
17747
18421
|
init_sdk_version();
|
|
17748
18422
|
init_output();
|
|
18423
|
+
init_declared_secrets();
|
|
17749
18424
|
function outputDir(projectRoot) {
|
|
17750
18425
|
return join(projectRoot, "node_modules", "@dashai", "generated");
|
|
17751
18426
|
}
|
|
@@ -17822,6 +18497,9 @@ async function moduleGenerateCommand(options) {
|
|
|
17822
18497
|
info(`Hash: ${dim(res.contentHash)}`);
|
|
17823
18498
|
if (verdict.note !== null && declaresUses(manifest)) info(verdict.note);
|
|
17824
18499
|
warnIfSdkBelowUsesFloor(projectRoot, manifest);
|
|
18500
|
+
reportSecretLines(
|
|
18501
|
+
syncEnvLocalSecretLines(projectRoot, declaredSecretNames(manifest))
|
|
18502
|
+
);
|
|
17825
18503
|
return 0;
|
|
17826
18504
|
}
|
|
17827
18505
|
function declaresUses(manifest) {
|
|
@@ -18080,6 +18758,101 @@ init_init();
|
|
|
18080
18758
|
// src/commands/module/install.ts
|
|
18081
18759
|
init_api_client();
|
|
18082
18760
|
init_config();
|
|
18761
|
+
|
|
18762
|
+
// src/lib/agent-session.ts
|
|
18763
|
+
var MARKERS = [
|
|
18764
|
+
["CLAUDECODE", (v) => v === "1"],
|
|
18765
|
+
["CLAUDE_CODE_REMOTE", (v) => v === "true"],
|
|
18766
|
+
["CODEX_SANDBOX", (v) => v.length > 0],
|
|
18767
|
+
["CODEX_SANDBOX_NETWORK_DISABLED", (v) => v.length > 0],
|
|
18768
|
+
["CURSOR_AGENT", (v) => v.length > 0],
|
|
18769
|
+
["DW_AGENT", (v) => v.length > 0],
|
|
18770
|
+
["DASHWISE_AGENT_SESSION", (v) => v === "1" || v === "true"]
|
|
18771
|
+
];
|
|
18772
|
+
function agentSessionMarker(env = process.env) {
|
|
18773
|
+
for (const [name, test] of MARKERS) {
|
|
18774
|
+
const value = env[name];
|
|
18775
|
+
if (value !== void 0 && test(value)) return name;
|
|
18776
|
+
}
|
|
18777
|
+
return null;
|
|
18778
|
+
}
|
|
18779
|
+
|
|
18780
|
+
// src/lib/drift-preview.ts
|
|
18781
|
+
init_api_client();
|
|
18782
|
+
function redactedConsumer(redacted) {
|
|
18783
|
+
switch (redacted) {
|
|
18784
|
+
case "private_copy":
|
|
18785
|
+
return "someone's private copy";
|
|
18786
|
+
case "other_team_copy":
|
|
18787
|
+
return "another team's copy";
|
|
18788
|
+
case "development_copy":
|
|
18789
|
+
return "a development copy you cannot see";
|
|
18790
|
+
default:
|
|
18791
|
+
return "a copy you cannot see";
|
|
18792
|
+
}
|
|
18793
|
+
}
|
|
18794
|
+
var WIDEN_WORDS = {
|
|
18795
|
+
uses_need_added: "a new table it reads from another module",
|
|
18796
|
+
uses_ops_widened: "more operations on a table it reads",
|
|
18797
|
+
uses_fields_added: "more fields of a table it reads",
|
|
18798
|
+
dependency_added: "a new or wider dependency",
|
|
18799
|
+
exposes_widened: "more of what it exposes to other modules",
|
|
18800
|
+
permissions_added: "more permissions",
|
|
18801
|
+
roles_widened: "more roles or permission keys",
|
|
18802
|
+
outbound_added: "new outbound APIs (data can leave)",
|
|
18803
|
+
secrets_added: "new secrets",
|
|
18804
|
+
public_access_enabled: "public access"
|
|
18805
|
+
};
|
|
18806
|
+
function describeWidening(widening) {
|
|
18807
|
+
return widening.map((w) => WIDEN_WORDS[w] ?? w).join(", ");
|
|
18808
|
+
}
|
|
18809
|
+
async function getUpgradePreview(installationId, version, options = {}) {
|
|
18810
|
+
return apiRequest(
|
|
18811
|
+
"GET",
|
|
18812
|
+
`/api/installations/${installationId}/upgrade-preview?version=${encodeURIComponent(version)}`,
|
|
18813
|
+
void 0,
|
|
18814
|
+
options
|
|
18815
|
+
);
|
|
18816
|
+
}
|
|
18817
|
+
function count(n, unit) {
|
|
18818
|
+
if (n === null) return `${unit}s not counted`;
|
|
18819
|
+
return `${n.toLocaleString("en-US")} ${unit}${n === 1 ? "" : "s"}`;
|
|
18820
|
+
}
|
|
18821
|
+
function describeDrift(drift) {
|
|
18822
|
+
const lines = [];
|
|
18823
|
+
const d = drift.details;
|
|
18824
|
+
const removedTables = d?.tables_to_remove ?? drift.tables_to_remove.map((t) => ({ table: t, name: t, rows: null }));
|
|
18825
|
+
for (const t of removedTables) {
|
|
18826
|
+
lines.push({
|
|
18827
|
+
loses: t.rows !== 0,
|
|
18828
|
+
text: `Moves table ${t.table} (${count(t.rows, "row")}) to the Trash`
|
|
18829
|
+
});
|
|
18830
|
+
}
|
|
18831
|
+
const removedFields = d?.fields_to_remove ?? drift.fields_to_remove.map((f) => ({ ...f, name: f.field, values: null }));
|
|
18832
|
+
for (const f of removedFields) {
|
|
18833
|
+
lines.push({
|
|
18834
|
+
loses: f.values !== 0,
|
|
18835
|
+
text: `Moves field ${f.table}.${f.field} (${count(f.values, "value")}) to the Trash`
|
|
18836
|
+
});
|
|
18837
|
+
}
|
|
18838
|
+
const retyped = d?.fields_to_retype ?? (drift.fields_to_retype ?? []).map((f) => ({ ...f, name: f.field, values: null }));
|
|
18839
|
+
for (const f of retyped) {
|
|
18840
|
+
lines.push({
|
|
18841
|
+
loses: f.values !== 0,
|
|
18842
|
+
text: `Converts field ${f.table}.${f.field} from ${f.from} to ${f.to} (${count(f.values, "value")}; a value that does not convert is lost)`
|
|
18843
|
+
});
|
|
18844
|
+
}
|
|
18845
|
+
lines.sort((a, b) => Number(b.loses) - Number(a.loses));
|
|
18846
|
+
for (const t of drift.tables_to_create) {
|
|
18847
|
+
lines.push({ loses: false, text: `Adds table ${t}` });
|
|
18848
|
+
}
|
|
18849
|
+
for (const f of drift.fields_to_create) {
|
|
18850
|
+
lines.push({ loses: false, text: `Adds field ${f.table}.${f.field}` });
|
|
18851
|
+
}
|
|
18852
|
+
return lines;
|
|
18853
|
+
}
|
|
18854
|
+
|
|
18855
|
+
// src/commands/module/install.ts
|
|
18083
18856
|
init_manifest_io();
|
|
18084
18857
|
|
|
18085
18858
|
// src/lib/module-lifecycle.ts
|
|
@@ -18258,18 +19031,31 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
18258
19031
|
);
|
|
18259
19032
|
}
|
|
18260
19033
|
}
|
|
18261
|
-
|
|
18262
|
-
|
|
19034
|
+
let preview;
|
|
19035
|
+
let lossy = false;
|
|
19036
|
+
let lossAccepted = false;
|
|
19037
|
+
if (existing) {
|
|
19038
|
+
const rerun = rerunLine(mod.slug, workspace2.slug, targetVersion, options, devInstallToReplace !== void 0);
|
|
19039
|
+
const previewed = await previewUpgrade(existing.id, targetVersion, options, auth, rerun, devInstallToReplace?.id);
|
|
19040
|
+
if (typeof previewed === "number") return previewed;
|
|
19041
|
+
preview = previewed.preview;
|
|
19042
|
+
lossy = previewed.lossy;
|
|
19043
|
+
lossAccepted = previewed.accepted;
|
|
19044
|
+
}
|
|
19045
|
+
const askForLoss = lossy && !lossAccepted;
|
|
19046
|
+
if (askForLoss || !options.yes && process.stdout.isTTY && !confirmedViaPicker) {
|
|
19047
|
+
const lossClause = lossy ? " It moves the data listed above to the Trash or converts it." : "";
|
|
19048
|
+
const message = devInstallToReplace ? isUpgrade ? `Destroy the dev install (revokes local-dev credentials) and upgrade ${code(mod.slug)} to ${code(`v${targetVersion}`)} in ${code(workspace2.slug)}?${lossClause}` : `Replace the dev install (revokes local-dev credentials) and install ${code(mod.slug)} ${code(`v${targetVersion}`)} into ${code(workspace2.slug)} as production?` : `${isUpgrade ? "Upgrade" : "Install"} ${code(mod.slug)} ${code(`v${targetVersion}`)} into workspace ${code(workspace2.slug)}?${lossClause}`;
|
|
18263
19049
|
const answer = await select({
|
|
18264
19050
|
message,
|
|
18265
19051
|
options: [
|
|
18266
19052
|
{
|
|
18267
19053
|
value: "go",
|
|
18268
|
-
label: devInstallToReplace ? isUpgrade ? "Yes, destroy the dev install + upgrade" : "Yes, replace the dev install + production-install" : isUpgrade ? "Yes, upgrade now" : "Yes, install now"
|
|
19054
|
+
label: devInstallToReplace ? isUpgrade ? lossy ? "Yes, destroy the dev install + upgrade and lose the data listed above" : "Yes, destroy the dev install + upgrade" : "Yes, replace the dev install + production-install" : isUpgrade ? lossy ? "Yes, upgrade and lose the data listed above" : "Yes, upgrade now" : "Yes, install now"
|
|
18269
19055
|
},
|
|
18270
19056
|
{ value: "cancel", label: "No, abort" }
|
|
18271
19057
|
],
|
|
18272
|
-
initialValue: devInstallToReplace ? "cancel" : "go"
|
|
19058
|
+
initialValue: devInstallToReplace || lossy ? "cancel" : "go"
|
|
18273
19059
|
});
|
|
18274
19060
|
if (isCancel(answer) || answer === "cancel") {
|
|
18275
19061
|
fail("Aborted. Nothing was changed.");
|
|
@@ -18291,7 +19077,9 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
18291
19077
|
workspaceId: workspace2.id,
|
|
18292
19078
|
version: targetVersion,
|
|
18293
19079
|
...existing ? { installation_id: existing.id } : {},
|
|
18294
|
-
...options.force !== void 0 ? { force: options.force } : {}
|
|
19080
|
+
...options.force !== void 0 ? { force: options.force } : {},
|
|
19081
|
+
// P5-08: confirms exactly the delta that was shown (409 DRIFT_CHANGED otherwise).
|
|
19082
|
+
...preview ? { drift_hash: preview.drift_hash } : {}
|
|
18295
19083
|
},
|
|
18296
19084
|
auth
|
|
18297
19085
|
);
|
|
@@ -18343,6 +19131,9 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
18343
19131
|
was_upgrade: result.was_upgrade,
|
|
18344
19132
|
public_url: publicUrl,
|
|
18345
19133
|
verified,
|
|
19134
|
+
// P5-08: the confirmed preview; `null` for a fresh install (nothing to preview).
|
|
19135
|
+
drift_hash: preview?.drift_hash ?? null,
|
|
19136
|
+
data_loss: preview ? lossy : null,
|
|
18346
19137
|
// `null` when the binding surface was unreachable; `[]` when the
|
|
18347
19138
|
// module declares no slots. The two are not the same thing.
|
|
18348
19139
|
uses: readiness === null ? null : usesInstallJson(readiness),
|
|
@@ -18511,6 +19302,19 @@ function surfaceInstallError(err, slug, workspaceSlug, devInstallReplaced) {
|
|
|
18511
19302
|
Ask the module's author to invite this workspace (or open the install gate), then retry.`
|
|
18512
19303
|
);
|
|
18513
19304
|
break;
|
|
19305
|
+
case "DRIFT_CHANGED":
|
|
19306
|
+
fail(
|
|
19307
|
+
`Install ${code(slug)} changed since its upgrade preview, so the confirmed change no longer matches. Nothing was changed.
|
|
19308
|
+
Re-run ${code("dashwise module install")} to see the new preview.`
|
|
19309
|
+
);
|
|
19310
|
+
break;
|
|
19311
|
+
case "DATA_LOSS_CONFIRM_REQUIRED":
|
|
19312
|
+
case "UPGRADE_CONFIRM_REQUIRED":
|
|
19313
|
+
fail(
|
|
19314
|
+
`The platform asks for a confirmed preview before this upgrade (${err.code}). Nothing was changed.
|
|
19315
|
+
Re-run ${code("dashwise module install")}: it previews the upgrade and sends the confirmation.`
|
|
19316
|
+
);
|
|
19317
|
+
break;
|
|
18514
19318
|
case "UPGRADE_BREAKS_CONSUMERS":
|
|
18515
19319
|
fail(
|
|
18516
19320
|
`Upgrading ${code(slug)} would break modules that consume its current schema.
|
|
@@ -18560,6 +19364,75 @@ function usesInstallJson(readiness) {
|
|
|
18560
19364
|
}))
|
|
18561
19365
|
};
|
|
18562
19366
|
}
|
|
19367
|
+
async function previewUpgrade(installationId, targetVersion, options, auth, rerun, devInstallToReplaceId) {
|
|
19368
|
+
let preview;
|
|
19369
|
+
try {
|
|
19370
|
+
preview = await getUpgradePreview(installationId, targetVersion, auth);
|
|
19371
|
+
} catch (err) {
|
|
19372
|
+
const why = err instanceof ApiError ? `${err.code || err.status} ${err.message}` : err.message;
|
|
19373
|
+
const checkNote = options.acceptDataLoss !== void 0 ? `, so ${code("--accept-data-loss")} cannot be checked` : "";
|
|
19374
|
+
fail(
|
|
19375
|
+
`Could not preview what upgrading install #${installationId} to ${code(`v${targetVersion}`)} does to its data (${why})${checkNote}. Nothing was changed.`
|
|
19376
|
+
);
|
|
19377
|
+
info(`An upgrade is never applied without its preview; retry when the platform answers.`);
|
|
19378
|
+
return 1;
|
|
19379
|
+
}
|
|
19380
|
+
const lines = describeDrift(preview.drift);
|
|
19381
|
+
const lossy = lines.some((l) => l.loses);
|
|
19382
|
+
const from = preview.from_version ? `v${preview.from_version}` : "its current version";
|
|
19383
|
+
if (lines.length === 0) {
|
|
19384
|
+
info(`Upgrading install #${installationId} from ${from} to v${preview.to_version} changes no table or field.`);
|
|
19385
|
+
} else {
|
|
19386
|
+
info(`What upgrading install #${installationId} from ${from} to v${preview.to_version} does to its data:`);
|
|
19387
|
+
for (const line of lines) {
|
|
19388
|
+
(line.loses ? warn : info)(` ${line.text}`);
|
|
19389
|
+
}
|
|
19390
|
+
}
|
|
19391
|
+
const broken = preview.breaks_consumers ?? [];
|
|
19392
|
+
if (broken.length > 0) {
|
|
19393
|
+
const names = broken.map(
|
|
19394
|
+
(c2) => c2.module_slug ? `${c2.module_slug}${c2.installation_id ? ` #${c2.installation_id}` : ""}` : redactedConsumer(c2.redacted)
|
|
19395
|
+
);
|
|
19396
|
+
warn(` Breaks ${broken.length} module${broken.length === 1 ? "" : "s"} that read${broken.length === 1 ? "s" : ""} this one: ${names.join(", ")}`);
|
|
19397
|
+
}
|
|
19398
|
+
if (preview.approval_required) {
|
|
19399
|
+
const asks = preview.widening && preview.widening.length > 0 ? `: it asks for ${describeWidening(preview.widening)}` : "";
|
|
19400
|
+
warn(` This update waits for the module author's approval${asks}.`);
|
|
19401
|
+
}
|
|
19402
|
+
if (options.acceptDataLoss !== void 0 && options.acceptDataLoss !== preview.drift_hash) {
|
|
19403
|
+
fail(
|
|
19404
|
+
`${code("--accept-data-loss")} does not match this upgrade's preview: the copy or the release changed since that hash was printed. Nothing was changed.`
|
|
19405
|
+
);
|
|
19406
|
+
info(`To accept the change listed above, a person re-runs:
|
|
19407
|
+
${rerun(preview.drift_hash)}`);
|
|
19408
|
+
return 1;
|
|
19409
|
+
}
|
|
19410
|
+
const accepted = lossy && options.acceptDataLoss === preview.drift_hash;
|
|
19411
|
+
if (lossy && !accepted) {
|
|
19412
|
+
const yesNote = options.yes ? ` ${code("--yes")} does not accept data loss.` : "";
|
|
19413
|
+
const marker = agentSessionMarker();
|
|
19414
|
+
const why = marker ? `this is an agent session (${code(marker)} is set): a person decides` : !process.stdout.isTTY ? "there is no terminal to ask in" : null;
|
|
19415
|
+
if (why) {
|
|
19416
|
+
const alsoDev = devInstallToReplaceId !== void 0 ? ` and destroys dev install #${devInstallToReplaceId} first` : "";
|
|
19417
|
+
fail(`This upgrade loses the data listed above${alsoDev}, and ${why}.${yesNote} Refused. Nothing was changed.`);
|
|
19418
|
+
info(`To accept exactly that loss, a person re-runs:
|
|
19419
|
+
${rerun(preview.drift_hash)}`);
|
|
19420
|
+
return 1;
|
|
19421
|
+
}
|
|
19422
|
+
}
|
|
19423
|
+
return { preview, lossy, accepted };
|
|
19424
|
+
}
|
|
19425
|
+
function rerunLine(slug, workspaceSlug, targetVersion, options, replacingDevInstall) {
|
|
19426
|
+
return (hash) => [
|
|
19427
|
+
"dashwise module install",
|
|
19428
|
+
slug,
|
|
19429
|
+
`--workspace ${workspaceSlug}`,
|
|
19430
|
+
`--version ${targetVersion}`,
|
|
19431
|
+
...options.force ? ["--force"] : [],
|
|
19432
|
+
...replacingDevInstall ? ["--replace-dev-install"] : [],
|
|
19433
|
+
`--accept-data-loss ${hash}`
|
|
19434
|
+
].join(" ");
|
|
19435
|
+
}
|
|
18563
19436
|
|
|
18564
19437
|
// src/commands/module/list.ts
|
|
18565
19438
|
init_api_client();
|
|
@@ -18929,6 +19802,7 @@ function formatBytes(n) {
|
|
|
18929
19802
|
}
|
|
18930
19803
|
|
|
18931
19804
|
// src/commands/module/publish.ts
|
|
19805
|
+
init_diff_check();
|
|
18932
19806
|
var VALID_BUMP = /* @__PURE__ */ new Set(["patch", "minor", "major"]);
|
|
18933
19807
|
var PACKED_FILE_LIST_CAP = 20;
|
|
18934
19808
|
function packedFileLines(files, cap = PACKED_FILE_LIST_CAP) {
|
|
@@ -19015,6 +19889,10 @@ async function modulePublishCommand(options) {
|
|
|
19015
19889
|
info(
|
|
19016
19890
|
`Module: ${code(mod.slug)} (id ${mod.id})${mod.current_version ? `, current ${code(`v${mod.current_version}`)}` : dim(", no published version yet")}`
|
|
19017
19891
|
);
|
|
19892
|
+
if (mod.current_version) {
|
|
19893
|
+
const gate = await publishEvolutionGate(projectRoot, options.strict === true);
|
|
19894
|
+
if (gate !== 0) return gate;
|
|
19895
|
+
}
|
|
19018
19896
|
let tarball;
|
|
19019
19897
|
try {
|
|
19020
19898
|
tarball = await packTarball({
|
|
@@ -19606,7 +20484,7 @@ async function moduleReleaseCommand(slugArg, options) {
|
|
|
19606
20484
|
}
|
|
19607
20485
|
async function wait() {
|
|
19608
20486
|
const remaining = Math.max(0, deadline - Date.now());
|
|
19609
|
-
await new Promise((
|
|
20487
|
+
await new Promise((resolve16) => setTimeout(resolve16, Math.min(options.pollIntervalMs ?? 4e3, remaining)));
|
|
19610
20488
|
}
|
|
19611
20489
|
}
|
|
19612
20490
|
|
|
@@ -20028,6 +20906,7 @@ async function modulePullDataCommand(options) {
|
|
|
20028
20906
|
// src/commands/module/validate.ts
|
|
20029
20907
|
init_api_client();
|
|
20030
20908
|
init_output();
|
|
20909
|
+
init_manifest_reference_lint();
|
|
20031
20910
|
async function moduleValidateCommand(options) {
|
|
20032
20911
|
const projectRoot = resolve(options.dir ?? process.cwd());
|
|
20033
20912
|
const manifestPath2 = join(projectRoot, "module.json");
|
|
@@ -20052,6 +20931,7 @@ async function moduleValidateCommand(options) {
|
|
|
20052
20931
|
);
|
|
20053
20932
|
if (res.ok) {
|
|
20054
20933
|
success("Manifest is valid.");
|
|
20934
|
+
for (const w of referenceFieldWarnings(manifest)) warn(w.message);
|
|
20055
20935
|
return 0;
|
|
20056
20936
|
}
|
|
20057
20937
|
fail(`Unexpected response shape: ${JSON.stringify(res)}`);
|
|
@@ -22454,6 +23334,23 @@ init_api_client();
|
|
|
22454
23334
|
init_config();
|
|
22455
23335
|
init_manifest_io();
|
|
22456
23336
|
init_output();
|
|
23337
|
+
init_declared_secrets();
|
|
23338
|
+
var SECRETS_NOTE = "# Declared secrets (runtime.secrets[]): set each value on your host; Dashwise never exports a secret value.";
|
|
23339
|
+
function secretNameLines(names, format) {
|
|
23340
|
+
const lines = names.map((name) => {
|
|
23341
|
+
switch (format) {
|
|
23342
|
+
case "env":
|
|
23343
|
+
return `# ${name}=`;
|
|
23344
|
+
case "railway":
|
|
23345
|
+
return `# ${name}: railway variables set ${name}='<value>'`;
|
|
23346
|
+
case "vercel":
|
|
23347
|
+
return `# ${name}: vercel env add ${name} production`;
|
|
23348
|
+
case "fly":
|
|
23349
|
+
return `# ${name}: fly secrets set ${name}='<value>'`;
|
|
23350
|
+
}
|
|
23351
|
+
});
|
|
23352
|
+
return [SECRETS_NOTE, ...lines].join("\n");
|
|
23353
|
+
}
|
|
22457
23354
|
var PLACEHOLDER = "<run-`dashwise-api-keys-create-<name>`-and-paste-here>";
|
|
22458
23355
|
async function envExportCommand(options) {
|
|
22459
23356
|
const format = options.format ?? "env";
|
|
@@ -22544,7 +23441,19 @@ async function envExportCommand(options) {
|
|
|
22544
23441
|
...apiKeyComment !== null ? { comment: apiKeyComment } : {}
|
|
22545
23442
|
}
|
|
22546
23443
|
];
|
|
23444
|
+
const secretNames = options.withSecretNames ? declaredSecretNames(manifest) : [];
|
|
22547
23445
|
process.stdout.write(render(entries, format));
|
|
23446
|
+
if (secretNames.length > 0) {
|
|
23447
|
+
if (format === "json") {
|
|
23448
|
+
info(
|
|
23449
|
+
`Declared secrets to set on your host (Dashwise never exports a secret value): ${secretNames.join(", ")}`
|
|
23450
|
+
);
|
|
23451
|
+
} else {
|
|
23452
|
+
process.stdout.write(`
|
|
23453
|
+
|
|
23454
|
+
${secretNameLines(secretNames, format)}`);
|
|
23455
|
+
}
|
|
23456
|
+
}
|
|
22548
23457
|
if (format !== "json") process.stdout.write("\n");
|
|
22549
23458
|
if (apiKey === PLACEHOLDER) {
|
|
22550
23459
|
info("");
|
|
@@ -22600,6 +23509,7 @@ init_config();
|
|
|
22600
23509
|
init_dotenv_local();
|
|
22601
23510
|
init_manifest_io();
|
|
22602
23511
|
init_output();
|
|
23512
|
+
init_install_target();
|
|
22603
23513
|
init_dev();
|
|
22604
23514
|
async function queryCommand(options) {
|
|
22605
23515
|
const projectRoot = resolve(options.dir ?? process.cwd());
|
|
@@ -22622,17 +23532,25 @@ async function queryCommand(options) {
|
|
|
22622
23532
|
);
|
|
22623
23533
|
return 1;
|
|
22624
23534
|
}
|
|
23535
|
+
const keyInShell = process.env.DASHWISE_API_KEY !== void 0;
|
|
22625
23536
|
hydrateEnvFromDotenvLocal(projectRoot);
|
|
23537
|
+
const cfg = (() => {
|
|
23538
|
+
try {
|
|
23539
|
+
return readConfig();
|
|
23540
|
+
} catch {
|
|
23541
|
+
return null;
|
|
23542
|
+
}
|
|
23543
|
+
})();
|
|
23544
|
+
const apiUrl = process.env.DASHWISE_API_URL ?? cfg?.apiUrl ?? "http://localhost:3000";
|
|
23545
|
+
let keySource = keyInShell ? "environment" : ".env.local";
|
|
23546
|
+
let entry = manifestSlug ? readModuleEntry(manifestSlug) : null;
|
|
22626
23547
|
if (!process.env.DASHWISE_API_KEY && manifestSlug) {
|
|
22627
|
-
|
|
22628
|
-
|
|
22629
|
-
|
|
22630
|
-
|
|
22631
|
-
|
|
22632
|
-
|
|
22633
|
-
})();
|
|
22634
|
-
const apiUrl = process.env.DASHWISE_API_URL ?? cfg?.apiUrl ?? "http://localhost:3000";
|
|
22635
|
-
let entry = readModuleEntry(manifestSlug);
|
|
23548
|
+
if (entry && options.install !== void 0 && entry.installationId !== options.install) {
|
|
23549
|
+
fail(
|
|
23550
|
+
`No key for install #${options.install}: the cached ${code("dwk_")} key of ${code(manifestSlug)} belongs to install #${entry.installationId}. Nothing was run.`
|
|
23551
|
+
);
|
|
23552
|
+
return 1;
|
|
23553
|
+
}
|
|
22636
23554
|
if (cfg) {
|
|
22637
23555
|
const healed = await resolveModuleApiKey(
|
|
22638
23556
|
manifestSlug,
|
|
@@ -22644,11 +23562,26 @@ async function queryCommand(options) {
|
|
|
22644
23562
|
}
|
|
22645
23563
|
if (entry?.apiKey) {
|
|
22646
23564
|
process.env.DASHWISE_API_KEY = entry.apiKey;
|
|
23565
|
+
keySource = "cached";
|
|
22647
23566
|
if (!process.env.DASHWISE_API_URL) {
|
|
22648
23567
|
process.env.DASHWISE_API_URL = apiUrl;
|
|
22649
23568
|
}
|
|
22650
23569
|
}
|
|
22651
23570
|
}
|
|
23571
|
+
if (process.env.DASHWISE_API_KEY) {
|
|
23572
|
+
const target = await nameKeyTarget({
|
|
23573
|
+
apiKey: process.env.DASHWISE_API_KEY,
|
|
23574
|
+
keySource,
|
|
23575
|
+
entry,
|
|
23576
|
+
cfg,
|
|
23577
|
+
apiUrl: process.env.DASHWISE_API_URL ?? apiUrl,
|
|
23578
|
+
...options.install !== void 0 ? { install: options.install } : {}
|
|
23579
|
+
});
|
|
23580
|
+
if (target !== 0) return target;
|
|
23581
|
+
} else if (options.install !== void 0) {
|
|
23582
|
+
fail(`No ${code("dwk_")} key to check against install #${options.install}. Set ${code("DASHWISE_API_KEY")}. Nothing was run.`);
|
|
23583
|
+
return 1;
|
|
23584
|
+
}
|
|
22652
23585
|
const modes = [
|
|
22653
23586
|
options.inline !== void 0 ? "inline" : null,
|
|
22654
23587
|
options.file !== void 0 ? "file" : null,
|
|
@@ -23014,7 +23947,7 @@ function formatTimestamp(iso) {
|
|
|
23014
23947
|
return iso.slice(0, 19).replace("T", " ");
|
|
23015
23948
|
}
|
|
23016
23949
|
function sleep2(ms) {
|
|
23017
|
-
return new Promise((
|
|
23950
|
+
return new Promise((resolve16) => setTimeout(resolve16, ms));
|
|
23018
23951
|
}
|
|
23019
23952
|
|
|
23020
23953
|
// src/commands/module/action-log.ts
|
|
@@ -23116,7 +24049,7 @@ async function runTail2(installationId, options) {
|
|
|
23116
24049
|
}
|
|
23117
24050
|
dim(`(tail: transient error \u2014 ${err.message})`);
|
|
23118
24051
|
}
|
|
23119
|
-
await new Promise((
|
|
24052
|
+
await new Promise((resolve16) => setTimeout(resolve16, POLL_INTERVAL_MS2));
|
|
23120
24053
|
}
|
|
23121
24054
|
}
|
|
23122
24055
|
function printTable(rows) {
|
|
@@ -23501,7 +24434,7 @@ async function sleep3(ms, aborted) {
|
|
|
23501
24434
|
const slice = 100;
|
|
23502
24435
|
let waited = 0;
|
|
23503
24436
|
while (waited < ms && !aborted()) {
|
|
23504
|
-
await new Promise((
|
|
24437
|
+
await new Promise((resolve16) => setTimeout(resolve16, Math.min(slice, ms - waited)));
|
|
23505
24438
|
waited += slice;
|
|
23506
24439
|
}
|
|
23507
24440
|
}
|
|
@@ -24332,12 +25265,29 @@ moduleCmd.command("list").description("List modules you own (or `--all` for owne
|
|
|
24332
25265
|
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
24333
25266
|
});
|
|
24334
25267
|
});
|
|
24335
|
-
moduleCmd.command("diff").description(
|
|
24336
|
-
|
|
24337
|
-
|
|
24338
|
-
|
|
24339
|
-
|
|
24340
|
-
});
|
|
25268
|
+
moduleCmd.command("diff").description(
|
|
25269
|
+
"Show what would change at the next publish vs the currently-published version. With --check: exit 1 when module.json is not an additive evolution of the released manifest (a removed table or field, a retype, a removed or reordered option, a new unique index (`indexes[].unique`) over an existing field, a dropped unique index, required tightened, or a slug reused after an earlier removal), 2 on a usage or read error. Offline with --base."
|
|
25270
|
+
).option("--dir <path>", "Project root (default: current directory).").option("--json", "Emit the structured diff as JSON instead of the human-readable summary.").option("--check", "Check additive evolution against the released manifest (exit 0 additive, 1 not additive, 2 error).").option("--base <ref|path>", "With --check: the base manifest from a git ref (its module.json, history from its log) or, when the path exists, a file. Default: the released version on the platform.").option("--base-file <path>", "With --check: the base manifest from a file.").option("--head <path>", "With --check: the manifest to check (default: <dir>/module.json).").option("--no-history", "With --check: skip the slug-reuse scan of older versions.").option("--dry-run", "Accepted for uniformity; --check never writes anything.").action(
|
|
25271
|
+
async (cmdOpts) => {
|
|
25272
|
+
if (cmdOpts.check) {
|
|
25273
|
+
const { moduleDiffCheckCommand: moduleDiffCheckCommand2 } = await Promise.resolve().then(() => (init_diff_check(), diff_check_exports));
|
|
25274
|
+
process.exitCode = await moduleDiffCheckCommand2({
|
|
25275
|
+
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
25276
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {},
|
|
25277
|
+
...cmdOpts.base !== void 0 ? { base: cmdOpts.base } : {},
|
|
25278
|
+
...cmdOpts.baseFile !== void 0 ? { baseFile: cmdOpts.baseFile } : {},
|
|
25279
|
+
...cmdOpts.head !== void 0 ? { head: cmdOpts.head } : {},
|
|
25280
|
+
// commander maps `--no-history` to `history === false`.
|
|
25281
|
+
...cmdOpts.history === false ? { noHistory: true } : {}
|
|
25282
|
+
});
|
|
25283
|
+
return;
|
|
25284
|
+
}
|
|
25285
|
+
process.exitCode = await moduleDiffCommand({
|
|
25286
|
+
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
25287
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
25288
|
+
});
|
|
25289
|
+
}
|
|
25290
|
+
);
|
|
24341
25291
|
moduleCmd.command("pull-data").description("Export the module's own-table rows to a local JSON file (for fixtures, local dev seeding).").option("--dir <path>", "Project root (default: current directory).").option("--limit <n>", "Max rows per table (1-1000, default 100).", parseIntOption).option("--out <path>", "Output file path (default: ./dashwise-data.json).").action(async (cmdOpts) => {
|
|
24342
25292
|
process.exitCode = await modulePullDataCommand({
|
|
24343
25293
|
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
@@ -24489,6 +25439,10 @@ program.command("query").description(
|
|
|
24489
25439
|
).option(
|
|
24490
25440
|
"--stream",
|
|
24491
25441
|
"Call .stream() instead of .execute() \u2014 prints rows incrementally as JSON-lines (one row per line). Lets you see progress on big queries + Ctrl-C cleanly midway. Mutually exclusive with --explain and --repl."
|
|
25442
|
+
).option(
|
|
25443
|
+
"--install <id>",
|
|
25444
|
+
"The install the key must belong to: reads that install's key list with your CLI login (one request, 1.5 s), prints `\u2192 install #<id> (key checked)` and refuses (exit 1) when DASHWISE_API_KEY is not an active key of that install or the list cannot be read.",
|
|
25445
|
+
parseIntOption
|
|
24492
25446
|
).action(
|
|
24493
25447
|
async (cmdOpts) => {
|
|
24494
25448
|
process.exitCode = await queryCommand({
|
|
@@ -24498,16 +25452,22 @@ program.command("query").description(
|
|
|
24498
25452
|
...cmdOpts.repl !== void 0 ? { repl: cmdOpts.repl } : {},
|
|
24499
25453
|
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {},
|
|
24500
25454
|
...cmdOpts.explain !== void 0 ? { explain: cmdOpts.explain } : {},
|
|
24501
|
-
...cmdOpts.stream !== void 0 ? { stream: cmdOpts.stream } : {}
|
|
25455
|
+
...cmdOpts.stream !== void 0 ? { stream: cmdOpts.stream } : {},
|
|
25456
|
+
...cmdOpts.install !== void 0 ? { install: cmdOpts.install } : {}
|
|
24502
25457
|
});
|
|
24503
25458
|
}
|
|
24504
25459
|
);
|
|
24505
25460
|
program.command("run [cmd...]").description(
|
|
24506
|
-
"Run a command with the module's Dashwise env injected \u2014 resolves DASHWISE_API_URL + DASHWISE_API_KEY through the same chain as `dashwise query` (explicit env \u2192 .env.local \u2192 the cached dwk_ key) and spawns the command with them overlaid, inheriting stdio and propagating the exit code. Example: `dashwise run -- node seed.mjs`."
|
|
24507
|
-
).option("--dir <path>", "Project root containing `module.json` (default: current directory).").
|
|
25461
|
+
"Run a command with the module's Dashwise env injected \u2014 resolves DASHWISE_API_URL + DASHWISE_API_KEY through the same chain as `dashwise query` (explicit env \u2192 .env.local \u2192 the cached dwk_ key) and spawns the command with them overlaid, inheriting stdio and propagating the exit code. Names its target first (`\u2192 install #<id> (\u2026)`, at most one lookup request); `--install <id>` refuses a key of any other install. Example: `dashwise run --install 42 -- node seed.mjs`."
|
|
25462
|
+
).option("--dir <path>", "Project root containing `module.json` (default: current directory).").option(
|
|
25463
|
+
"--install <id>",
|
|
25464
|
+
"The install the key must belong to. Reads that install's key list with your CLI login (one request, 1.5 s), prints `\u2192 install #<id> (key checked)` and refuses (exit 1, nothing run) when DASHWISE_API_KEY is not an active key of that install or the list cannot be read.",
|
|
25465
|
+
parseIntOption
|
|
25466
|
+
).action(async (cmdArgs, cmdOpts) => {
|
|
24508
25467
|
const { runCommand: runCommand2 } = await Promise.resolve().then(() => (init_run(), run_exports));
|
|
24509
25468
|
process.exitCode = await runCommand2(cmdArgs ?? [], {
|
|
24510
|
-
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {}
|
|
25469
|
+
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
25470
|
+
...cmdOpts.install !== void 0 ? { install: cmdOpts.install } : {}
|
|
24511
25471
|
});
|
|
24512
25472
|
});
|
|
24513
25473
|
program.command("pull").description(
|
|
@@ -24634,13 +25594,17 @@ envCmd.command("export").description(
|
|
|
24634
25594
|
).option(
|
|
24635
25595
|
"--key <dwk_value>",
|
|
24636
25596
|
"Use a previously-minted API key (paste plaintext). (Mutually exclusive with --new-key.)"
|
|
25597
|
+
).option(
|
|
25598
|
+
"--with-secret-names",
|
|
25599
|
+
"Also name each secret declared in module.json runtime.secrets[] as a commented `# KEY=` line (a comment naming the command for railway/vercel/fly; stderr for json). Never a value: values never leave Dashwise; set them on your host."
|
|
24637
25600
|
).action(
|
|
24638
25601
|
async (cmdOpts) => {
|
|
24639
25602
|
process.exitCode = await envExportCommand({
|
|
24640
25603
|
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
24641
25604
|
...cmdOpts.format !== void 0 ? { format: cmdOpts.format } : {},
|
|
24642
25605
|
...cmdOpts.newKey !== void 0 ? { newKey: cmdOpts.newKey } : {},
|
|
24643
|
-
...cmdOpts.key !== void 0 ? { key: cmdOpts.key } : {}
|
|
25606
|
+
...cmdOpts.key !== void 0 ? { key: cmdOpts.key } : {},
|
|
25607
|
+
...cmdOpts.withSecretNames ? { withSecretNames: true } : {}
|
|
24644
25608
|
});
|
|
24645
25609
|
}
|
|
24646
25610
|
);
|
|
@@ -24666,7 +25630,10 @@ moduleCmd.command("publish").description("Pack the current directory + upload it
|
|
|
24666
25630
|
).option(
|
|
24667
25631
|
"--timeout <seconds>",
|
|
24668
25632
|
"Max seconds to wait for an async build before giving up polling (default 360)."
|
|
24669
|
-
).option("--json", "Emit the final build result as machine-readable JSON.").
|
|
25633
|
+
).option("--json", "Emit the final build result as machine-readable JSON.").option(
|
|
25634
|
+
"--strict",
|
|
25635
|
+
"Refuse to publish when module.json is not an additive evolution of the released version (the `module diff --check` rules), or when that check cannot run. Without it the check only warns."
|
|
25636
|
+
).action(
|
|
24670
25637
|
async (cmdOpts) => {
|
|
24671
25638
|
const timeoutSec = cmdOpts.timeout !== void 0 ? Number(cmdOpts.timeout) : void 0;
|
|
24672
25639
|
process.exitCode = await modulePublishCommand({
|
|
@@ -24682,19 +25649,23 @@ moduleCmd.command("publish").description("Pack the current directory + upload it
|
|
|
24682
25649
|
// commander maps `--no-wait` to `wait === false`.
|
|
24683
25650
|
...cmdOpts.wait === false ? { noWait: true } : {},
|
|
24684
25651
|
...timeoutSec !== void 0 && Number.isFinite(timeoutSec) ? { timeoutSec } : {},
|
|
24685
|
-
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
25652
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {},
|
|
25653
|
+
...cmdOpts.strict !== void 0 ? { strict: cmdOpts.strict } : {}
|
|
24686
25654
|
});
|
|
24687
25655
|
}
|
|
24688
25656
|
);
|
|
24689
|
-
moduleCmd.command("install [slug]").alias("deploy").description(
|
|
24690
|
-
|
|
25657
|
+
moduleCmd.command("install [slug]").alias("deploy").alias("upgrade").description(
|
|
25658
|
+
"Install (deploy) a published module into a workspace \u2014 provisions the runtime and prints the public URL. Upgrades/repairs an existing production install idempotently: an upgrade first prints what it does to the copy's data (tables and fields it moves to the Trash or converts, with their counts), asks once, and sends the preview's drift_hash. A move that loses data is accepted only by a person: the one question in a terminal, or --accept-data-loss <drift_hash> with the hash the preview printed. --yes never accepts data loss; with no terminal, or inside an AI agent session, a lossy move is refused and the refusal prints the re-run line. When only a dev install exists it asks whether to install alongside it (--keep-dev-install) or replace it (--replace-dev-install), rather than silently landing on the dev row. Exits 3 \u2014 \"deployed, setup pending\" \u2014 when a required `uses` slot is still unbound: the deploy succeeded but the module throws on its first data call until an admin binds it."
|
|
24691
25659
|
).option("--workspace <slug-or-id>", "Target workspace (default: cached module workspace, config default, or interactive picker).").option("--version <semver>", "Install a specific published version (default: latest).").option("--force", "Proceed even when the upgrade breaks consumer modules (UPGRADE_BREAKS_CONSUMERS).").option(
|
|
24692
25660
|
"--keep-dev-install",
|
|
24693
25661
|
"Install production ALONGSIDE the workspace's kind=local dev install, leaving local dev working. Non-destructive. Backends >= 2026-07-15 keep a separate live-install slot per kind group, so the two coexist."
|
|
24694
25662
|
).option(
|
|
24695
25663
|
"--replace-dev-install",
|
|
24696
25664
|
"Destroy the workspace's kind=local dev install first, then fresh production install. Revokes the cached local-dev credentials; on backends >= 2026-07-15 local dev can be re-provisioned afterwards (dashwise module reset-dev-install, or dashwise init <slug> --workspace <ws>) and will coexist with the production install. On OLDER backends this is one-way \u2014 prefer --keep-dev-install."
|
|
24697
|
-
).option("--dir <path>", "Project root for the module.json slug lookup (default: current directory).").option("-y, --yes", "Skip the confirmation prompt (for CI / scripted use).
|
|
25665
|
+
).option("--dir <path>", "Project root for the module.json slug lookup (default: current directory).").option("-y, --yes", "Skip the confirmation prompt (for CI / scripted use). Never accepts data loss: see --accept-data-loss.").option(
|
|
25666
|
+
"--accept-data-loss <drift_hash>",
|
|
25667
|
+
"Accept the data loss of this upgrade: the drift_hash its preview printed. Only that exact hash accepts it (a person decides; an agent stops and reports the hash)."
|
|
25668
|
+
).option("--json", "Emit the final result as machine-readable JSON.").action(
|
|
24698
25669
|
async (slug, cmdOpts) => {
|
|
24699
25670
|
process.exitCode = await moduleInstallCommand(slug, {
|
|
24700
25671
|
...cmdOpts.workspace !== void 0 ? { workspace: cmdOpts.workspace } : {},
|
|
@@ -24704,6 +25675,7 @@ moduleCmd.command("install [slug]").alias("deploy").description(
|
|
|
24704
25675
|
...cmdOpts.replaceDevInstall !== void 0 ? { replaceDevInstall: cmdOpts.replaceDevInstall } : {},
|
|
24705
25676
|
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
24706
25677
|
...cmdOpts.yes !== void 0 ? { yes: cmdOpts.yes } : {},
|
|
25678
|
+
...cmdOpts.acceptDataLoss !== void 0 ? { acceptDataLoss: cmdOpts.acceptDataLoss } : {},
|
|
24707
25679
|
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
24708
25680
|
});
|
|
24709
25681
|
}
|
|
@@ -25000,7 +25972,7 @@ fieldCmd.command("set-type <table-slug> <field-slug> <new-type>").description(
|
|
|
25000
25972
|
}
|
|
25001
25973
|
);
|
|
25002
25974
|
function getVersion() {
|
|
25003
|
-
return "0.
|
|
25975
|
+
return "0.26.0";
|
|
25004
25976
|
}
|
|
25005
25977
|
function parseIntOption(value) {
|
|
25006
25978
|
const n = Number.parseInt(value, 10);
|
|
@@ -25016,6 +25988,7 @@ function collectRepeated(value, previous) {
|
|
|
25016
25988
|
// src/bin.ts
|
|
25017
25989
|
(async () => {
|
|
25018
25990
|
try {
|
|
25991
|
+
await installEnvProxy();
|
|
25019
25992
|
await program.parseAsync(process.argv);
|
|
25020
25993
|
} catch (err) {
|
|
25021
25994
|
if (err instanceof ExcessArgumentsError) {
|