@dashai/cli 0.23.0 → 0.25.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 +2 -1
- package/dist/bin.js +913 -121
- package/dist/bin.js.map +1 -1
- package/package.json +4 -3
package/dist/bin.js
CHANGED
|
@@ -3,7 +3,7 @@ import pc from 'picocolors';
|
|
|
3
3
|
import { existsSync, readFileSync, writeFileSync, rmSync, mkdirSync, watch, readdirSync, chmodSync, renameSync, lstatSync, mkdtempSync, statSync } from 'fs';
|
|
4
4
|
import { hostname, tmpdir, platform, homedir } from 'os';
|
|
5
5
|
import { resolve, join, relative, dirname, basename, win32, sep } from 'path';
|
|
6
|
-
import { spawn, spawnSync } from 'child_process';
|
|
6
|
+
import { execFileSync, spawn, spawnSync } from 'child_process';
|
|
7
7
|
import { intro, log, select, isCancel, cancel, spinner, outro, text, confirm, multiselect } from '@clack/prompts';
|
|
8
8
|
import { Command, Option } from 'commander';
|
|
9
9
|
import open from 'open';
|
|
@@ -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;
|
|
@@ -1406,13 +1412,13 @@ async function resolveDevPort(requested) {
|
|
|
1406
1412
|
}
|
|
1407
1413
|
async function isPortFree(port) {
|
|
1408
1414
|
const { createServer } = await import('net');
|
|
1409
|
-
return new Promise((
|
|
1415
|
+
return new Promise((resolve15) => {
|
|
1410
1416
|
const server = createServer();
|
|
1411
1417
|
server.once("error", () => {
|
|
1412
|
-
|
|
1418
|
+
resolve15(false);
|
|
1413
1419
|
});
|
|
1414
1420
|
server.once("listening", () => {
|
|
1415
|
-
server.close(() =>
|
|
1421
|
+
server.close(() => resolve15(true));
|
|
1416
1422
|
});
|
|
1417
1423
|
server.listen(port, "0.0.0.0");
|
|
1418
1424
|
});
|
|
@@ -1493,6 +1499,62 @@ var init_dev = __esm({
|
|
|
1493
1499
|
PORT_PROBE_LIMIT = 10;
|
|
1494
1500
|
}
|
|
1495
1501
|
});
|
|
1502
|
+
function resolveSdkPackageJson(projectRoot) {
|
|
1503
|
+
let dir = projectRoot;
|
|
1504
|
+
for (; ; ) {
|
|
1505
|
+
const candidate = join(dir, "node_modules", "@dashai", "sdk", "package.json");
|
|
1506
|
+
if (existsSync(candidate)) return candidate;
|
|
1507
|
+
const parent = dirname(dir);
|
|
1508
|
+
if (parent === dir) return null;
|
|
1509
|
+
dir = parent;
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
function readInstalledSdk(projectRoot) {
|
|
1513
|
+
const pkgPath = resolveSdkPackageJson(projectRoot);
|
|
1514
|
+
if (pkgPath === null) return null;
|
|
1515
|
+
try {
|
|
1516
|
+
const parsed = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
1517
|
+
if (typeof parsed.version !== "string" || parsed.version.length === 0) return null;
|
|
1518
|
+
return { version: parsed.version };
|
|
1519
|
+
} catch {
|
|
1520
|
+
return null;
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
function compareVersions(a, b) {
|
|
1524
|
+
const parse = (v) => {
|
|
1525
|
+
const [core = "", pre] = v.trim().replace(/^[^0-9]*/, "").split("-", 2);
|
|
1526
|
+
const nums = core.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
1527
|
+
while (nums.length < 3) nums.push(0);
|
|
1528
|
+
return [nums.slice(0, 3), pre !== void 0 && pre.length > 0];
|
|
1529
|
+
};
|
|
1530
|
+
const [an, apre] = parse(a);
|
|
1531
|
+
const [bn, bpre] = parse(b);
|
|
1532
|
+
for (let i = 0; i < 3; i++) {
|
|
1533
|
+
const d = (an[i] ?? 0) - (bn[i] ?? 0);
|
|
1534
|
+
if (d !== 0) return d;
|
|
1535
|
+
}
|
|
1536
|
+
if (apre === bpre) return 0;
|
|
1537
|
+
return apre ? -1 : 1;
|
|
1538
|
+
}
|
|
1539
|
+
function satisfiesFloor(version, floor) {
|
|
1540
|
+
return compareVersions(version, floor) >= 0;
|
|
1541
|
+
}
|
|
1542
|
+
function checkSdkFloor(projectRoot, floor) {
|
|
1543
|
+
const installed = readInstalledSdk(projectRoot);
|
|
1544
|
+
if (installed === null) return { ok: true, installed: null, floor };
|
|
1545
|
+
return {
|
|
1546
|
+
ok: satisfiesFloor(installed.version, floor),
|
|
1547
|
+
installed: installed.version,
|
|
1548
|
+
floor
|
|
1549
|
+
};
|
|
1550
|
+
}
|
|
1551
|
+
var USES_SDK_FLOOR, SCAFFOLD_SDK_RANGE;
|
|
1552
|
+
var init_sdk_version = __esm({
|
|
1553
|
+
"src/lib/sdk-version.ts"() {
|
|
1554
|
+
USES_SDK_FLOOR = "2.5.0";
|
|
1555
|
+
SCAFFOLD_SDK_RANGE = "^2.5.0";
|
|
1556
|
+
}
|
|
1557
|
+
});
|
|
1496
1558
|
async function selectWorkspace(opts) {
|
|
1497
1559
|
const workspaces = await apiRequest(
|
|
1498
1560
|
"GET",
|
|
@@ -1551,62 +1613,6 @@ var init_workspaces = __esm({
|
|
|
1551
1613
|
init_api_client();
|
|
1552
1614
|
}
|
|
1553
1615
|
});
|
|
1554
|
-
function resolveSdkPackageJson(projectRoot) {
|
|
1555
|
-
let dir = projectRoot;
|
|
1556
|
-
for (; ; ) {
|
|
1557
|
-
const candidate = join(dir, "node_modules", "@dashai", "sdk", "package.json");
|
|
1558
|
-
if (existsSync(candidate)) return candidate;
|
|
1559
|
-
const parent = dirname(dir);
|
|
1560
|
-
if (parent === dir) return null;
|
|
1561
|
-
dir = parent;
|
|
1562
|
-
}
|
|
1563
|
-
}
|
|
1564
|
-
function readInstalledSdk(projectRoot) {
|
|
1565
|
-
const pkgPath = resolveSdkPackageJson(projectRoot);
|
|
1566
|
-
if (pkgPath === null) return null;
|
|
1567
|
-
try {
|
|
1568
|
-
const parsed = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
1569
|
-
if (typeof parsed.version !== "string" || parsed.version.length === 0) return null;
|
|
1570
|
-
return { version: parsed.version };
|
|
1571
|
-
} catch {
|
|
1572
|
-
return null;
|
|
1573
|
-
}
|
|
1574
|
-
}
|
|
1575
|
-
function compareVersions(a, b) {
|
|
1576
|
-
const parse = (v) => {
|
|
1577
|
-
const [core = "", pre] = v.trim().replace(/^[^0-9]*/, "").split("-", 2);
|
|
1578
|
-
const nums = core.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
1579
|
-
while (nums.length < 3) nums.push(0);
|
|
1580
|
-
return [nums.slice(0, 3), pre !== void 0 && pre.length > 0];
|
|
1581
|
-
};
|
|
1582
|
-
const [an, apre] = parse(a);
|
|
1583
|
-
const [bn, bpre] = parse(b);
|
|
1584
|
-
for (let i = 0; i < 3; i++) {
|
|
1585
|
-
const d = (an[i] ?? 0) - (bn[i] ?? 0);
|
|
1586
|
-
if (d !== 0) return d;
|
|
1587
|
-
}
|
|
1588
|
-
if (apre === bpre) return 0;
|
|
1589
|
-
return apre ? -1 : 1;
|
|
1590
|
-
}
|
|
1591
|
-
function satisfiesFloor(version, floor) {
|
|
1592
|
-
return compareVersions(version, floor) >= 0;
|
|
1593
|
-
}
|
|
1594
|
-
function checkSdkFloor(projectRoot, floor) {
|
|
1595
|
-
const installed = readInstalledSdk(projectRoot);
|
|
1596
|
-
if (installed === null) return { ok: true, installed: null, floor };
|
|
1597
|
-
return {
|
|
1598
|
-
ok: satisfiesFloor(installed.version, floor),
|
|
1599
|
-
installed: installed.version,
|
|
1600
|
-
floor
|
|
1601
|
-
};
|
|
1602
|
-
}
|
|
1603
|
-
var USES_SDK_FLOOR, SCAFFOLD_SDK_RANGE;
|
|
1604
|
-
var init_sdk_version = __esm({
|
|
1605
|
-
"src/lib/sdk-version.ts"() {
|
|
1606
|
-
USES_SDK_FLOOR = "2.5.0";
|
|
1607
|
-
SCAFFOLD_SDK_RANGE = "^2.5.0";
|
|
1608
|
-
}
|
|
1609
|
-
});
|
|
1610
1616
|
|
|
1611
1617
|
// src/lib/scaffold-locks/simple.json
|
|
1612
1618
|
var simple_default;
|
|
@@ -1634,7 +1640,7 @@ var init_simple = __esm({
|
|
|
1634
1640
|
typescript: "^5.5.0"
|
|
1635
1641
|
},
|
|
1636
1642
|
engines: {
|
|
1637
|
-
node: "22.x"
|
|
1643
|
+
node: "22.x || 24.x"
|
|
1638
1644
|
}
|
|
1639
1645
|
},
|
|
1640
1646
|
"node_modules/@dashai/sdk": {
|
|
@@ -2679,7 +2685,7 @@ var init_custom = __esm({
|
|
|
2679
2685
|
typescript: "^5.5.0"
|
|
2680
2686
|
},
|
|
2681
2687
|
engines: {
|
|
2682
|
-
node: "22.x"
|
|
2688
|
+
node: "22.x || 24.x"
|
|
2683
2689
|
}
|
|
2684
2690
|
},
|
|
2685
2691
|
"node_modules/@alloc/quick-lru": {
|
|
@@ -9990,7 +9996,7 @@ function manifestJson(opts) {
|
|
|
9990
9996
|
// everything on the default — a warning on every column just trains
|
|
9991
9997
|
// people to click through warnings.
|
|
9992
9998
|
fields: [
|
|
9993
|
-
{ slug: "title", type: "text", human_edit: "open" },
|
|
9999
|
+
{ slug: "title", type: "text", primary: true, human_edit: "open" },
|
|
9994
10000
|
{ slug: "done", type: "boolean", human_edit: "guarded" }
|
|
9995
10001
|
]
|
|
9996
10002
|
}
|
|
@@ -10017,9 +10023,10 @@ function packageJson(opts) {
|
|
|
10017
10023
|
version: "0.0.1",
|
|
10018
10024
|
private: true,
|
|
10019
10025
|
type: "module",
|
|
10020
|
-
engines: { node:
|
|
10026
|
+
engines: { node: NODE_ENGINES },
|
|
10021
10027
|
scripts: {
|
|
10022
|
-
dev: "dashwise
|
|
10028
|
+
dev: "dashwise dev",
|
|
10029
|
+
"dev:watch": "dashwise module dev",
|
|
10023
10030
|
"dev:next": "next dev",
|
|
10024
10031
|
"dashwise:generate": "dashwise module generate",
|
|
10025
10032
|
typecheck: "tsc --noEmit",
|
|
@@ -10235,7 +10242,7 @@ function appPageTsx(opts) {
|
|
|
10235
10242
|
Module slug: <code>${escapeHtml(opts.slug)}</code>
|
|
10236
10243
|
</p>
|
|
10237
10244
|
<p style={{ marginTop: 24, color: '#64748b', fontSize: 14 }}>
|
|
10238
|
-
Run <code>
|
|
10245
|
+
Run <code>npm run dev</code> to start the app and the watch loop.
|
|
10239
10246
|
<br />
|
|
10240
10247
|
Edit <code>module.json</code> to add tables; regenerated types
|
|
10241
10248
|
land in <code>@dashai/generated</code>.
|
|
@@ -10269,7 +10276,7 @@ debugger to Next), use the legacy two-terminal flow:
|
|
|
10269
10276
|
|
|
10270
10277
|
\`\`\`bash
|
|
10271
10278
|
dashwise module generate # produces node_modules/@dashai/generated/
|
|
10272
|
-
|
|
10279
|
+
npm run dev:watch # watch module.json + auto-regenerate
|
|
10273
10280
|
npm run dev:next # plain Next.js dev server (in another terminal)
|
|
10274
10281
|
\`\`\`
|
|
10275
10282
|
|
|
@@ -10314,7 +10321,7 @@ debugger to Next), use the legacy two-terminal flow:
|
|
|
10314
10321
|
|
|
10315
10322
|
\`\`\`bash
|
|
10316
10323
|
dashwise module generate # produces node_modules/@dashai/generated/
|
|
10317
|
-
|
|
10324
|
+
npm run dev:watch # watch + regen on module.json save (terminal 1)
|
|
10318
10325
|
npm run dev:next # next dev on :3000 (terminal 2)
|
|
10319
10326
|
\`\`\`
|
|
10320
10327
|
|
|
@@ -11119,9 +11126,10 @@ function packageJsonCustom(opts) {
|
|
|
11119
11126
|
version: "0.0.1",
|
|
11120
11127
|
private: true,
|
|
11121
11128
|
type: "module",
|
|
11122
|
-
engines: { node:
|
|
11129
|
+
engines: { node: NODE_ENGINES },
|
|
11123
11130
|
scripts: {
|
|
11124
|
-
dev: "dashwise
|
|
11131
|
+
dev: "dashwise dev",
|
|
11132
|
+
"dev:watch": "dashwise module dev",
|
|
11125
11133
|
"dev:next": "next dev",
|
|
11126
11134
|
"dashwise:generate": "dashwise module generate",
|
|
11127
11135
|
typecheck: "tsc --noEmit",
|
|
@@ -12206,7 +12214,9 @@ function claudeMd(opts) {
|
|
|
12206
12214
|
Instructions for AI coding agents (Claude Code, Cursor, etc.) working
|
|
12207
12215
|
in this Dashwise module. Read this before making changes.
|
|
12208
12216
|
|
|
12209
|
-
|
|
12217
|
+
Every table declares exactly one primary field (the row's name everywhere in Dashwise).
|
|
12218
|
+
|
|
12219
|
+
${opts.cloudReady === false ? "" : cloudAgentInstructions() + "\n" + claudeCloudNote(opts.apiUrl)}
|
|
12210
12220
|
## What this module is
|
|
12211
12221
|
|
|
12212
12222
|
A Dashwise ${isCustom ? "**custom Next.js** module (full app runtime, machine-class)" : "**hand-authored simple** module (isolate runtime)"} authored against \`@dashai/sdk\`.
|
|
@@ -12350,17 +12360,18 @@ which calls the schema endpoints and rewrites the local
|
|
|
12350
12360
|
Common commands:
|
|
12351
12361
|
|
|
12352
12362
|
\`\`\`
|
|
12353
|
-
dashwise table create <slug> --field
|
|
12363
|
+
dashwise table create <slug> --field title:text:primary
|
|
12354
12364
|
dashwise table delete <slug>
|
|
12355
12365
|
dashwise field add <table> <slug:type[:required]>
|
|
12356
12366
|
dashwise field remove <table> <slug>
|
|
12357
12367
|
dashwise field set-type <table> <slug> <type>
|
|
12358
12368
|
\`\`\`
|
|
12359
12369
|
|
|
12360
|
-
Allowed field types (
|
|
12370
|
+
Allowed field types (21): \`text\`, \`long_text\`, \`number\`, \`boolean\`,
|
|
12361
12371
|
\`date\`, \`datetime\`, \`single_select\`, \`multi_select\`,
|
|
12362
12372
|
\`link_row\`, \`file\`, \`multi_file\`, \`email\`, \`url\`,
|
|
12363
|
-
\`phone\`, \`formula\`, \`ai\`, \`uuid\`, \`jsonb
|
|
12373
|
+
\`phone\`, \`formula\`, \`ai\`, \`uuid\`, \`jsonb\`,
|
|
12374
|
+
\`rating\`, \`duration\`, \`autonumber\`.
|
|
12364
12375
|
|
|
12365
12376
|
### \`human_edit\` \u2014 who may hand-edit each column
|
|
12366
12377
|
|
|
@@ -12614,7 +12625,7 @@ what tables it owns.
|
|
|
12614
12625
|
|
|
12615
12626
|
| Table | Columns | Notes |
|
|
12616
12627
|
|---|---|---|
|
|
12617
|
-
| \`items\` | \`title:text\`, \`done:boolean\` | seeded example \u2014 edit or replace; \`dashwise table create <slug> --field
|
|
12628
|
+
| \`items\` | \`title:text:primary\`, \`done:boolean\` | seeded example \u2014 edit or replace; \`dashwise table create <slug> --field title:text:primary\` adds more |
|
|
12618
12629
|
|
|
12619
12630
|
## API / contract
|
|
12620
12631
|
|
|
@@ -12742,21 +12753,153 @@ primary_region = "iad"
|
|
|
12742
12753
|
`;
|
|
12743
12754
|
}
|
|
12744
12755
|
function cloudAgentInstructions() {
|
|
12745
|
-
return `##
|
|
12756
|
+
return `## Default way of working
|
|
12757
|
+
|
|
12758
|
+
These are defaults. When the person you work for says otherwise (in the chat,
|
|
12759
|
+
or in their own \`CLAUDE.md\` / \`AGENTS.md\`), follow them instead.
|
|
12760
|
+
|
|
12761
|
+
- Assume the person is not a developer. Before and after each step, say in one
|
|
12762
|
+
or two plain sentences what you are doing and why, for example "I'm saving
|
|
12763
|
+
your changes so Dashwise can see them." When a term like branch or pull
|
|
12764
|
+
request is unavoidable, explain it in a few words.
|
|
12765
|
+
- You handle git. Get the finished work onto the repository's default branch
|
|
12766
|
+
(usually \`main\`) yourself, so the person, Dashwise and the next agent all
|
|
12767
|
+
see the latest version. The person needs no git, branch or pull request
|
|
12768
|
+
knowledge:
|
|
12769
|
+
- Working locally on the person's computer: commit on the default branch and push it.
|
|
12770
|
+
- Working in the cloud (Codex cloud, Claude Code on the web): if your tool
|
|
12771
|
+
lets you push to the default branch or merge your own pull request, do it
|
|
12772
|
+
once the checks pass. If it only lets you push a branch, push it and tell the
|
|
12773
|
+
person in plain words that the changes are saved and that Release in
|
|
12774
|
+
Dashwise includes them. Don't ask them to merge anything.
|
|
12775
|
+
- Never force-push and never rewrite the default branch's history.
|
|
12776
|
+
- The person only confirms Release and Take live in Dashwise. When the work is
|
|
12777
|
+
pushed, tell them it is ready: in Dashwise open the module, click Release,
|
|
12778
|
+
then Take live.
|
|
12779
|
+
|
|
12780
|
+
## Cloud workflow
|
|
12746
12781
|
|
|
12747
12782
|
- Read \`module.json\`, the task and \`spec.md\` first. Preserve module identity and permission checks.
|
|
12748
|
-
- Use Node 22
|
|
12783
|
+
- Use Node 22 or 24 (\`.nvmrc\` prefers 22; Codex's default 24 works too). From the repository root run \`bash scripts/cloud-setup.sh\`.
|
|
12749
12784
|
- 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.
|
|
12750
12785
|
- Update \`spec.md\` for behavior changes and \`agent-log.md\` in the same commit. Read \`CLAUDE.md\` for SDK, authorization and UI conventions.
|
|
12751
|
-
-
|
|
12752
|
-
-
|
|
12786
|
+
- \`npm run smoke:production\` starts the built app on a local loopback port. If a cloud sandbox refuses that port, rerun the command with the network permission; never skip the check.
|
|
12787
|
+
- Report what you changed, the pushed SHA and the checks you ran, including failures, in plain words.
|
|
12788
|
+
- 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.
|
|
12753
12789
|
- Never automatically release, publish, deploy or take an installation live. Release and Live require an explicit human action.
|
|
12790
|
+
- 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.
|
|
12791
|
+
|
|
12792
|
+
## If you can't see the repository
|
|
12793
|
+
|
|
12794
|
+
Connecting GitHub in the AI tool is not enough for a private repository. Tell
|
|
12795
|
+
the person, in plain words, to also install the tool's GitHub app on the GitHub
|
|
12796
|
+
organisation that owns this repository and give it access to this repository
|
|
12797
|
+
(an organisation owner may have to approve it), then start the task again:
|
|
12798
|
+
|
|
12799
|
+
- Claude: the Claude GitHub app, https://github.com/apps/claude
|
|
12800
|
+
- Codex: the ChatGPT Codex Connector, https://github.com/apps/chatgpt-codex-connector
|
|
12801
|
+
|
|
12802
|
+
## Dashwise login (only for full development)
|
|
12803
|
+
|
|
12804
|
+
The checks (\`npm run typecheck\`, \`npm test\`, \`npm run build\`,
|
|
12805
|
+
\`npm run smoke:production\`) need no login. Full Dashwise development does:
|
|
12806
|
+
\`npm run dev\` (\`dashwise dev\`: the app plus the watcher), \`dashwise module
|
|
12807
|
+
generate\` and the schema commands. When the task needs them:
|
|
12808
|
+
|
|
12809
|
+
- If \`dashwise\` is not installed, run \`npm install -g @dashai/cli@latest\`.
|
|
12810
|
+
- Ask the person once, in plain words, for example: "To run your module
|
|
12811
|
+
against Dashwise I need to sign in as you. I'll show you a code and a link;
|
|
12812
|
+
open the link and approve." Then run \`dashwise login --no-prompt\` and show
|
|
12813
|
+
them the code and link it prints. The login acts with the person's own rights.
|
|
12814
|
+
- Keep the login outside the repository. In the cloud, run CLI commands with
|
|
12815
|
+
\`XDG_CONFIG_HOME=/tmp/dashwise-config\` so the saved login lands in
|
|
12816
|
+
\`/tmp\`, never in the checkout.
|
|
12817
|
+
- Never write a token into a script, a file in the repository, a commit, an
|
|
12818
|
+
environment setup script or snapshot, or a log.
|
|
12819
|
+
- In the cloud, run CLI commands with \`NODE_USE_ENV_PROXY=1\` so the CLI
|
|
12820
|
+
uses the environment's network proxy (newer CLI versions do this on their own).
|
|
12821
|
+
- A cloud environment forgets the login when it restarts; ask again only when
|
|
12822
|
+
a command reports that the login is missing.
|
|
12823
|
+
`;
|
|
12824
|
+
}
|
|
12825
|
+
function companyApiHost(apiUrl) {
|
|
12826
|
+
if (!apiUrl) return null;
|
|
12827
|
+
try {
|
|
12828
|
+
const host = new URL(apiUrl).host;
|
|
12829
|
+
return host in HOSTED_API_HOSTS ? host : null;
|
|
12830
|
+
} catch {
|
|
12831
|
+
return null;
|
|
12832
|
+
}
|
|
12833
|
+
}
|
|
12834
|
+
function apiHostLine(apiUrl) {
|
|
12835
|
+
const host = companyApiHost(apiUrl);
|
|
12836
|
+
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";
|
|
12837
|
+
}
|
|
12838
|
+
function codexEnvironmentInstructions(apiUrl) {
|
|
12839
|
+
const host = companyApiHost(apiUrl);
|
|
12840
|
+
const apiUrlValue = host ? `https://${host}` : "https://<the company API host above>";
|
|
12841
|
+
return `## Codex cloud environment
|
|
12842
|
+
|
|
12843
|
+
When the person asks you to set up the Codex cloud environment for this
|
|
12844
|
+
repository, use exactly these settings. Show each one to the person and say in
|
|
12845
|
+
one sentence what it is for.
|
|
12846
|
+
|
|
12847
|
+
| Setting | Value |
|
|
12848
|
+
|---|---|
|
|
12849
|
+
| Node.js | 22 or 24 (24 is Codex's default and works; \`.nvmrc\` prefers 22) |
|
|
12850
|
+
| Setup script | \`if [ -f scripts/cloud-setup.sh ]; then bash scripts/cloud-setup.sh; else echo "No Dashwise template yet, setup skipped"; fi\` |
|
|
12851
|
+
| Agent internet access | On: keep the "package managers" preset and add only the company's Dashwise API host (below) |
|
|
12852
|
+
| Environment variables | \`DASHWISE_API_URL=${apiUrlValue}\`, \`NODE_USE_ENV_PROXY=1\` |
|
|
12853
|
+
| Secrets | none |
|
|
12854
|
+
|
|
12855
|
+
Set the network access before you start the setup, so the first run can
|
|
12856
|
+
already install packages. The preset covers npm and GitHub; the one added
|
|
12857
|
+
domain is:
|
|
12858
|
+
|
|
12859
|
+
- ${apiHostLine(apiUrl)}
|
|
12860
|
+
|
|
12861
|
+
The module and the CLI talk to the company's Dashwise API. Do not add other
|
|
12862
|
+
Dashwise hosts. If the environment offers no preset, allow exactly
|
|
12863
|
+
${CLOUD_ALLOWED_HOSTS.map((h) => `\`${h}\``).join(", ")} plus that host.
|
|
12864
|
+
|
|
12865
|
+
The setup script checks Node 22 or 24 and installs the exact dependencies with
|
|
12866
|
+
\`npm ci\`. The guard lets a first run pass while the repository holds only a
|
|
12867
|
+
README.
|
|
12868
|
+
|
|
12869
|
+
Never add secrets: no \`dwk_\` module key, no \`dwc_\` CLI token and no password,
|
|
12870
|
+
and never put a \`dashwise login\` into the setup script. Delivering code needs
|
|
12871
|
+
none of them; for full development see "Dashwise login".
|
|
12872
|
+
|
|
12873
|
+
To check the environment, run \`npm run typecheck\`, \`npm test\`,
|
|
12874
|
+
\`npm run build\` and \`npm run smoke:production\`. If one fails because of a
|
|
12875
|
+
setting, fix the setting; otherwise report the failure in plain words.
|
|
12876
|
+
|
|
12877
|
+
If the repository has no \`package.json\` yet (only a README), skip these
|
|
12878
|
+
checks: the setup script already skipped installing, and the first task adds
|
|
12879
|
+
the template. Tell the person the settings are ready.
|
|
12880
|
+
|
|
12881
|
+
When the checks pass, or were skipped for a README-only repository, tell the
|
|
12882
|
+
person: "The environment works. Click Publish to save it for your next tasks."
|
|
12883
|
+
Publishing is their click.
|
|
12884
|
+
`;
|
|
12885
|
+
}
|
|
12886
|
+
function claudeCloudNote(apiUrl) {
|
|
12887
|
+
const host = companyApiHost(apiUrl);
|
|
12888
|
+
return `## Claude cloud
|
|
12889
|
+
|
|
12890
|
+
Claude Code on the web has no environment setup chat. It reads this repository
|
|
12891
|
+
and \`CLAUDE.md\`, and \`.claude/settings.json\` runs \`scripts/cloud-setup.sh\`
|
|
12892
|
+
when a cloud session starts. Node 22 or 24 both work. If
|
|
12893
|
+
the environment has a network setting, allow the same domains as for Codex:
|
|
12894
|
+
${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.
|
|
12754
12895
|
`;
|
|
12755
12896
|
}
|
|
12756
12897
|
function agentsMd(opts) {
|
|
12757
12898
|
return `# ${opts.name}: agent instructions
|
|
12758
12899
|
|
|
12759
|
-
|
|
12900
|
+
Every table declares exactly one primary field (the row's name everywhere in Dashwise).
|
|
12901
|
+
|
|
12902
|
+
` + cloudAgentInstructions() + "\n" + codexEnvironmentInstructions(opts.apiUrl) + "\n" + claudeCloudNote(opts.apiUrl);
|
|
12760
12903
|
}
|
|
12761
12904
|
function nodeVersion() {
|
|
12762
12905
|
return "22\n";
|
|
@@ -12778,9 +12921,12 @@ fi
|
|
|
12778
12921
|
repo_root="$(cd -- "$(dirname -- "\${BASH_SOURCE[0]}")/.." && pwd)"
|
|
12779
12922
|
cd "$repo_root"
|
|
12780
12923
|
fail() { printf 'cloud-setup: %s\\n' "$1" >&2; exit 1; }
|
|
12781
|
-
command -v node >/dev/null 2>&1 || fail 'Node 22 is required. Select Node 22 in your cloud environment, then retry.'
|
|
12782
|
-
|
|
12783
|
-
|
|
12924
|
+
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.'
|
|
12925
|
+
case "$(node -p 'process.versions.node.split(".")[0]')" in
|
|
12926
|
+
22|24) ;;
|
|
12927
|
+
*) 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.' ;;
|
|
12928
|
+
esac
|
|
12929
|
+
command -v npm >/dev/null 2>&1 || fail 'npm is required with Node 22 or 24.'
|
|
12784
12930
|
[[ -f package-lock.json && -f package.json ]] || fail 'Missing package.json or committed package-lock.json. Run this from a complete repository checkout.'
|
|
12785
12931
|
|
|
12786
12932
|
# Only the Claude startup/resume path reuses an unchanged install. Explicit
|
|
@@ -12788,15 +12934,21 @@ command -v npm >/dev/null 2>&1 || fail 'npm is required with Node 22.'
|
|
|
12788
12934
|
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"));')"
|
|
12789
12935
|
stamp_file="node_modules/.dashwise-cloud-setup"
|
|
12790
12936
|
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
|
|
12791
|
-
printf 'cloud-setup: dependencies already match Node
|
|
12937
|
+
printf 'cloud-setup: dependencies already match this Node version and the lockfile.\\n'
|
|
12792
12938
|
exit 0
|
|
12793
12939
|
fi
|
|
12794
12940
|
# Keep install output out of session logs: third-party lifecycle scripts may
|
|
12795
12941
|
# echo environment variables. npm's own detailed logs stay in its local cache.
|
|
12796
12942
|
rm -f "$stamp_file"
|
|
12797
|
-
|
|
12943
|
+
# Some sandboxes mount HOME read-only; npm then cannot write its cache.
|
|
12944
|
+
npm_cache="\${npm_config_cache:-\${HOME:-}/.npm}"
|
|
12945
|
+
if ! { mkdir -p "$npm_cache" 2>/dev/null && [[ -w "$npm_cache" ]]; }; then
|
|
12946
|
+
export npm_config_cache="\${TMPDIR:-/tmp}/npm-cache"
|
|
12947
|
+
printf 'cloud-setup: %s is not writable, using %s for the npm cache.\\n' "$npm_cache" "$npm_config_cache"
|
|
12948
|
+
fi
|
|
12949
|
+
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.'
|
|
12798
12950
|
printf '%s' "$stamp" > "$stamp_file"
|
|
12799
|
-
printf 'cloud-setup: dependencies installed with Node
|
|
12951
|
+
printf 'cloud-setup: dependencies installed with Node %s.\\n' "$(node -p 'process.versions.node')"
|
|
12800
12952
|
`;
|
|
12801
12953
|
}
|
|
12802
12954
|
function claudeSettingsJson() {
|
|
@@ -12952,7 +13104,7 @@ function cloudReadme() {
|
|
|
12952
13104
|
return `
|
|
12953
13105
|
## Cloud builders
|
|
12954
13106
|
|
|
12955
|
-
Use Node 22 (the root runtime pins) and run \`bash scripts/cloud-setup.sh\`.
|
|
13107
|
+
Use Node 22 (the root runtime pins; 24 also works) and run \`bash scripts/cloud-setup.sh\`.
|
|
12956
13108
|
Commit \`package-lock.json\` after any dependency change; setup uses \`npm ci\`.
|
|
12957
13109
|
Checks: \`npm run typecheck\`, \`npm test\`, \`npm run build\`, \`npm run smoke:production\`.
|
|
12958
13110
|
The smoke command starts the built server, probes anonymous API denial and stops
|
|
@@ -12961,33 +13113,49 @@ The example uses \`lib/items.ts\` directly against the SDK, so a fresh checkout
|
|
|
12961
13113
|
can build without authenticated code generation. Keep it aligned with the
|
|
12962
13114
|
example table, or replace it when you author your schema.
|
|
12963
13115
|
|
|
12964
|
-
Codex:
|
|
12965
|
-
|
|
12966
|
-
|
|
12967
|
-
|
|
13116
|
+
Codex: \`AGENTS.md\` \u2192 "Codex cloud environment" lists the exact settings
|
|
13117
|
+
(Node 22 or 24, setup script, network, \`DASHWISE_API_URL\`, no secrets);
|
|
13118
|
+
Codex's environment setup chat can follow it, then you click Publish. GitHub
|
|
13119
|
+
authorization is configured in the provider: install the ChatGPT Codex
|
|
13120
|
+
Connector on your GitHub organisation so Codex can see a private repository.
|
|
12968
13121
|
|
|
12969
13122
|
Claude Code web: \`.claude/settings.json\` ships a cloud-only SessionStart
|
|
12970
13123
|
hook for startup/resume. It calls \`bash "$CLAUDE_PROJECT_DIR"/scripts/cloud-setup.sh --claude\`;
|
|
12971
13124
|
local sessions skip installation and unchanged cloud installs reuse dependencies.
|
|
12972
|
-
Select Node 22 in the environment; do not rely on a setup subshell changing the
|
|
13125
|
+
Select Node 22 or 24 in the environment; do not rely on a setup subshell changing the
|
|
12973
13126
|
runtime of later agent commands. The same script can be used in the environment
|
|
12974
13127
|
setup field. Allow \`registry.npmjs.org\` for installs (Claude's Trusted list).
|
|
12975
13128
|
The [official hook recipe](https://code.claude.com/docs/en/cloud-environments#install-dependencies-with-a-sessionstart-hook)
|
|
12976
13129
|
explains startup and cache behavior.
|
|
12977
13130
|
|
|
12978
|
-
Read \`AGENTS.md\` / \`CLAUDE.md
|
|
12979
|
-
|
|
13131
|
+
Read \`AGENTS.md\` / \`CLAUDE.md\`. By default agents get the work onto the
|
|
13132
|
+
default branch themselves and explain each step in plain words; Release in
|
|
13133
|
+
Dashwise includes work an agent could only push to a branch. Release and Live
|
|
13134
|
+
remain human actions. No Dashwise token is needed for
|
|
13135
|
+
repository code delivery.
|
|
12980
13136
|
`;
|
|
12981
13137
|
}
|
|
12982
|
-
var DEPLOY_TARGETS, SCAFFOLD_DEFAULT_API_URL, MODULE_SLUG_REGEX;
|
|
13138
|
+
var NODE_ENGINES, DEPLOY_TARGETS, SCAFFOLD_DEFAULT_API_URL, MODULE_SLUG_REGEX, CLOUD_ALLOWED_HOSTS, HOSTED_API_HOSTS;
|
|
12983
13139
|
var init_scaffold = __esm({
|
|
12984
13140
|
"src/lib/scaffold.ts"() {
|
|
12985
13141
|
init_sdk_version();
|
|
12986
13142
|
init_simple();
|
|
12987
13143
|
init_custom();
|
|
13144
|
+
NODE_ENGINES = "22.x || 24.x";
|
|
12988
13145
|
DEPLOY_TARGETS = ["railway", "vercel", "fly"];
|
|
12989
13146
|
SCAFFOLD_DEFAULT_API_URL = "http://localhost:3000";
|
|
12990
13147
|
MODULE_SLUG_REGEX = /^[a-z]([a-z0-9-]*[a-z0-9])?$/;
|
|
13148
|
+
CLOUD_ALLOWED_HOSTS = [
|
|
13149
|
+
"registry.npmjs.org",
|
|
13150
|
+
"github.com",
|
|
13151
|
+
"api.github.com",
|
|
13152
|
+
"raw.githubusercontent.com",
|
|
13153
|
+
"codeload.github.com"
|
|
13154
|
+
];
|
|
13155
|
+
HOSTED_API_HOSTS = {
|
|
13156
|
+
"api.dashwise.dev": "app.dashwise.dev",
|
|
13157
|
+
"api.dashwise.net": "app.dashwise.net"
|
|
13158
|
+
};
|
|
12991
13159
|
}
|
|
12992
13160
|
});
|
|
12993
13161
|
function isFreshCloneEntry(name) {
|
|
@@ -13065,7 +13233,7 @@ function canAdoptCloudFiles(dir, templatePackage) {
|
|
|
13065
13233
|
if (lock.lockfileVersion !== 2 && lock.lockfileVersion !== 3 || !root) {
|
|
13066
13234
|
return false;
|
|
13067
13235
|
}
|
|
13068
|
-
if (pkg.engines?.node !== "22.x" || !pkg.scripts || !expected.scripts) {
|
|
13236
|
+
if (pkg.engines?.node !== "22.x" && pkg.engines?.node !== expected.engines?.node || !pkg.scripts || !expected.scripts) {
|
|
13069
13237
|
return false;
|
|
13070
13238
|
}
|
|
13071
13239
|
for (const script of ["typecheck", "test", "build", "start", "smoke:production"]) {
|
|
@@ -13168,12 +13336,14 @@ async function moduleInitCommand(slugArg, options) {
|
|
|
13168
13336
|
const templatePackage = (kind === "custom" ? packageJsonCustom : packageJson)({
|
|
13169
13337
|
slug});
|
|
13170
13338
|
const cloudReady = writeMode === "all" || canAdoptCloudFiles(dir, templatePackage);
|
|
13339
|
+
const companyApiUrl = resolveExplicitApiUrl();
|
|
13171
13340
|
const scaffoldOpts = {
|
|
13172
13341
|
cloudReady,
|
|
13173
13342
|
slug,
|
|
13174
13343
|
name,
|
|
13175
13344
|
kind,
|
|
13176
|
-
...options.git !== void 0 ? { repositoryUrl: options.git } : {}
|
|
13345
|
+
...options.git !== void 0 ? { repositoryUrl: options.git } : {},
|
|
13346
|
+
...companyApiUrl !== null ? { apiUrl: companyApiUrl } : {}
|
|
13177
13347
|
};
|
|
13178
13348
|
const scaffoldApiUrl = resolveApiUrl();
|
|
13179
13349
|
const files = [
|
|
@@ -13371,7 +13541,7 @@ async function moduleInitCommand(slugArg, options) {
|
|
|
13371
13541
|
|
|
13372
13542
|
${dim(" (or, for the two-terminal flow:")}
|
|
13373
13543
|
${dim(" dashwise module generate")} ${dim("# materialize typed wrappers")}
|
|
13374
|
-
${dim("
|
|
13544
|
+
${dim(" npm run dev:watch")} ${dim("# watch + auto-regenerate")}
|
|
13375
13545
|
${dim(" npm run dev:next # next dev on :3000)")}
|
|
13376
13546
|
${devInstall ? `
|
|
13377
13547
|
${dim(`Dev installation id=${devInstall.installation.id}; API key expires ${devInstall.api_key_expires_at}.`)}
|
|
@@ -14023,6 +14193,16 @@ function printApplySummary(applied) {
|
|
|
14023
14193
|
const updated = asArray(applied.tables_updated);
|
|
14024
14194
|
const removed = asArray(applied.tables_removed);
|
|
14025
14195
|
success(`Manifest applied to the dev install.`);
|
|
14196
|
+
for (const warning of asArray(applied.warnings)) {
|
|
14197
|
+
const message = strProp(warning, "message");
|
|
14198
|
+
if (!message) continue;
|
|
14199
|
+
const warningCode = strProp(warning, "code");
|
|
14200
|
+
const table = strProp(warning, "table");
|
|
14201
|
+
const field = strProp(warning, "field");
|
|
14202
|
+
const location = table ? `${table}${field ? `.${field}` : ""}` : field;
|
|
14203
|
+
const prefix = [warningCode, location].filter(Boolean).join(" ");
|
|
14204
|
+
warn(`${prefix ? `${code(prefix)}: ` : ""}${message}`);
|
|
14205
|
+
}
|
|
14026
14206
|
if (created.length === 0 && updated.length === 0 && removed.length === 0) {
|
|
14027
14207
|
info(dim("No table changes (metadata-only or already in sync)."));
|
|
14028
14208
|
return;
|
|
@@ -14132,6 +14312,76 @@ var init_push = __esm({
|
|
|
14132
14312
|
}
|
|
14133
14313
|
});
|
|
14134
14314
|
|
|
14315
|
+
// ../sdk/dist/index.js
|
|
14316
|
+
function primaryDeclarationIssues(table) {
|
|
14317
|
+
const invalid = table.fields.findIndex(
|
|
14318
|
+
(field2) => field2.primary !== void 0 && field2.primary !== true
|
|
14319
|
+
);
|
|
14320
|
+
if (invalid !== -1) {
|
|
14321
|
+
return [
|
|
14322
|
+
{ path: ["fields", invalid, "primary"], message: "primary must be literal true or omitted" }
|
|
14323
|
+
];
|
|
14324
|
+
}
|
|
14325
|
+
const marked = table.fields.filter((field2) => field2.primary === true);
|
|
14326
|
+
if (marked.length !== 1) {
|
|
14327
|
+
return [
|
|
14328
|
+
{
|
|
14329
|
+
path: ["fields"],
|
|
14330
|
+
message: `Table ${table.slug}: mark exactly one field as primary: true (the row's label in links, pickers, titles)`
|
|
14331
|
+
}
|
|
14332
|
+
];
|
|
14333
|
+
}
|
|
14334
|
+
const field = marked[0];
|
|
14335
|
+
const path = ["fields", table.fields.indexOf(field), "primary"];
|
|
14336
|
+
if (field.type === "formula") {
|
|
14337
|
+
return [{ path, message: "formula fields cannot be the primary of a module table yet" }];
|
|
14338
|
+
}
|
|
14339
|
+
if (!PRIMARY_TYPES.has(field.type)) {
|
|
14340
|
+
return [
|
|
14341
|
+
{
|
|
14342
|
+
path,
|
|
14343
|
+
message: `Table ${table.slug}: mark a field that can be the primary with primary: true (${field.slug} of type ${field.type} cannot be primary)`
|
|
14344
|
+
}
|
|
14345
|
+
];
|
|
14346
|
+
}
|
|
14347
|
+
return [];
|
|
14348
|
+
}
|
|
14349
|
+
function weakPrimaryWarning(table) {
|
|
14350
|
+
const field = table.fields.find((field2) => field2.primary === true);
|
|
14351
|
+
if (!field || !WEAK_PRIMARY_TYPES.has(field.type)) return void 0;
|
|
14352
|
+
return `${field.slug} is a weak row label; prefer a short text field such as a name or title`;
|
|
14353
|
+
}
|
|
14354
|
+
var PRIMARY_TYPES, WEAK_PRIMARY_TYPES;
|
|
14355
|
+
var init_dist = __esm({
|
|
14356
|
+
"../sdk/dist/index.js"() {
|
|
14357
|
+
PRIMARY_TYPES = /* @__PURE__ */ new Set([
|
|
14358
|
+
"text",
|
|
14359
|
+
"long_text",
|
|
14360
|
+
"number",
|
|
14361
|
+
"date",
|
|
14362
|
+
"datetime",
|
|
14363
|
+
"single_select",
|
|
14364
|
+
"multi_select",
|
|
14365
|
+
"email",
|
|
14366
|
+
"url",
|
|
14367
|
+
"phone",
|
|
14368
|
+
"rating",
|
|
14369
|
+
"duration",
|
|
14370
|
+
"autonumber"
|
|
14371
|
+
]);
|
|
14372
|
+
WEAK_PRIMARY_TYPES = /* @__PURE__ */ new Set([
|
|
14373
|
+
"single_select",
|
|
14374
|
+
"multi_select",
|
|
14375
|
+
"number",
|
|
14376
|
+
"rating",
|
|
14377
|
+
"duration",
|
|
14378
|
+
"date",
|
|
14379
|
+
"datetime",
|
|
14380
|
+
"autonumber"
|
|
14381
|
+
]);
|
|
14382
|
+
}
|
|
14383
|
+
});
|
|
14384
|
+
|
|
14135
14385
|
// src/lib/field-spec.ts
|
|
14136
14386
|
function parseFieldSpec(spec) {
|
|
14137
14387
|
const parts = spec.split(":");
|
|
@@ -14186,6 +14436,7 @@ function parseFieldSpec(spec) {
|
|
|
14186
14436
|
);
|
|
14187
14437
|
}
|
|
14188
14438
|
if (flag === "required") result.required = true;
|
|
14439
|
+
if (flag === "primary") result.primary = true;
|
|
14189
14440
|
}
|
|
14190
14441
|
}
|
|
14191
14442
|
if ((type === "single_select" || type === "multi_select") && result.options === void 0) {
|
|
@@ -14228,6 +14479,9 @@ function parseFieldJson(json) {
|
|
|
14228
14479
|
`--field-json missing or invalid ${quote("type")}. Allowed: ${FIELD_TYPES.join(", ")}.`
|
|
14229
14480
|
);
|
|
14230
14481
|
}
|
|
14482
|
+
if (o.primary !== void 0 && o.primary !== true) {
|
|
14483
|
+
throw new FieldSpecError("primary must be literal true or omitted");
|
|
14484
|
+
}
|
|
14231
14485
|
return o;
|
|
14232
14486
|
}
|
|
14233
14487
|
function quote(s) {
|
|
@@ -14240,6 +14494,9 @@ var init_field_spec = __esm({
|
|
|
14240
14494
|
"text",
|
|
14241
14495
|
"long_text",
|
|
14242
14496
|
"number",
|
|
14497
|
+
"rating",
|
|
14498
|
+
"duration",
|
|
14499
|
+
"autonumber",
|
|
14243
14500
|
"boolean",
|
|
14244
14501
|
"date",
|
|
14245
14502
|
"datetime",
|
|
@@ -14255,7 +14512,7 @@ var init_field_spec = __esm({
|
|
|
14255
14512
|
"ai"
|
|
14256
14513
|
];
|
|
14257
14514
|
SLUG_REGEX = /^[a-z][a-z0-9_]*[a-z0-9]$/;
|
|
14258
|
-
KNOWN_FLAGS = /* @__PURE__ */ new Set(["required"]);
|
|
14515
|
+
KNOWN_FLAGS = /* @__PURE__ */ new Set(["required", "primary"]);
|
|
14259
14516
|
DECIMALS_FLAG_REGEX = /^\d+$/;
|
|
14260
14517
|
MAX_DECIMAL_PLACES = 10;
|
|
14261
14518
|
FieldSpecError = class extends Error {
|
|
@@ -14276,7 +14533,7 @@ __export(create_exports, {
|
|
|
14276
14533
|
});
|
|
14277
14534
|
async function tableCreateCommand(slug, options) {
|
|
14278
14535
|
if (!slug) {
|
|
14279
|
-
fail("Usage: `dashwise table create <slug> --field <slug>:<type>[:required] [...]`");
|
|
14536
|
+
fail("Usage: `dashwise table create <slug> --field <slug>:<type>[:primary,required] [...]`");
|
|
14280
14537
|
return 1;
|
|
14281
14538
|
}
|
|
14282
14539
|
const fields = [];
|
|
@@ -14308,6 +14565,15 @@ async function tableCreateCommand(slug, options) {
|
|
|
14308
14565
|
}
|
|
14309
14566
|
seen.add(f.slug);
|
|
14310
14567
|
}
|
|
14568
|
+
const table = { slug, fields };
|
|
14569
|
+
const issues = primaryDeclarationIssues(table);
|
|
14570
|
+
if (issues.length > 0) {
|
|
14571
|
+
for (const issue of issues) fail(issue.message);
|
|
14572
|
+
info("Mark the row name explicitly, e.g. --field title:text:primary.");
|
|
14573
|
+
return 1;
|
|
14574
|
+
}
|
|
14575
|
+
const primaryWarning = weakPrimaryWarning(table);
|
|
14576
|
+
if (primaryWarning) warn(primaryWarning);
|
|
14311
14577
|
let ctx;
|
|
14312
14578
|
try {
|
|
14313
14579
|
ctx = loadSchemaCommandContext({
|
|
@@ -14400,6 +14666,7 @@ function surfaceFailure(err) {
|
|
|
14400
14666
|
}
|
|
14401
14667
|
var init_create = __esm({
|
|
14402
14668
|
"src/commands/table/create.ts"() {
|
|
14669
|
+
init_dist();
|
|
14403
14670
|
init_manifest_io();
|
|
14404
14671
|
init_api_client();
|
|
14405
14672
|
init_manifest_merge();
|
|
@@ -14619,6 +14886,26 @@ var init_set_type = __esm({
|
|
|
14619
14886
|
}
|
|
14620
14887
|
});
|
|
14621
14888
|
|
|
14889
|
+
// src/lib/env-proxy.ts
|
|
14890
|
+
var PROXY_VARS = ["HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy"];
|
|
14891
|
+
function hasProxyEnv(env = process.env) {
|
|
14892
|
+
return PROXY_VARS.some((name) => Boolean(env[name]?.trim()));
|
|
14893
|
+
}
|
|
14894
|
+
async function installEnvProxy(env = process.env) {
|
|
14895
|
+
if (!hasProxyEnv(env)) return false;
|
|
14896
|
+
const undici = await import('undici');
|
|
14897
|
+
undici.setGlobalDispatcher(new undici.EnvHttpProxyAgent());
|
|
14898
|
+
Object.assign(globalThis, {
|
|
14899
|
+
fetch: undici.fetch,
|
|
14900
|
+
FormData: undici.FormData,
|
|
14901
|
+
Headers: undici.Headers,
|
|
14902
|
+
Request: undici.Request,
|
|
14903
|
+
Response: undici.Response
|
|
14904
|
+
});
|
|
14905
|
+
env.NODE_USE_ENV_PROXY ??= "1";
|
|
14906
|
+
return true;
|
|
14907
|
+
}
|
|
14908
|
+
|
|
14622
14909
|
// src/lib/excess-args.ts
|
|
14623
14910
|
init_output();
|
|
14624
14911
|
var SMART_DASH_RE = /^[‒–—―−]/;
|
|
@@ -15021,6 +15308,440 @@ function toMe(cached) {
|
|
|
15021
15308
|
};
|
|
15022
15309
|
}
|
|
15023
15310
|
|
|
15311
|
+
// src/commands/doctor.ts
|
|
15312
|
+
init_api_client();
|
|
15313
|
+
init_config();
|
|
15314
|
+
init_output();
|
|
15315
|
+
init_sdk_version();
|
|
15316
|
+
var MIN_NODE = [20, 18, 1];
|
|
15317
|
+
var CLOUD_FILES = [
|
|
15318
|
+
"scripts/cloud-setup.sh",
|
|
15319
|
+
".claude/settings.json",
|
|
15320
|
+
"AGENTS.md",
|
|
15321
|
+
"CLAUDE.md"
|
|
15322
|
+
];
|
|
15323
|
+
async function doctorCommand(options, deps) {
|
|
15324
|
+
const checks = await runDoctorChecks(resolve(options.dir ?? process.cwd()), deps);
|
|
15325
|
+
const ok = !checks.some((c2) => c2.status === "fail");
|
|
15326
|
+
if (options.json) {
|
|
15327
|
+
out(JSON.stringify({ ok, checks }, null, 2));
|
|
15328
|
+
} else {
|
|
15329
|
+
for (const c2 of checks) {
|
|
15330
|
+
out(`${badge(c2.status)} ${c2.label}: ${c2.detail}`);
|
|
15331
|
+
if (c2.fix) out(` ${dim("\u2192")} ${c2.fix}`);
|
|
15332
|
+
}
|
|
15333
|
+
const fails = checks.filter((c2) => c2.status === "fail").length;
|
|
15334
|
+
const warns = checks.filter((c2) => c2.status === "warn").length;
|
|
15335
|
+
out("");
|
|
15336
|
+
out(
|
|
15337
|
+
fails === 0 && warns === 0 ? pc.green("Everything looks good.") : `${fails} problem${fails === 1 ? "" : "s"}, ${warns} warning${warns === 1 ? "" : "s"}.`
|
|
15338
|
+
);
|
|
15339
|
+
}
|
|
15340
|
+
return ok ? 0 : 1;
|
|
15341
|
+
}
|
|
15342
|
+
async function runDoctorChecks(dir, deps) {
|
|
15343
|
+
const checks = [];
|
|
15344
|
+
const apiUrl = resolveApiUrl();
|
|
15345
|
+
checks.push(targetCheck(apiUrl));
|
|
15346
|
+
const cfg = safeReadConfig2();
|
|
15347
|
+
let signedIn = false;
|
|
15348
|
+
if (!cfg?.token) {
|
|
15349
|
+
checks.push({
|
|
15350
|
+
id: "login",
|
|
15351
|
+
label: "Login",
|
|
15352
|
+
status: "fail",
|
|
15353
|
+
detail: "not signed in",
|
|
15354
|
+
fix: `Run ${code("dashwise login")}.`
|
|
15355
|
+
});
|
|
15356
|
+
checks.push(await connectionOnly(apiUrl));
|
|
15357
|
+
} else {
|
|
15358
|
+
try {
|
|
15359
|
+
const me = await apiRequest("GET", "/api/auth/me");
|
|
15360
|
+
signedIn = true;
|
|
15361
|
+
checks.push({
|
|
15362
|
+
id: "login",
|
|
15363
|
+
label: "Login",
|
|
15364
|
+
status: "ok",
|
|
15365
|
+
detail: `signed in as ${me.email}`
|
|
15366
|
+
});
|
|
15367
|
+
checks.push({
|
|
15368
|
+
id: "connection",
|
|
15369
|
+
label: "Connection",
|
|
15370
|
+
status: "ok",
|
|
15371
|
+
detail: `${apiUrl} answers`
|
|
15372
|
+
});
|
|
15373
|
+
} catch (err) {
|
|
15374
|
+
if (err instanceof ApiError && err.code === "NETWORK_ERROR") {
|
|
15375
|
+
checks.push({
|
|
15376
|
+
id: "login",
|
|
15377
|
+
label: "Login",
|
|
15378
|
+
status: "skip",
|
|
15379
|
+
detail: "not checked, Dashwise is not reachable"
|
|
15380
|
+
});
|
|
15381
|
+
checks.push(unreachable(apiUrl));
|
|
15382
|
+
} else if (err instanceof ApiError && err.status === 401) {
|
|
15383
|
+
checks.push({
|
|
15384
|
+
id: "login",
|
|
15385
|
+
label: "Login",
|
|
15386
|
+
status: "fail",
|
|
15387
|
+
detail: "the saved login is expired or was revoked",
|
|
15388
|
+
fix: `Run ${code("dashwise login")} again.`
|
|
15389
|
+
});
|
|
15390
|
+
checks.push({
|
|
15391
|
+
id: "connection",
|
|
15392
|
+
label: "Connection",
|
|
15393
|
+
status: "ok",
|
|
15394
|
+
detail: `${apiUrl} answers`
|
|
15395
|
+
});
|
|
15396
|
+
} else {
|
|
15397
|
+
checks.push({
|
|
15398
|
+
id: "login",
|
|
15399
|
+
label: "Login",
|
|
15400
|
+
status: "fail",
|
|
15401
|
+
detail: `could not check the login (${err.message})`,
|
|
15402
|
+
fix: `Try again; if it stays, run ${code("dashwise login")}.`
|
|
15403
|
+
});
|
|
15404
|
+
checks.push({
|
|
15405
|
+
id: "connection",
|
|
15406
|
+
label: "Connection",
|
|
15407
|
+
status: "warn",
|
|
15408
|
+
detail: `${apiUrl} answered with an error`
|
|
15409
|
+
});
|
|
15410
|
+
}
|
|
15411
|
+
}
|
|
15412
|
+
}
|
|
15413
|
+
checks.push(nodeCheck(deps.nodeVersion));
|
|
15414
|
+
checks.push(
|
|
15415
|
+
await versionCheck(
|
|
15416
|
+
"cli",
|
|
15417
|
+
"Dashwise CLI",
|
|
15418
|
+
deps.cliVersion,
|
|
15419
|
+
await deps.latestVersion("@dashai/cli"),
|
|
15420
|
+
`Run ${code("npx @dashai/cli@latest <command>")} or ${code("npm install -g @dashai/cli@latest")}.`
|
|
15421
|
+
)
|
|
15422
|
+
);
|
|
15423
|
+
const manifestPath2 = join(dir, "module.json");
|
|
15424
|
+
if (!existsSync(manifestPath2)) {
|
|
15425
|
+
checks.push({
|
|
15426
|
+
id: "module",
|
|
15427
|
+
label: "Module",
|
|
15428
|
+
status: "skip",
|
|
15429
|
+
detail: `no ${code("module.json")} here, so the module checks were skipped`,
|
|
15430
|
+
fix: `Run ${code("dashwise doctor")} inside a module folder to check it too.`
|
|
15431
|
+
});
|
|
15432
|
+
return checks;
|
|
15433
|
+
}
|
|
15434
|
+
let parsed = false;
|
|
15435
|
+
let manifest = null;
|
|
15436
|
+
try {
|
|
15437
|
+
manifest = JSON.parse(readFileSync(manifestPath2, "utf-8"));
|
|
15438
|
+
parsed = true;
|
|
15439
|
+
} catch (err) {
|
|
15440
|
+
checks.push({
|
|
15441
|
+
id: "module",
|
|
15442
|
+
label: "module.json",
|
|
15443
|
+
status: "fail",
|
|
15444
|
+
detail: `cannot be read: ${err.message}`,
|
|
15445
|
+
fix: `Fix the JSON in ${code("module.json")}.`
|
|
15446
|
+
});
|
|
15447
|
+
}
|
|
15448
|
+
const header = manifest !== null && typeof manifest === "object" ? manifest.module : void 0;
|
|
15449
|
+
const slug = typeof header?.slug === "string" ? header.slug : null;
|
|
15450
|
+
if (parsed) checks.push(await manifestCheck(manifest, signedIn));
|
|
15451
|
+
const sdk = readInstalledSdk(dir);
|
|
15452
|
+
checks.push(
|
|
15453
|
+
sdk ? await versionCheck(
|
|
15454
|
+
"sdk",
|
|
15455
|
+
"Dashwise SDK",
|
|
15456
|
+
sdk.version,
|
|
15457
|
+
await deps.latestVersion("@dashai/sdk"),
|
|
15458
|
+
`Run ${code("npm install @dashai/sdk@latest")}.`
|
|
15459
|
+
) : {
|
|
15460
|
+
id: "sdk",
|
|
15461
|
+
label: "Dashwise SDK",
|
|
15462
|
+
status: "warn",
|
|
15463
|
+
detail: "not installed in this folder",
|
|
15464
|
+
fix: `Run ${code("npm install")}.`
|
|
15465
|
+
}
|
|
15466
|
+
);
|
|
15467
|
+
checks.push(await repoCheck(dir, slug, signedIn, deps));
|
|
15468
|
+
const missing = CLOUD_FILES.filter((f) => !existsSync(join(dir, f)));
|
|
15469
|
+
checks.push(
|
|
15470
|
+
missing.length === 0 ? {
|
|
15471
|
+
id: "cloud-setup",
|
|
15472
|
+
label: "Cloud setup files",
|
|
15473
|
+
status: "ok",
|
|
15474
|
+
detail: "present (cloud agents can set up this module)"
|
|
15475
|
+
} : {
|
|
15476
|
+
id: "cloud-setup",
|
|
15477
|
+
label: "Cloud setup files",
|
|
15478
|
+
status: "warn",
|
|
15479
|
+
detail: `missing: ${missing.join(", ")}`,
|
|
15480
|
+
fix: `Copy them from a fresh ${code("dashwise init")} of the template, so Claude Code web and Codex cloud can set this module up.`
|
|
15481
|
+
}
|
|
15482
|
+
);
|
|
15483
|
+
return checks;
|
|
15484
|
+
}
|
|
15485
|
+
function targetCheck(apiUrl) {
|
|
15486
|
+
const source = process.env.DASHWISE_API_URL?.trim() ? "from DASHWISE_API_URL" : "from your saved login";
|
|
15487
|
+
if (sameOrigin(apiUrl, KNOWN_API_URLS.prod)) {
|
|
15488
|
+
return {
|
|
15489
|
+
id: "target",
|
|
15490
|
+
label: "Target",
|
|
15491
|
+
status: "warn",
|
|
15492
|
+
detail: `PRODUCTION (${apiUrl}, ${source}). Commands here change real company data.`,
|
|
15493
|
+
fix: `To build against dev, run ${code(`dashwise login --api-url ${KNOWN_API_URLS.dev}`)} or set ${code(`DASHWISE_API_URL=${KNOWN_API_URLS.dev}`)}.`
|
|
15494
|
+
};
|
|
15495
|
+
}
|
|
15496
|
+
if (sameOrigin(apiUrl, KNOWN_API_URLS.dev)) {
|
|
15497
|
+
return {
|
|
15498
|
+
id: "target",
|
|
15499
|
+
label: "Target",
|
|
15500
|
+
status: "ok",
|
|
15501
|
+
detail: `dev (${apiUrl}, ${source})`
|
|
15502
|
+
};
|
|
15503
|
+
}
|
|
15504
|
+
return {
|
|
15505
|
+
id: "target",
|
|
15506
|
+
label: "Target",
|
|
15507
|
+
status: "ok",
|
|
15508
|
+
detail: `custom (${apiUrl}, ${source})`
|
|
15509
|
+
};
|
|
15510
|
+
}
|
|
15511
|
+
async function connectionOnly(apiUrl) {
|
|
15512
|
+
try {
|
|
15513
|
+
await apiRequest("GET", "/api/auth/me", void 0, { auth: "public" });
|
|
15514
|
+
} catch (err) {
|
|
15515
|
+
if (err instanceof ApiError && err.code === "NETWORK_ERROR") {
|
|
15516
|
+
return unreachable(apiUrl);
|
|
15517
|
+
}
|
|
15518
|
+
}
|
|
15519
|
+
return {
|
|
15520
|
+
id: "connection",
|
|
15521
|
+
label: "Connection",
|
|
15522
|
+
status: "ok",
|
|
15523
|
+
detail: `${apiUrl} answers`
|
|
15524
|
+
};
|
|
15525
|
+
}
|
|
15526
|
+
function unreachable(apiUrl) {
|
|
15527
|
+
return {
|
|
15528
|
+
id: "connection",
|
|
15529
|
+
label: "Connection",
|
|
15530
|
+
status: "fail",
|
|
15531
|
+
detail: `cannot reach ${apiUrl}`,
|
|
15532
|
+
fix: "Check your network, VPN or proxy, and that the address is right."
|
|
15533
|
+
};
|
|
15534
|
+
}
|
|
15535
|
+
function nodeCheck(version) {
|
|
15536
|
+
const parts = version.replace(/^v/, "").split(".").map((n) => Number.parseInt(n, 10));
|
|
15537
|
+
let supported = parts.length === 3 && parts.every(Number.isFinite);
|
|
15538
|
+
for (let i = 0; supported && i < 3; i++) {
|
|
15539
|
+
if (parts[i] !== MIN_NODE[i]) {
|
|
15540
|
+
supported = parts[i] > MIN_NODE[i];
|
|
15541
|
+
break;
|
|
15542
|
+
}
|
|
15543
|
+
}
|
|
15544
|
+
return supported ? { id: "node", label: "Node", status: "ok", detail: version } : {
|
|
15545
|
+
id: "node",
|
|
15546
|
+
label: "Node",
|
|
15547
|
+
status: "fail",
|
|
15548
|
+
detail: `${version} is too old`,
|
|
15549
|
+
fix: `Install Node ${MIN_NODE.join(".")} or newer (the template pins Node 22).`
|
|
15550
|
+
};
|
|
15551
|
+
}
|
|
15552
|
+
async function versionCheck(id, label, installed, latest, fix) {
|
|
15553
|
+
if (!latest) {
|
|
15554
|
+
return {
|
|
15555
|
+
id,
|
|
15556
|
+
label,
|
|
15557
|
+
status: "ok",
|
|
15558
|
+
detail: `${installed} (could not look up the newest version)`
|
|
15559
|
+
};
|
|
15560
|
+
}
|
|
15561
|
+
return compareVersions(installed, latest) < 0 ? {
|
|
15562
|
+
id,
|
|
15563
|
+
label,
|
|
15564
|
+
status: "warn",
|
|
15565
|
+
detail: `${installed}, newest is ${latest}`,
|
|
15566
|
+
fix
|
|
15567
|
+
} : { id, label, status: "ok", detail: `${installed} (newest)` };
|
|
15568
|
+
}
|
|
15569
|
+
async function manifestCheck(manifest, signedIn) {
|
|
15570
|
+
if (manifest === null || typeof manifest !== "object" || Array.isArray(manifest)) {
|
|
15571
|
+
return {
|
|
15572
|
+
id: "module",
|
|
15573
|
+
label: "module.json",
|
|
15574
|
+
status: "fail",
|
|
15575
|
+
detail: "is not a JSON object",
|
|
15576
|
+
fix: `Run ${code("dashwise module validate")} and fix ${code("module.json")}.`
|
|
15577
|
+
};
|
|
15578
|
+
}
|
|
15579
|
+
if (!signedIn) {
|
|
15580
|
+
return {
|
|
15581
|
+
id: "module",
|
|
15582
|
+
label: "module.json",
|
|
15583
|
+
status: "ok",
|
|
15584
|
+
detail: "readable (not validated, you are not signed in)"
|
|
15585
|
+
};
|
|
15586
|
+
}
|
|
15587
|
+
try {
|
|
15588
|
+
await apiRequest("POST", "/api/modules/manifest/validate", manifest);
|
|
15589
|
+
return {
|
|
15590
|
+
id: "module",
|
|
15591
|
+
label: "module.json",
|
|
15592
|
+
status: "ok",
|
|
15593
|
+
detail: "valid"
|
|
15594
|
+
};
|
|
15595
|
+
} catch (err) {
|
|
15596
|
+
if (err instanceof ApiError && err.status === 400) {
|
|
15597
|
+
return {
|
|
15598
|
+
id: "module",
|
|
15599
|
+
label: "module.json",
|
|
15600
|
+
status: "fail",
|
|
15601
|
+
detail: `invalid: ${err.message}`,
|
|
15602
|
+
fix: `Run ${code("dashwise module validate")} for the full list.`
|
|
15603
|
+
};
|
|
15604
|
+
}
|
|
15605
|
+
return {
|
|
15606
|
+
id: "module",
|
|
15607
|
+
label: "module.json",
|
|
15608
|
+
status: "warn",
|
|
15609
|
+
detail: `not validated (${err.message})`
|
|
15610
|
+
};
|
|
15611
|
+
}
|
|
15612
|
+
}
|
|
15613
|
+
async function repoCheck(dir, slug, signedIn, deps) {
|
|
15614
|
+
const origin = deps.gitOrigin(dir);
|
|
15615
|
+
if (!slug || !signedIn) {
|
|
15616
|
+
return {
|
|
15617
|
+
id: "repository",
|
|
15618
|
+
label: "Repository",
|
|
15619
|
+
status: "skip",
|
|
15620
|
+
detail: !slug ? `no module slug in ${code("module.json")}` : "not checked, you are not signed in"
|
|
15621
|
+
};
|
|
15622
|
+
}
|
|
15623
|
+
let linked = null;
|
|
15624
|
+
try {
|
|
15625
|
+
const mod = await apiRequest(
|
|
15626
|
+
"GET",
|
|
15627
|
+
`/api/modules/by-slug/${encodeURIComponent(slug)}`
|
|
15628
|
+
);
|
|
15629
|
+
if (!("repository_url" in mod)) {
|
|
15630
|
+
return {
|
|
15631
|
+
id: "repository",
|
|
15632
|
+
label: "Repository",
|
|
15633
|
+
status: "warn",
|
|
15634
|
+
code: "REPOSITORY_NOT_VISIBLE",
|
|
15635
|
+
detail: `you cannot see the repository of ${code(slug)}: only its builders and owners in its company can`,
|
|
15636
|
+
fix: "Ask one of the module owners to add you as a builder."
|
|
15637
|
+
};
|
|
15638
|
+
}
|
|
15639
|
+
linked = mod.repository_url ?? null;
|
|
15640
|
+
} catch (err) {
|
|
15641
|
+
return {
|
|
15642
|
+
id: "repository",
|
|
15643
|
+
label: "Repository",
|
|
15644
|
+
status: "warn",
|
|
15645
|
+
detail: err instanceof ApiError && err.status === 404 ? `module ${code(slug)} is not found in this Dashwise` : `could not read the module (${err.message})`,
|
|
15646
|
+
fix: "Check the target above and that you can build this module."
|
|
15647
|
+
};
|
|
15648
|
+
}
|
|
15649
|
+
if (!linked) {
|
|
15650
|
+
return {
|
|
15651
|
+
id: "repository",
|
|
15652
|
+
label: "Repository",
|
|
15653
|
+
status: "warn",
|
|
15654
|
+
code: "REPOSITORY_NOT_LINKED",
|
|
15655
|
+
detail: "the module has no repository linked in Dashwise",
|
|
15656
|
+
fix: "Link one on the module's Build tab in Dashwise."
|
|
15657
|
+
};
|
|
15658
|
+
}
|
|
15659
|
+
if (!origin) {
|
|
15660
|
+
return {
|
|
15661
|
+
id: "repository",
|
|
15662
|
+
label: "Repository",
|
|
15663
|
+
status: "warn",
|
|
15664
|
+
detail: `linked to ${linked}, but this folder has no git origin`,
|
|
15665
|
+
fix: `Run ${code(`git remote add origin ${linked}.git`)}.`
|
|
15666
|
+
};
|
|
15667
|
+
}
|
|
15668
|
+
return sameRepo(origin, linked) ? {
|
|
15669
|
+
id: "repository",
|
|
15670
|
+
label: "Repository",
|
|
15671
|
+
status: "ok",
|
|
15672
|
+
detail: `${linked} (matches git origin)`
|
|
15673
|
+
} : {
|
|
15674
|
+
id: "repository",
|
|
15675
|
+
label: "Repository",
|
|
15676
|
+
status: "warn",
|
|
15677
|
+
detail: `git origin is ${origin}, Dashwise builds from ${linked}`,
|
|
15678
|
+
fix: `Push to ${linked}, or link this repository on the module's Build tab.`
|
|
15679
|
+
};
|
|
15680
|
+
}
|
|
15681
|
+
function repoKey(url) {
|
|
15682
|
+
const m = /github\.com[/:]([^/]+)\/([^/]+?)(?:\.git)?\/?$/i.exec(url.trim());
|
|
15683
|
+
return m ? `${m[1]}/${m[2]}`.toLowerCase() : null;
|
|
15684
|
+
}
|
|
15685
|
+
function sameRepo(a, b) {
|
|
15686
|
+
const ka = repoKey(a);
|
|
15687
|
+
return ka !== null && ka === repoKey(b);
|
|
15688
|
+
}
|
|
15689
|
+
function sameOrigin(a, b) {
|
|
15690
|
+
try {
|
|
15691
|
+
return new URL(a).origin === new URL(b).origin;
|
|
15692
|
+
} catch {
|
|
15693
|
+
return false;
|
|
15694
|
+
}
|
|
15695
|
+
}
|
|
15696
|
+
function safeReadConfig2() {
|
|
15697
|
+
try {
|
|
15698
|
+
return readConfig();
|
|
15699
|
+
} catch {
|
|
15700
|
+
return null;
|
|
15701
|
+
}
|
|
15702
|
+
}
|
|
15703
|
+
function badge(status) {
|
|
15704
|
+
switch (status) {
|
|
15705
|
+
case "ok":
|
|
15706
|
+
return pc.green("\u2714");
|
|
15707
|
+
case "warn":
|
|
15708
|
+
return pc.yellow("!");
|
|
15709
|
+
case "fail":
|
|
15710
|
+
return pc.red("\u2716");
|
|
15711
|
+
default:
|
|
15712
|
+
return dim("-");
|
|
15713
|
+
}
|
|
15714
|
+
}
|
|
15715
|
+
function defaultDoctorDeps(cliVersion) {
|
|
15716
|
+
return {
|
|
15717
|
+
cliVersion,
|
|
15718
|
+
nodeVersion: process.version,
|
|
15719
|
+
latestVersion: async (pkg) => {
|
|
15720
|
+
try {
|
|
15721
|
+
const res = await fetch(
|
|
15722
|
+
`https://registry.npmjs.org/${pkg.replace("/", "%2f")}/latest`,
|
|
15723
|
+
{ signal: AbortSignal.timeout(4e3) }
|
|
15724
|
+
);
|
|
15725
|
+
if (!res.ok) return null;
|
|
15726
|
+
const body = await res.json();
|
|
15727
|
+
return typeof body.version === "string" ? body.version : null;
|
|
15728
|
+
} catch {
|
|
15729
|
+
return null;
|
|
15730
|
+
}
|
|
15731
|
+
},
|
|
15732
|
+
gitOrigin: (dir) => {
|
|
15733
|
+
try {
|
|
15734
|
+
return execFileSync("git", ["remote", "get-url", "origin"], {
|
|
15735
|
+
cwd: dir,
|
|
15736
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
15737
|
+
}).toString().trim() || null;
|
|
15738
|
+
} catch {
|
|
15739
|
+
return null;
|
|
15740
|
+
}
|
|
15741
|
+
}
|
|
15742
|
+
};
|
|
15743
|
+
}
|
|
15744
|
+
|
|
15024
15745
|
// src/commands/mcp.ts
|
|
15025
15746
|
init_api_client();
|
|
15026
15747
|
init_config();
|
|
@@ -15145,8 +15866,8 @@ function startMcpProxy(opts) {
|
|
|
15145
15866
|
let streamUnavailable = false;
|
|
15146
15867
|
let streamStarted = false;
|
|
15147
15868
|
let resolveDone;
|
|
15148
|
-
const done = new Promise((
|
|
15149
|
-
resolveDone =
|
|
15869
|
+
const done = new Promise((resolve15) => {
|
|
15870
|
+
resolveDone = resolve15;
|
|
15150
15871
|
});
|
|
15151
15872
|
const trace = (line) => {
|
|
15152
15873
|
if (verbose) log2(line);
|
|
@@ -15632,7 +16353,7 @@ async function revokeSession(endpointId) {
|
|
|
15632
16353
|
() => false
|
|
15633
16354
|
);
|
|
15634
16355
|
const timeout = new Promise(
|
|
15635
|
-
(
|
|
16356
|
+
(resolve15) => setTimeout(() => resolve15(false), REVOKE_TIMEOUT_MS).unref()
|
|
15636
16357
|
);
|
|
15637
16358
|
await Promise.race([attempt, timeout]);
|
|
15638
16359
|
}
|
|
@@ -15810,7 +16531,7 @@ async function mcpAddCommand(options = {}) {
|
|
|
15810
16531
|
fail(`Could not run \`claude\`: ${target.refused} Run the command above yourself.`);
|
|
15811
16532
|
return 1;
|
|
15812
16533
|
}
|
|
15813
|
-
return await new Promise((
|
|
16534
|
+
return await new Promise((resolve15) => {
|
|
15814
16535
|
const child = spawn(target.command, target.args, { shell: target.shell, stdio: "inherit" });
|
|
15815
16536
|
let settled = false;
|
|
15816
16537
|
child.on("error", (err) => {
|
|
@@ -15820,16 +16541,16 @@ async function mcpAddCommand(options = {}) {
|
|
|
15820
16541
|
fail(
|
|
15821
16542
|
`Claude Code is not on your PATH, so nothing was registered. Run the command above once ${code("claude")} is installed.`
|
|
15822
16543
|
);
|
|
15823
|
-
|
|
16544
|
+
resolve15(1);
|
|
15824
16545
|
return;
|
|
15825
16546
|
}
|
|
15826
16547
|
fail(`Could not run \`claude\`: ${err.message}`);
|
|
15827
|
-
|
|
16548
|
+
resolve15(1);
|
|
15828
16549
|
});
|
|
15829
16550
|
child.on("close", (codeOut) => {
|
|
15830
16551
|
if (settled) return;
|
|
15831
16552
|
settled = true;
|
|
15832
|
-
|
|
16553
|
+
resolve15(codeOut ?? 1);
|
|
15833
16554
|
});
|
|
15834
16555
|
});
|
|
15835
16556
|
}
|
|
@@ -15948,10 +16669,10 @@ function upgradeCommand(plugin, marketplace) {
|
|
|
15948
16669
|
var PLUGIN_NAME = "dashwise";
|
|
15949
16670
|
var MARKETPLACE_NAME = "dashwise";
|
|
15950
16671
|
var DEFAULT_MARKETPLACE_SOURCE = "Intuize/dashwise-cli";
|
|
15951
|
-
var defaultRunner = (cmd, args, opts) => new Promise((
|
|
16672
|
+
var defaultRunner = (cmd, args, opts) => new Promise((resolve15) => {
|
|
15952
16673
|
const target = spawnTarget(cmd, args);
|
|
15953
16674
|
if ("refused" in target) {
|
|
15954
|
-
|
|
16675
|
+
resolve15({ code: null, stdout: "", stderr: target.refused, missing: false });
|
|
15955
16676
|
return;
|
|
15956
16677
|
}
|
|
15957
16678
|
const child = spawn(target.command, target.args, {
|
|
@@ -15966,7 +16687,7 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolve14) => {
|
|
|
15966
16687
|
child.on("error", (err) => {
|
|
15967
16688
|
if (settled) return;
|
|
15968
16689
|
settled = true;
|
|
15969
|
-
|
|
16690
|
+
resolve15({
|
|
15970
16691
|
code: null,
|
|
15971
16692
|
stdout,
|
|
15972
16693
|
stderr: stderr || err.message,
|
|
@@ -15976,7 +16697,7 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolve14) => {
|
|
|
15976
16697
|
child.on("close", (exitCode) => {
|
|
15977
16698
|
if (settled) return;
|
|
15978
16699
|
settled = true;
|
|
15979
|
-
|
|
16700
|
+
resolve15({ code: exitCode, stdout, stderr, missing: false });
|
|
15980
16701
|
});
|
|
15981
16702
|
});
|
|
15982
16703
|
function readSkillsOnDisk() {
|
|
@@ -17670,7 +18391,14 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
17670
18391
|
try {
|
|
17671
18392
|
const installs = await listWorkspaceInstallations(workspace2.id, auth);
|
|
17672
18393
|
const byKind = selectInstallsByKind(installs, mod.id);
|
|
17673
|
-
|
|
18394
|
+
const production = byKind.all.filter((row2) => !isDevInstallKind(row2.kind));
|
|
18395
|
+
if (production.length > 1) {
|
|
18396
|
+
fail(
|
|
18397
|
+
"Several production installations exist for this module in this company. Select the installation to update in the app. Nothing was changed."
|
|
18398
|
+
);
|
|
18399
|
+
return 1;
|
|
18400
|
+
}
|
|
18401
|
+
existing = production[0];
|
|
17674
18402
|
devInstall = byKind.dev;
|
|
17675
18403
|
} catch (err) {
|
|
17676
18404
|
fail(
|
|
@@ -17753,7 +18481,8 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
17753
18481
|
mod.id,
|
|
17754
18482
|
{
|
|
17755
18483
|
workspaceId: workspace2.id,
|
|
17756
|
-
|
|
18484
|
+
version: targetVersion,
|
|
18485
|
+
...existing ? { installation_id: existing.id } : {},
|
|
17757
18486
|
...options.force !== void 0 ? { force: options.force } : {}
|
|
17758
18487
|
},
|
|
17759
18488
|
auth
|
|
@@ -17761,6 +18490,12 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
17761
18490
|
} catch (err) {
|
|
17762
18491
|
return surfaceInstallError(err, mod.slug, workspace2.slug, devInstallReplaced);
|
|
17763
18492
|
}
|
|
18493
|
+
if (existing && (isInstallGated(result) || result.installation.id !== existing.id)) {
|
|
18494
|
+
fail(
|
|
18495
|
+
`The backend did not confirm an in-place update of installation #${existing.id}. Check its state in the app before retrying; no fallback install will be attempted.`
|
|
18496
|
+
);
|
|
18497
|
+
return 1;
|
|
18498
|
+
}
|
|
17764
18499
|
if (isInstallGated(result)) {
|
|
17765
18500
|
if (options.json) {
|
|
17766
18501
|
out(JSON.stringify(result));
|
|
@@ -17780,7 +18515,9 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
17780
18515
|
let verified = false;
|
|
17781
18516
|
try {
|
|
17782
18517
|
const after = await listWorkspaceInstallations(workspace2.id, auth);
|
|
17783
|
-
row =
|
|
18518
|
+
row = after.find(
|
|
18519
|
+
(candidate) => candidate.id === result.installation.id && candidate.module_id === mod.id && !isDevInstallKind(candidate.kind)
|
|
18520
|
+
);
|
|
17784
18521
|
verified = row !== void 0;
|
|
17785
18522
|
} catch {
|
|
17786
18523
|
}
|
|
@@ -18390,6 +19127,16 @@ function packedFileLines(files, cap = PACKED_FILE_LIST_CAP) {
|
|
|
18390
19127
|
if (files.length <= cap) return [...files];
|
|
18391
19128
|
return [...files.slice(0, cap), `\u2026 and ${files.length - cap} more`];
|
|
18392
19129
|
}
|
|
19130
|
+
function repositoryLinkedDecision(input) {
|
|
19131
|
+
if (!input.repositoryUrl || input.fromLocal || input.dryRun) return "proceed";
|
|
19132
|
+
return input.interactive ? "confirm" : "refuse";
|
|
19133
|
+
}
|
|
19134
|
+
function canPrompt(stdin, stdout) {
|
|
19135
|
+
return stdin.isTTY === true && stdout.isTTY === true;
|
|
19136
|
+
}
|
|
19137
|
+
function repositoryLinkedWarning(repositoryUrl) {
|
|
19138
|
+
return `These files are not in ${repositoryUrl}; the next repository build replaces them.`;
|
|
19139
|
+
}
|
|
18393
19140
|
async function modulePublishCommand(options) {
|
|
18394
19141
|
const projectRoot = resolve(options.dir ?? process.cwd());
|
|
18395
19142
|
const manifestPath2 = join(projectRoot, "module.json");
|
|
@@ -18486,6 +19233,35 @@ async function modulePublishCommand(options) {
|
|
|
18486
19233
|
);
|
|
18487
19234
|
}
|
|
18488
19235
|
for (const w of tarball.warnings) warn(w);
|
|
19236
|
+
if (mod.repository_url) {
|
|
19237
|
+
const decision = repositoryLinkedDecision({
|
|
19238
|
+
repositoryUrl: mod.repository_url,
|
|
19239
|
+
fromLocal: options.fromLocal === true,
|
|
19240
|
+
dryRun: options.dryRun === true,
|
|
19241
|
+
interactive: canPrompt(process.stdin, process.stdout)
|
|
19242
|
+
});
|
|
19243
|
+
warn(repositoryLinkedWarning(mod.repository_url));
|
|
19244
|
+
if (decision === "refuse") {
|
|
19245
|
+
fail(
|
|
19246
|
+
`${code(slug)} is linked to a repository. Re-run with ${code("--from-local")} to publish these local files anyway.`
|
|
19247
|
+
);
|
|
19248
|
+
return 1;
|
|
19249
|
+
}
|
|
19250
|
+
if (decision === "confirm") {
|
|
19251
|
+
const answer = await select({
|
|
19252
|
+
message: "Publish these local files anyway?",
|
|
19253
|
+
options: [
|
|
19254
|
+
{ value: "go", label: "Yes, publish from local files" },
|
|
19255
|
+
{ value: "cancel", label: "No, abort" }
|
|
19256
|
+
],
|
|
19257
|
+
initialValue: "cancel"
|
|
19258
|
+
});
|
|
19259
|
+
if (isCancel(answer) || answer === "cancel") {
|
|
19260
|
+
fail("Aborted.");
|
|
19261
|
+
return 130;
|
|
19262
|
+
}
|
|
19263
|
+
}
|
|
19264
|
+
}
|
|
18489
19265
|
if (!options.yes && process.stdout.isTTY && !options.dryRun) {
|
|
18490
19266
|
const answer = await select({
|
|
18491
19267
|
message: `Upload + publish ${code(`bump:${bump}`)}?`,
|
|
@@ -19022,7 +19798,7 @@ async function moduleReleaseCommand(slugArg, options) {
|
|
|
19022
19798
|
}
|
|
19023
19799
|
async function wait() {
|
|
19024
19800
|
const remaining = Math.max(0, deadline - Date.now());
|
|
19025
|
-
await new Promise((
|
|
19801
|
+
await new Promise((resolve15) => setTimeout(resolve15, Math.min(options.pollIntervalMs ?? 4e3, remaining)));
|
|
19026
19802
|
}
|
|
19027
19803
|
}
|
|
19028
19804
|
|
|
@@ -22430,7 +23206,7 @@ function formatTimestamp(iso) {
|
|
|
22430
23206
|
return iso.slice(0, 19).replace("T", " ");
|
|
22431
23207
|
}
|
|
22432
23208
|
function sleep2(ms) {
|
|
22433
|
-
return new Promise((
|
|
23209
|
+
return new Promise((resolve15) => setTimeout(resolve15, ms));
|
|
22434
23210
|
}
|
|
22435
23211
|
|
|
22436
23212
|
// src/commands/module/action-log.ts
|
|
@@ -22532,7 +23308,7 @@ async function runTail2(installationId, options) {
|
|
|
22532
23308
|
}
|
|
22533
23309
|
dim(`(tail: transient error \u2014 ${err.message})`);
|
|
22534
23310
|
}
|
|
22535
|
-
await new Promise((
|
|
23311
|
+
await new Promise((resolve15) => setTimeout(resolve15, POLL_INTERVAL_MS2));
|
|
22536
23312
|
}
|
|
22537
23313
|
}
|
|
22538
23314
|
function printTable(rows) {
|
|
@@ -22917,7 +23693,7 @@ async function sleep3(ms, aborted) {
|
|
|
22917
23693
|
const slice = 100;
|
|
22918
23694
|
let waited = 0;
|
|
22919
23695
|
while (waited < ms && !aborted()) {
|
|
22920
|
-
await new Promise((
|
|
23696
|
+
await new Promise((resolve15) => setTimeout(resolve15, Math.min(slice, ms - waited)));
|
|
22921
23697
|
waited += slice;
|
|
22922
23698
|
}
|
|
22923
23699
|
}
|
|
@@ -23467,6 +24243,17 @@ program.command("login").description("Sign in via the device-code flow.").option
|
|
|
23467
24243
|
});
|
|
23468
24244
|
process.exitCode = exit;
|
|
23469
24245
|
});
|
|
24246
|
+
program.command("doctor").description(
|
|
24247
|
+
"Check your setup: which Dashwise you target (dev or prod), login, connection, Node, CLI and SDK versions, and inside a module folder module.json, the repository link and the cloud setup files. Each item says what to do. Exits 1 when something fails."
|
|
24248
|
+
).option("--dir <path>", "Module folder to check (default: current directory).").option("--json", "Print one JSON object { ok, checks } instead of the report.").action(async (cmdOpts) => {
|
|
24249
|
+
process.exitCode = await doctorCommand(
|
|
24250
|
+
{
|
|
24251
|
+
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
24252
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
24253
|
+
},
|
|
24254
|
+
defaultDoctorDeps(getVersion())
|
|
24255
|
+
);
|
|
24256
|
+
});
|
|
23470
24257
|
program.command("logout").description("Clear the local credentials and revoke the server-side token.").action(async () => {
|
|
23471
24258
|
process.exitCode = await logoutCommand();
|
|
23472
24259
|
});
|
|
@@ -24057,6 +24844,9 @@ moduleCmd.command("release [slug]").description("Release an exact pushed reposit
|
|
|
24057
24844
|
});
|
|
24058
24845
|
});
|
|
24059
24846
|
moduleCmd.command("publish").description("Pack the current directory + upload it as a new module version.").option("--dir <path>", "Project root (default: current directory).").option("--bump <direction>", "Semver bump: patch | minor | major.").option("--dry-run", "Validate + upload but do not persist the version row.").option("-y, --yes", "Skip the confirmation prompt (for CI / scripted use).").option(
|
|
24847
|
+
"--from-local",
|
|
24848
|
+
"Publish local files to a module that has a linked repository (the next repository build replaces them)."
|
|
24849
|
+
).option(
|
|
24060
24850
|
"--exclude <pattern...>",
|
|
24061
24851
|
"Extra exclude patterns (segment-matched) appended to the defaults."
|
|
24062
24852
|
).option(
|
|
@@ -24076,6 +24866,7 @@ moduleCmd.command("publish").description("Pack the current directory + upload it
|
|
|
24076
24866
|
...cmdOpts.bump !== void 0 ? { bump: cmdOpts.bump } : {},
|
|
24077
24867
|
...cmdOpts.dryRun !== void 0 ? { dryRun: cmdOpts.dryRun } : {},
|
|
24078
24868
|
...cmdOpts.yes !== void 0 ? { yes: cmdOpts.yes } : {},
|
|
24869
|
+
...cmdOpts.fromLocal !== void 0 ? { fromLocal: cmdOpts.fromLocal } : {},
|
|
24079
24870
|
...cmdOpts.exclude !== void 0 ? { exclude: cmdOpts.exclude } : {},
|
|
24080
24871
|
// commander maps `--no-respect-gitignore` to
|
|
24081
24872
|
// `respectGitignore === false`.
|
|
@@ -24332,12 +25123,12 @@ tableCmd.command("create <slug>").description(
|
|
|
24332
25123
|
"Create a new table. Pass --field flags to define columns. Calls POST /api/installations/:id/schema/tables and syncs the updated manifest back to local module.json."
|
|
24333
25124
|
).option("--dir <path>", "Project root (default: current directory).").option(
|
|
24334
25125
|
"-f, --field <spec>",
|
|
24335
|
-
"Field spec
|
|
25126
|
+
"Field spec: `<slug>:<type>[:primary,required]`. Exactly one eligible field must be primary. Repeat for multiple fields. For options/target/default, use --field-json instead. Example: `--field title:text:primary,required --field done:boolean`.",
|
|
24336
25127
|
(val, prev = []) => [...prev, val],
|
|
24337
25128
|
[]
|
|
24338
25129
|
).option(
|
|
24339
25130
|
"--field-json <json>",
|
|
24340
|
-
"Field as a JSON object \u2014 `{slug,type,required?,options?,target?,default?}`. Repeatable. Use for fields where the simple syntax can't express the shape.",
|
|
25131
|
+
"Field as a JSON object \u2014 `{slug,type,primary?:true,required?,options?,target?,default?}`. Repeatable. Use for fields where the simple syntax can't express the shape.",
|
|
24341
25132
|
(val, prev = []) => [...prev, val],
|
|
24342
25133
|
[]
|
|
24343
25134
|
).action(
|
|
@@ -24401,7 +25192,7 @@ fieldCmd.command("set-type <table-slug> <field-slug> <new-type>").description(
|
|
|
24401
25192
|
}
|
|
24402
25193
|
);
|
|
24403
25194
|
function getVersion() {
|
|
24404
|
-
return "0.
|
|
25195
|
+
return "0.25.0";
|
|
24405
25196
|
}
|
|
24406
25197
|
function parseIntOption(value) {
|
|
24407
25198
|
const n = Number.parseInt(value, 10);
|
|
@@ -24417,6 +25208,7 @@ function collectRepeated(value, previous) {
|
|
|
24417
25208
|
// src/bin.ts
|
|
24418
25209
|
(async () => {
|
|
24419
25210
|
try {
|
|
25211
|
+
await installEnvProxy();
|
|
24420
25212
|
await program.parseAsync(process.argv);
|
|
24421
25213
|
} catch (err) {
|
|
24422
25214
|
if (err instanceof ExcessArgumentsError) {
|