@dashai/cli 0.23.0 → 0.24.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 -0
- package/dist/bin.js +687 -88
- package/dist/bin.js.map +1 -1
- package/package.json +2 -2
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';
|
|
@@ -1406,13 +1406,13 @@ async function resolveDevPort(requested) {
|
|
|
1406
1406
|
}
|
|
1407
1407
|
async function isPortFree(port) {
|
|
1408
1408
|
const { createServer } = await import('net');
|
|
1409
|
-
return new Promise((
|
|
1409
|
+
return new Promise((resolve15) => {
|
|
1410
1410
|
const server = createServer();
|
|
1411
1411
|
server.once("error", () => {
|
|
1412
|
-
|
|
1412
|
+
resolve15(false);
|
|
1413
1413
|
});
|
|
1414
1414
|
server.once("listening", () => {
|
|
1415
|
-
server.close(() =>
|
|
1415
|
+
server.close(() => resolve15(true));
|
|
1416
1416
|
});
|
|
1417
1417
|
server.listen(port, "0.0.0.0");
|
|
1418
1418
|
});
|
|
@@ -1493,6 +1493,62 @@ var init_dev = __esm({
|
|
|
1493
1493
|
PORT_PROBE_LIMIT = 10;
|
|
1494
1494
|
}
|
|
1495
1495
|
});
|
|
1496
|
+
function resolveSdkPackageJson(projectRoot) {
|
|
1497
|
+
let dir = projectRoot;
|
|
1498
|
+
for (; ; ) {
|
|
1499
|
+
const candidate = join(dir, "node_modules", "@dashai", "sdk", "package.json");
|
|
1500
|
+
if (existsSync(candidate)) return candidate;
|
|
1501
|
+
const parent = dirname(dir);
|
|
1502
|
+
if (parent === dir) return null;
|
|
1503
|
+
dir = parent;
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
function readInstalledSdk(projectRoot) {
|
|
1507
|
+
const pkgPath = resolveSdkPackageJson(projectRoot);
|
|
1508
|
+
if (pkgPath === null) return null;
|
|
1509
|
+
try {
|
|
1510
|
+
const parsed = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
1511
|
+
if (typeof parsed.version !== "string" || parsed.version.length === 0) return null;
|
|
1512
|
+
return { version: parsed.version };
|
|
1513
|
+
} catch {
|
|
1514
|
+
return null;
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
function compareVersions(a, b) {
|
|
1518
|
+
const parse = (v) => {
|
|
1519
|
+
const [core = "", pre] = v.trim().replace(/^[^0-9]*/, "").split("-", 2);
|
|
1520
|
+
const nums = core.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
1521
|
+
while (nums.length < 3) nums.push(0);
|
|
1522
|
+
return [nums.slice(0, 3), pre !== void 0 && pre.length > 0];
|
|
1523
|
+
};
|
|
1524
|
+
const [an, apre] = parse(a);
|
|
1525
|
+
const [bn, bpre] = parse(b);
|
|
1526
|
+
for (let i = 0; i < 3; i++) {
|
|
1527
|
+
const d = (an[i] ?? 0) - (bn[i] ?? 0);
|
|
1528
|
+
if (d !== 0) return d;
|
|
1529
|
+
}
|
|
1530
|
+
if (apre === bpre) return 0;
|
|
1531
|
+
return apre ? -1 : 1;
|
|
1532
|
+
}
|
|
1533
|
+
function satisfiesFloor(version, floor) {
|
|
1534
|
+
return compareVersions(version, floor) >= 0;
|
|
1535
|
+
}
|
|
1536
|
+
function checkSdkFloor(projectRoot, floor) {
|
|
1537
|
+
const installed = readInstalledSdk(projectRoot);
|
|
1538
|
+
if (installed === null) return { ok: true, installed: null, floor };
|
|
1539
|
+
return {
|
|
1540
|
+
ok: satisfiesFloor(installed.version, floor),
|
|
1541
|
+
installed: installed.version,
|
|
1542
|
+
floor
|
|
1543
|
+
};
|
|
1544
|
+
}
|
|
1545
|
+
var USES_SDK_FLOOR, SCAFFOLD_SDK_RANGE;
|
|
1546
|
+
var init_sdk_version = __esm({
|
|
1547
|
+
"src/lib/sdk-version.ts"() {
|
|
1548
|
+
USES_SDK_FLOOR = "2.5.0";
|
|
1549
|
+
SCAFFOLD_SDK_RANGE = "^2.5.0";
|
|
1550
|
+
}
|
|
1551
|
+
});
|
|
1496
1552
|
async function selectWorkspace(opts) {
|
|
1497
1553
|
const workspaces = await apiRequest(
|
|
1498
1554
|
"GET",
|
|
@@ -1551,62 +1607,6 @@ var init_workspaces = __esm({
|
|
|
1551
1607
|
init_api_client();
|
|
1552
1608
|
}
|
|
1553
1609
|
});
|
|
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
1610
|
|
|
1611
1611
|
// src/lib/scaffold-locks/simple.json
|
|
1612
1612
|
var simple_default;
|
|
@@ -9990,7 +9990,7 @@ function manifestJson(opts) {
|
|
|
9990
9990
|
// everything on the default — a warning on every column just trains
|
|
9991
9991
|
// people to click through warnings.
|
|
9992
9992
|
fields: [
|
|
9993
|
-
{ slug: "title", type: "text", human_edit: "open" },
|
|
9993
|
+
{ slug: "title", type: "text", primary: true, human_edit: "open" },
|
|
9994
9994
|
{ slug: "done", type: "boolean", human_edit: "guarded" }
|
|
9995
9995
|
]
|
|
9996
9996
|
}
|
|
@@ -12206,6 +12206,8 @@ function claudeMd(opts) {
|
|
|
12206
12206
|
Instructions for AI coding agents (Claude Code, Cursor, etc.) working
|
|
12207
12207
|
in this Dashwise module. Read this before making changes.
|
|
12208
12208
|
|
|
12209
|
+
Every table declares exactly one primary field (the row's name everywhere in Dashwise).
|
|
12210
|
+
|
|
12209
12211
|
${opts.cloudReady === false ? "" : cloudAgentInstructions()}
|
|
12210
12212
|
## What this module is
|
|
12211
12213
|
|
|
@@ -12350,17 +12352,18 @@ which calls the schema endpoints and rewrites the local
|
|
|
12350
12352
|
Common commands:
|
|
12351
12353
|
|
|
12352
12354
|
\`\`\`
|
|
12353
|
-
dashwise table create <slug> --field
|
|
12355
|
+
dashwise table create <slug> --field title:text:primary
|
|
12354
12356
|
dashwise table delete <slug>
|
|
12355
12357
|
dashwise field add <table> <slug:type[:required]>
|
|
12356
12358
|
dashwise field remove <table> <slug>
|
|
12357
12359
|
dashwise field set-type <table> <slug> <type>
|
|
12358
12360
|
\`\`\`
|
|
12359
12361
|
|
|
12360
|
-
Allowed field types (
|
|
12362
|
+
Allowed field types (21): \`text\`, \`long_text\`, \`number\`, \`boolean\`,
|
|
12361
12363
|
\`date\`, \`datetime\`, \`single_select\`, \`multi_select\`,
|
|
12362
12364
|
\`link_row\`, \`file\`, \`multi_file\`, \`email\`, \`url\`,
|
|
12363
|
-
\`phone\`, \`formula\`, \`ai\`, \`uuid\`, \`jsonb
|
|
12365
|
+
\`phone\`, \`formula\`, \`ai\`, \`uuid\`, \`jsonb\`,
|
|
12366
|
+
\`rating\`, \`duration\`, \`autonumber\`.
|
|
12364
12367
|
|
|
12365
12368
|
### \`human_edit\` \u2014 who may hand-edit each column
|
|
12366
12369
|
|
|
@@ -12614,7 +12617,7 @@ what tables it owns.
|
|
|
12614
12617
|
|
|
12615
12618
|
| Table | Columns | Notes |
|
|
12616
12619
|
|---|---|---|
|
|
12617
|
-
| \`items\` | \`title:text\`, \`done:boolean\` | seeded example \u2014 edit or replace; \`dashwise table create <slug> --field
|
|
12620
|
+
| \`items\` | \`title:text:primary\`, \`done:boolean\` | seeded example \u2014 edit or replace; \`dashwise table create <slug> --field title:text:primary\` adds more |
|
|
12618
12621
|
|
|
12619
12622
|
## API / contract
|
|
12620
12623
|
|
|
@@ -12756,6 +12759,8 @@ function cloudAgentInstructions() {
|
|
|
12756
12759
|
function agentsMd(opts) {
|
|
12757
12760
|
return `# ${opts.name}: agent instructions
|
|
12758
12761
|
|
|
12762
|
+
Every table declares exactly one primary field (the row's name everywhere in Dashwise).
|
|
12763
|
+
|
|
12759
12764
|
` + cloudAgentInstructions();
|
|
12760
12765
|
}
|
|
12761
12766
|
function nodeVersion() {
|
|
@@ -14023,6 +14028,16 @@ function printApplySummary(applied) {
|
|
|
14023
14028
|
const updated = asArray(applied.tables_updated);
|
|
14024
14029
|
const removed = asArray(applied.tables_removed);
|
|
14025
14030
|
success(`Manifest applied to the dev install.`);
|
|
14031
|
+
for (const warning of asArray(applied.warnings)) {
|
|
14032
|
+
const message = strProp(warning, "message");
|
|
14033
|
+
if (!message) continue;
|
|
14034
|
+
const warningCode = strProp(warning, "code");
|
|
14035
|
+
const table = strProp(warning, "table");
|
|
14036
|
+
const field = strProp(warning, "field");
|
|
14037
|
+
const location = table ? `${table}${field ? `.${field}` : ""}` : field;
|
|
14038
|
+
const prefix = [warningCode, location].filter(Boolean).join(" ");
|
|
14039
|
+
warn(`${prefix ? `${code(prefix)}: ` : ""}${message}`);
|
|
14040
|
+
}
|
|
14026
14041
|
if (created.length === 0 && updated.length === 0 && removed.length === 0) {
|
|
14027
14042
|
info(dim("No table changes (metadata-only or already in sync)."));
|
|
14028
14043
|
return;
|
|
@@ -14132,6 +14147,76 @@ var init_push = __esm({
|
|
|
14132
14147
|
}
|
|
14133
14148
|
});
|
|
14134
14149
|
|
|
14150
|
+
// ../sdk/dist/index.js
|
|
14151
|
+
function primaryDeclarationIssues(table) {
|
|
14152
|
+
const invalid = table.fields.findIndex(
|
|
14153
|
+
(field2) => field2.primary !== void 0 && field2.primary !== true
|
|
14154
|
+
);
|
|
14155
|
+
if (invalid !== -1) {
|
|
14156
|
+
return [
|
|
14157
|
+
{ path: ["fields", invalid, "primary"], message: "primary must be literal true or omitted" }
|
|
14158
|
+
];
|
|
14159
|
+
}
|
|
14160
|
+
const marked = table.fields.filter((field2) => field2.primary === true);
|
|
14161
|
+
if (marked.length !== 1) {
|
|
14162
|
+
return [
|
|
14163
|
+
{
|
|
14164
|
+
path: ["fields"],
|
|
14165
|
+
message: `Table ${table.slug}: mark exactly one field as primary: true (the row's label in links, pickers, titles)`
|
|
14166
|
+
}
|
|
14167
|
+
];
|
|
14168
|
+
}
|
|
14169
|
+
const field = marked[0];
|
|
14170
|
+
const path = ["fields", table.fields.indexOf(field), "primary"];
|
|
14171
|
+
if (field.type === "formula") {
|
|
14172
|
+
return [{ path, message: "formula fields cannot be the primary of a module table yet" }];
|
|
14173
|
+
}
|
|
14174
|
+
if (!PRIMARY_TYPES.has(field.type)) {
|
|
14175
|
+
return [
|
|
14176
|
+
{
|
|
14177
|
+
path,
|
|
14178
|
+
message: `Table ${table.slug}: mark a field that can be the primary with primary: true (${field.slug} of type ${field.type} cannot be primary)`
|
|
14179
|
+
}
|
|
14180
|
+
];
|
|
14181
|
+
}
|
|
14182
|
+
return [];
|
|
14183
|
+
}
|
|
14184
|
+
function weakPrimaryWarning(table) {
|
|
14185
|
+
const field = table.fields.find((field2) => field2.primary === true);
|
|
14186
|
+
if (!field || !WEAK_PRIMARY_TYPES.has(field.type)) return void 0;
|
|
14187
|
+
return `${field.slug} is a weak row label; prefer a short text field such as a name or title`;
|
|
14188
|
+
}
|
|
14189
|
+
var PRIMARY_TYPES, WEAK_PRIMARY_TYPES;
|
|
14190
|
+
var init_dist = __esm({
|
|
14191
|
+
"../sdk/dist/index.js"() {
|
|
14192
|
+
PRIMARY_TYPES = /* @__PURE__ */ new Set([
|
|
14193
|
+
"text",
|
|
14194
|
+
"long_text",
|
|
14195
|
+
"number",
|
|
14196
|
+
"date",
|
|
14197
|
+
"datetime",
|
|
14198
|
+
"single_select",
|
|
14199
|
+
"multi_select",
|
|
14200
|
+
"email",
|
|
14201
|
+
"url",
|
|
14202
|
+
"phone",
|
|
14203
|
+
"rating",
|
|
14204
|
+
"duration",
|
|
14205
|
+
"autonumber"
|
|
14206
|
+
]);
|
|
14207
|
+
WEAK_PRIMARY_TYPES = /* @__PURE__ */ new Set([
|
|
14208
|
+
"single_select",
|
|
14209
|
+
"multi_select",
|
|
14210
|
+
"number",
|
|
14211
|
+
"rating",
|
|
14212
|
+
"duration",
|
|
14213
|
+
"date",
|
|
14214
|
+
"datetime",
|
|
14215
|
+
"autonumber"
|
|
14216
|
+
]);
|
|
14217
|
+
}
|
|
14218
|
+
});
|
|
14219
|
+
|
|
14135
14220
|
// src/lib/field-spec.ts
|
|
14136
14221
|
function parseFieldSpec(spec) {
|
|
14137
14222
|
const parts = spec.split(":");
|
|
@@ -14186,6 +14271,7 @@ function parseFieldSpec(spec) {
|
|
|
14186
14271
|
);
|
|
14187
14272
|
}
|
|
14188
14273
|
if (flag === "required") result.required = true;
|
|
14274
|
+
if (flag === "primary") result.primary = true;
|
|
14189
14275
|
}
|
|
14190
14276
|
}
|
|
14191
14277
|
if ((type === "single_select" || type === "multi_select") && result.options === void 0) {
|
|
@@ -14228,6 +14314,9 @@ function parseFieldJson(json) {
|
|
|
14228
14314
|
`--field-json missing or invalid ${quote("type")}. Allowed: ${FIELD_TYPES.join(", ")}.`
|
|
14229
14315
|
);
|
|
14230
14316
|
}
|
|
14317
|
+
if (o.primary !== void 0 && o.primary !== true) {
|
|
14318
|
+
throw new FieldSpecError("primary must be literal true or omitted");
|
|
14319
|
+
}
|
|
14231
14320
|
return o;
|
|
14232
14321
|
}
|
|
14233
14322
|
function quote(s) {
|
|
@@ -14240,6 +14329,9 @@ var init_field_spec = __esm({
|
|
|
14240
14329
|
"text",
|
|
14241
14330
|
"long_text",
|
|
14242
14331
|
"number",
|
|
14332
|
+
"rating",
|
|
14333
|
+
"duration",
|
|
14334
|
+
"autonumber",
|
|
14243
14335
|
"boolean",
|
|
14244
14336
|
"date",
|
|
14245
14337
|
"datetime",
|
|
@@ -14255,7 +14347,7 @@ var init_field_spec = __esm({
|
|
|
14255
14347
|
"ai"
|
|
14256
14348
|
];
|
|
14257
14349
|
SLUG_REGEX = /^[a-z][a-z0-9_]*[a-z0-9]$/;
|
|
14258
|
-
KNOWN_FLAGS = /* @__PURE__ */ new Set(["required"]);
|
|
14350
|
+
KNOWN_FLAGS = /* @__PURE__ */ new Set(["required", "primary"]);
|
|
14259
14351
|
DECIMALS_FLAG_REGEX = /^\d+$/;
|
|
14260
14352
|
MAX_DECIMAL_PLACES = 10;
|
|
14261
14353
|
FieldSpecError = class extends Error {
|
|
@@ -14276,7 +14368,7 @@ __export(create_exports, {
|
|
|
14276
14368
|
});
|
|
14277
14369
|
async function tableCreateCommand(slug, options) {
|
|
14278
14370
|
if (!slug) {
|
|
14279
|
-
fail("Usage: `dashwise table create <slug> --field <slug>:<type>[:required] [...]`");
|
|
14371
|
+
fail("Usage: `dashwise table create <slug> --field <slug>:<type>[:primary,required] [...]`");
|
|
14280
14372
|
return 1;
|
|
14281
14373
|
}
|
|
14282
14374
|
const fields = [];
|
|
@@ -14308,6 +14400,15 @@ async function tableCreateCommand(slug, options) {
|
|
|
14308
14400
|
}
|
|
14309
14401
|
seen.add(f.slug);
|
|
14310
14402
|
}
|
|
14403
|
+
const table = { slug, fields };
|
|
14404
|
+
const issues = primaryDeclarationIssues(table);
|
|
14405
|
+
if (issues.length > 0) {
|
|
14406
|
+
for (const issue of issues) fail(issue.message);
|
|
14407
|
+
info("Mark the row name explicitly, e.g. --field title:text:primary.");
|
|
14408
|
+
return 1;
|
|
14409
|
+
}
|
|
14410
|
+
const primaryWarning = weakPrimaryWarning(table);
|
|
14411
|
+
if (primaryWarning) warn(primaryWarning);
|
|
14311
14412
|
let ctx;
|
|
14312
14413
|
try {
|
|
14313
14414
|
ctx = loadSchemaCommandContext({
|
|
@@ -14400,6 +14501,7 @@ function surfaceFailure(err) {
|
|
|
14400
14501
|
}
|
|
14401
14502
|
var init_create = __esm({
|
|
14402
14503
|
"src/commands/table/create.ts"() {
|
|
14504
|
+
init_dist();
|
|
14403
14505
|
init_manifest_io();
|
|
14404
14506
|
init_api_client();
|
|
14405
14507
|
init_manifest_merge();
|
|
@@ -15021,6 +15123,433 @@ function toMe(cached) {
|
|
|
15021
15123
|
};
|
|
15022
15124
|
}
|
|
15023
15125
|
|
|
15126
|
+
// src/commands/doctor.ts
|
|
15127
|
+
init_api_client();
|
|
15128
|
+
init_config();
|
|
15129
|
+
init_output();
|
|
15130
|
+
init_sdk_version();
|
|
15131
|
+
var MIN_NODE_MAJOR = 20;
|
|
15132
|
+
var CLOUD_FILES = [
|
|
15133
|
+
"scripts/cloud-setup.sh",
|
|
15134
|
+
".claude/settings.json",
|
|
15135
|
+
"AGENTS.md",
|
|
15136
|
+
"CLAUDE.md"
|
|
15137
|
+
];
|
|
15138
|
+
async function doctorCommand(options, deps) {
|
|
15139
|
+
const checks = await runDoctorChecks(resolve(options.dir ?? process.cwd()), deps);
|
|
15140
|
+
const ok = !checks.some((c2) => c2.status === "fail");
|
|
15141
|
+
if (options.json) {
|
|
15142
|
+
out(JSON.stringify({ ok, checks }, null, 2));
|
|
15143
|
+
} else {
|
|
15144
|
+
for (const c2 of checks) {
|
|
15145
|
+
out(`${badge(c2.status)} ${c2.label}: ${c2.detail}`);
|
|
15146
|
+
if (c2.fix) out(` ${dim("\u2192")} ${c2.fix}`);
|
|
15147
|
+
}
|
|
15148
|
+
const fails = checks.filter((c2) => c2.status === "fail").length;
|
|
15149
|
+
const warns = checks.filter((c2) => c2.status === "warn").length;
|
|
15150
|
+
out("");
|
|
15151
|
+
out(
|
|
15152
|
+
fails === 0 && warns === 0 ? pc.green("Everything looks good.") : `${fails} problem${fails === 1 ? "" : "s"}, ${warns} warning${warns === 1 ? "" : "s"}.`
|
|
15153
|
+
);
|
|
15154
|
+
}
|
|
15155
|
+
return ok ? 0 : 1;
|
|
15156
|
+
}
|
|
15157
|
+
async function runDoctorChecks(dir, deps) {
|
|
15158
|
+
const checks = [];
|
|
15159
|
+
const apiUrl = resolveApiUrl();
|
|
15160
|
+
checks.push(targetCheck(apiUrl));
|
|
15161
|
+
const cfg = safeReadConfig2();
|
|
15162
|
+
let signedIn = false;
|
|
15163
|
+
if (!cfg?.token) {
|
|
15164
|
+
checks.push({
|
|
15165
|
+
id: "login",
|
|
15166
|
+
label: "Login",
|
|
15167
|
+
status: "fail",
|
|
15168
|
+
detail: "not signed in",
|
|
15169
|
+
fix: `Run ${code("dashwise login")}.`
|
|
15170
|
+
});
|
|
15171
|
+
checks.push(await connectionOnly(apiUrl));
|
|
15172
|
+
} else {
|
|
15173
|
+
try {
|
|
15174
|
+
const me = await apiRequest("GET", "/api/auth/me");
|
|
15175
|
+
signedIn = true;
|
|
15176
|
+
checks.push({
|
|
15177
|
+
id: "login",
|
|
15178
|
+
label: "Login",
|
|
15179
|
+
status: "ok",
|
|
15180
|
+
detail: `signed in as ${me.email}`
|
|
15181
|
+
});
|
|
15182
|
+
checks.push({
|
|
15183
|
+
id: "connection",
|
|
15184
|
+
label: "Connection",
|
|
15185
|
+
status: "ok",
|
|
15186
|
+
detail: `${apiUrl} answers`
|
|
15187
|
+
});
|
|
15188
|
+
} catch (err) {
|
|
15189
|
+
if (err instanceof ApiError && err.code === "NETWORK_ERROR") {
|
|
15190
|
+
checks.push({
|
|
15191
|
+
id: "login",
|
|
15192
|
+
label: "Login",
|
|
15193
|
+
status: "skip",
|
|
15194
|
+
detail: "not checked, Dashwise is not reachable"
|
|
15195
|
+
});
|
|
15196
|
+
checks.push(unreachable(apiUrl));
|
|
15197
|
+
} else if (err instanceof ApiError && err.status === 401) {
|
|
15198
|
+
checks.push({
|
|
15199
|
+
id: "login",
|
|
15200
|
+
label: "Login",
|
|
15201
|
+
status: "fail",
|
|
15202
|
+
detail: "the saved login is expired or was revoked",
|
|
15203
|
+
fix: `Run ${code("dashwise login")} again.`
|
|
15204
|
+
});
|
|
15205
|
+
checks.push({
|
|
15206
|
+
id: "connection",
|
|
15207
|
+
label: "Connection",
|
|
15208
|
+
status: "ok",
|
|
15209
|
+
detail: `${apiUrl} answers`
|
|
15210
|
+
});
|
|
15211
|
+
} else {
|
|
15212
|
+
checks.push({
|
|
15213
|
+
id: "login",
|
|
15214
|
+
label: "Login",
|
|
15215
|
+
status: "fail",
|
|
15216
|
+
detail: `could not check the login (${err.message})`,
|
|
15217
|
+
fix: `Try again; if it stays, run ${code("dashwise login")}.`
|
|
15218
|
+
});
|
|
15219
|
+
checks.push({
|
|
15220
|
+
id: "connection",
|
|
15221
|
+
label: "Connection",
|
|
15222
|
+
status: "warn",
|
|
15223
|
+
detail: `${apiUrl} answered with an error`
|
|
15224
|
+
});
|
|
15225
|
+
}
|
|
15226
|
+
}
|
|
15227
|
+
}
|
|
15228
|
+
checks.push(nodeCheck(deps.nodeVersion));
|
|
15229
|
+
checks.push(
|
|
15230
|
+
await versionCheck(
|
|
15231
|
+
"cli",
|
|
15232
|
+
"Dashwise CLI",
|
|
15233
|
+
deps.cliVersion,
|
|
15234
|
+
await deps.latestVersion("@dashai/cli"),
|
|
15235
|
+
`Run ${code("npx @dashai/cli@latest <command>")} or ${code("npm install -g @dashai/cli@latest")}.`
|
|
15236
|
+
)
|
|
15237
|
+
);
|
|
15238
|
+
const manifestPath2 = join(dir, "module.json");
|
|
15239
|
+
if (!existsSync(manifestPath2)) {
|
|
15240
|
+
checks.push({
|
|
15241
|
+
id: "module",
|
|
15242
|
+
label: "Module",
|
|
15243
|
+
status: "skip",
|
|
15244
|
+
detail: `no ${code("module.json")} here, so the module checks were skipped`,
|
|
15245
|
+
fix: `Run ${code("dashwise doctor")} inside a module folder to check it too.`
|
|
15246
|
+
});
|
|
15247
|
+
return checks;
|
|
15248
|
+
}
|
|
15249
|
+
let parsed = false;
|
|
15250
|
+
let manifest = null;
|
|
15251
|
+
try {
|
|
15252
|
+
manifest = JSON.parse(readFileSync(manifestPath2, "utf-8"));
|
|
15253
|
+
parsed = true;
|
|
15254
|
+
} catch (err) {
|
|
15255
|
+
checks.push({
|
|
15256
|
+
id: "module",
|
|
15257
|
+
label: "module.json",
|
|
15258
|
+
status: "fail",
|
|
15259
|
+
detail: `cannot be read: ${err.message}`,
|
|
15260
|
+
fix: `Fix the JSON in ${code("module.json")}.`
|
|
15261
|
+
});
|
|
15262
|
+
}
|
|
15263
|
+
const header = manifest !== null && typeof manifest === "object" ? manifest.module : void 0;
|
|
15264
|
+
const slug = typeof header?.slug === "string" ? header.slug : null;
|
|
15265
|
+
if (parsed) checks.push(await manifestCheck(manifest, signedIn));
|
|
15266
|
+
const sdk = readInstalledSdk(dir);
|
|
15267
|
+
checks.push(
|
|
15268
|
+
sdk ? await versionCheck(
|
|
15269
|
+
"sdk",
|
|
15270
|
+
"Dashwise SDK",
|
|
15271
|
+
sdk.version,
|
|
15272
|
+
await deps.latestVersion("@dashai/sdk"),
|
|
15273
|
+
`Run ${code("npm install @dashai/sdk@latest")}.`
|
|
15274
|
+
) : {
|
|
15275
|
+
id: "sdk",
|
|
15276
|
+
label: "Dashwise SDK",
|
|
15277
|
+
status: "warn",
|
|
15278
|
+
detail: "not installed in this folder",
|
|
15279
|
+
fix: `Run ${code("npm install")}.`
|
|
15280
|
+
}
|
|
15281
|
+
);
|
|
15282
|
+
checks.push(await repoCheck(dir, slug, signedIn, deps));
|
|
15283
|
+
const missing = CLOUD_FILES.filter((f) => !existsSync(join(dir, f)));
|
|
15284
|
+
checks.push(
|
|
15285
|
+
missing.length === 0 ? {
|
|
15286
|
+
id: "cloud-setup",
|
|
15287
|
+
label: "Cloud setup files",
|
|
15288
|
+
status: "ok",
|
|
15289
|
+
detail: "present (cloud agents can set up this module)"
|
|
15290
|
+
} : {
|
|
15291
|
+
id: "cloud-setup",
|
|
15292
|
+
label: "Cloud setup files",
|
|
15293
|
+
status: "warn",
|
|
15294
|
+
detail: `missing: ${missing.join(", ")}`,
|
|
15295
|
+
fix: `Copy them from a fresh ${code("dashwise init")} of the template, so Claude Code web and Codex cloud can set this module up.`
|
|
15296
|
+
}
|
|
15297
|
+
);
|
|
15298
|
+
return checks;
|
|
15299
|
+
}
|
|
15300
|
+
function targetCheck(apiUrl) {
|
|
15301
|
+
const source = process.env.DASHWISE_API_URL?.trim() ? "from DASHWISE_API_URL" : "from your saved login";
|
|
15302
|
+
if (sameOrigin(apiUrl, KNOWN_API_URLS.prod)) {
|
|
15303
|
+
return {
|
|
15304
|
+
id: "target",
|
|
15305
|
+
label: "Target",
|
|
15306
|
+
status: "warn",
|
|
15307
|
+
detail: `PRODUCTION (${apiUrl}, ${source}). Commands here change real company data.`,
|
|
15308
|
+
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}`)}.`
|
|
15309
|
+
};
|
|
15310
|
+
}
|
|
15311
|
+
if (sameOrigin(apiUrl, KNOWN_API_URLS.dev)) {
|
|
15312
|
+
return {
|
|
15313
|
+
id: "target",
|
|
15314
|
+
label: "Target",
|
|
15315
|
+
status: "ok",
|
|
15316
|
+
detail: `dev (${apiUrl}, ${source})`
|
|
15317
|
+
};
|
|
15318
|
+
}
|
|
15319
|
+
return {
|
|
15320
|
+
id: "target",
|
|
15321
|
+
label: "Target",
|
|
15322
|
+
status: "ok",
|
|
15323
|
+
detail: `custom (${apiUrl}, ${source})`
|
|
15324
|
+
};
|
|
15325
|
+
}
|
|
15326
|
+
async function connectionOnly(apiUrl) {
|
|
15327
|
+
try {
|
|
15328
|
+
await apiRequest("GET", "/api/auth/me", void 0, { auth: "public" });
|
|
15329
|
+
} catch (err) {
|
|
15330
|
+
if (err instanceof ApiError && err.code === "NETWORK_ERROR") {
|
|
15331
|
+
return unreachable(apiUrl);
|
|
15332
|
+
}
|
|
15333
|
+
}
|
|
15334
|
+
return {
|
|
15335
|
+
id: "connection",
|
|
15336
|
+
label: "Connection",
|
|
15337
|
+
status: "ok",
|
|
15338
|
+
detail: `${apiUrl} answers`
|
|
15339
|
+
};
|
|
15340
|
+
}
|
|
15341
|
+
function unreachable(apiUrl) {
|
|
15342
|
+
return {
|
|
15343
|
+
id: "connection",
|
|
15344
|
+
label: "Connection",
|
|
15345
|
+
status: "fail",
|
|
15346
|
+
detail: `cannot reach ${apiUrl}`,
|
|
15347
|
+
fix: "Check your network, VPN or proxy, and that the address is right."
|
|
15348
|
+
};
|
|
15349
|
+
}
|
|
15350
|
+
function nodeCheck(version) {
|
|
15351
|
+
const major = Number.parseInt(version.replace(/^v/, ""), 10);
|
|
15352
|
+
return Number.isFinite(major) && major >= MIN_NODE_MAJOR ? { id: "node", label: "Node", status: "ok", detail: version } : {
|
|
15353
|
+
id: "node",
|
|
15354
|
+
label: "Node",
|
|
15355
|
+
status: "fail",
|
|
15356
|
+
detail: `${version} is too old`,
|
|
15357
|
+
fix: `Install Node ${MIN_NODE_MAJOR} or newer (the template pins Node 22).`
|
|
15358
|
+
};
|
|
15359
|
+
}
|
|
15360
|
+
async function versionCheck(id, label, installed, latest, fix) {
|
|
15361
|
+
if (!latest) {
|
|
15362
|
+
return {
|
|
15363
|
+
id,
|
|
15364
|
+
label,
|
|
15365
|
+
status: "ok",
|
|
15366
|
+
detail: `${installed} (could not look up the newest version)`
|
|
15367
|
+
};
|
|
15368
|
+
}
|
|
15369
|
+
return compareVersions(installed, latest) < 0 ? {
|
|
15370
|
+
id,
|
|
15371
|
+
label,
|
|
15372
|
+
status: "warn",
|
|
15373
|
+
detail: `${installed}, newest is ${latest}`,
|
|
15374
|
+
fix
|
|
15375
|
+
} : { id, label, status: "ok", detail: `${installed} (newest)` };
|
|
15376
|
+
}
|
|
15377
|
+
async function manifestCheck(manifest, signedIn) {
|
|
15378
|
+
if (manifest === null || typeof manifest !== "object" || Array.isArray(manifest)) {
|
|
15379
|
+
return {
|
|
15380
|
+
id: "module",
|
|
15381
|
+
label: "module.json",
|
|
15382
|
+
status: "fail",
|
|
15383
|
+
detail: "is not a JSON object",
|
|
15384
|
+
fix: `Run ${code("dashwise module validate")} and fix ${code("module.json")}.`
|
|
15385
|
+
};
|
|
15386
|
+
}
|
|
15387
|
+
if (!signedIn) {
|
|
15388
|
+
return {
|
|
15389
|
+
id: "module",
|
|
15390
|
+
label: "module.json",
|
|
15391
|
+
status: "ok",
|
|
15392
|
+
detail: "readable (not validated, you are not signed in)"
|
|
15393
|
+
};
|
|
15394
|
+
}
|
|
15395
|
+
try {
|
|
15396
|
+
await apiRequest("POST", "/api/modules/manifest/validate", manifest);
|
|
15397
|
+
return {
|
|
15398
|
+
id: "module",
|
|
15399
|
+
label: "module.json",
|
|
15400
|
+
status: "ok",
|
|
15401
|
+
detail: "valid"
|
|
15402
|
+
};
|
|
15403
|
+
} catch (err) {
|
|
15404
|
+
if (err instanceof ApiError && err.status === 400) {
|
|
15405
|
+
return {
|
|
15406
|
+
id: "module",
|
|
15407
|
+
label: "module.json",
|
|
15408
|
+
status: "fail",
|
|
15409
|
+
detail: `invalid: ${err.message}`,
|
|
15410
|
+
fix: `Run ${code("dashwise module validate")} for the full list.`
|
|
15411
|
+
};
|
|
15412
|
+
}
|
|
15413
|
+
return {
|
|
15414
|
+
id: "module",
|
|
15415
|
+
label: "module.json",
|
|
15416
|
+
status: "warn",
|
|
15417
|
+
detail: `not validated (${err.message})`
|
|
15418
|
+
};
|
|
15419
|
+
}
|
|
15420
|
+
}
|
|
15421
|
+
async function repoCheck(dir, slug, signedIn, deps) {
|
|
15422
|
+
const origin = deps.gitOrigin(dir);
|
|
15423
|
+
if (!slug || !signedIn) {
|
|
15424
|
+
return {
|
|
15425
|
+
id: "repository",
|
|
15426
|
+
label: "Repository",
|
|
15427
|
+
status: "skip",
|
|
15428
|
+
detail: !slug ? `no module slug in ${code("module.json")}` : "not checked, you are not signed in"
|
|
15429
|
+
};
|
|
15430
|
+
}
|
|
15431
|
+
let linked = null;
|
|
15432
|
+
try {
|
|
15433
|
+
const mod = await apiRequest(
|
|
15434
|
+
"GET",
|
|
15435
|
+
`/api/modules/by-slug/${encodeURIComponent(slug)}`
|
|
15436
|
+
);
|
|
15437
|
+
if (!("repository_url" in mod)) {
|
|
15438
|
+
return {
|
|
15439
|
+
id: "repository",
|
|
15440
|
+
label: "Repository",
|
|
15441
|
+
status: "warn",
|
|
15442
|
+
code: "REPOSITORY_NOT_VISIBLE",
|
|
15443
|
+
detail: `you cannot see the repository of ${code(slug)}: only its builders and owners in its company can`,
|
|
15444
|
+
fix: "Ask one of the module owners to add you as a builder."
|
|
15445
|
+
};
|
|
15446
|
+
}
|
|
15447
|
+
linked = mod.repository_url ?? null;
|
|
15448
|
+
} catch (err) {
|
|
15449
|
+
return {
|
|
15450
|
+
id: "repository",
|
|
15451
|
+
label: "Repository",
|
|
15452
|
+
status: "warn",
|
|
15453
|
+
detail: err instanceof ApiError && err.status === 404 ? `module ${code(slug)} is not found in this Dashwise` : `could not read the module (${err.message})`,
|
|
15454
|
+
fix: "Check the target above and that you can build this module."
|
|
15455
|
+
};
|
|
15456
|
+
}
|
|
15457
|
+
if (!linked) {
|
|
15458
|
+
return {
|
|
15459
|
+
id: "repository",
|
|
15460
|
+
label: "Repository",
|
|
15461
|
+
status: "warn",
|
|
15462
|
+
code: "REPOSITORY_NOT_LINKED",
|
|
15463
|
+
detail: "the module has no repository linked in Dashwise",
|
|
15464
|
+
fix: "Link one on the module's Build tab in Dashwise."
|
|
15465
|
+
};
|
|
15466
|
+
}
|
|
15467
|
+
if (!origin) {
|
|
15468
|
+
return {
|
|
15469
|
+
id: "repository",
|
|
15470
|
+
label: "Repository",
|
|
15471
|
+
status: "warn",
|
|
15472
|
+
detail: `linked to ${linked}, but this folder has no git origin`,
|
|
15473
|
+
fix: `Run ${code(`git remote add origin ${linked}.git`)}.`
|
|
15474
|
+
};
|
|
15475
|
+
}
|
|
15476
|
+
return sameRepo(origin, linked) ? {
|
|
15477
|
+
id: "repository",
|
|
15478
|
+
label: "Repository",
|
|
15479
|
+
status: "ok",
|
|
15480
|
+
detail: `${linked} (matches git origin)`
|
|
15481
|
+
} : {
|
|
15482
|
+
id: "repository",
|
|
15483
|
+
label: "Repository",
|
|
15484
|
+
status: "warn",
|
|
15485
|
+
detail: `git origin is ${origin}, Dashwise builds from ${linked}`,
|
|
15486
|
+
fix: `Push to ${linked}, or link this repository on the module's Build tab.`
|
|
15487
|
+
};
|
|
15488
|
+
}
|
|
15489
|
+
function repoKey(url) {
|
|
15490
|
+
const m = /github\.com[/:]([^/]+)\/([^/]+?)(?:\.git)?\/?$/i.exec(url.trim());
|
|
15491
|
+
return m ? `${m[1]}/${m[2]}`.toLowerCase() : null;
|
|
15492
|
+
}
|
|
15493
|
+
function sameRepo(a, b) {
|
|
15494
|
+
const ka = repoKey(a);
|
|
15495
|
+
return ka !== null && ka === repoKey(b);
|
|
15496
|
+
}
|
|
15497
|
+
function sameOrigin(a, b) {
|
|
15498
|
+
try {
|
|
15499
|
+
return new URL(a).origin === new URL(b).origin;
|
|
15500
|
+
} catch {
|
|
15501
|
+
return false;
|
|
15502
|
+
}
|
|
15503
|
+
}
|
|
15504
|
+
function safeReadConfig2() {
|
|
15505
|
+
try {
|
|
15506
|
+
return readConfig();
|
|
15507
|
+
} catch {
|
|
15508
|
+
return null;
|
|
15509
|
+
}
|
|
15510
|
+
}
|
|
15511
|
+
function badge(status) {
|
|
15512
|
+
switch (status) {
|
|
15513
|
+
case "ok":
|
|
15514
|
+
return pc.green("\u2714");
|
|
15515
|
+
case "warn":
|
|
15516
|
+
return pc.yellow("!");
|
|
15517
|
+
case "fail":
|
|
15518
|
+
return pc.red("\u2716");
|
|
15519
|
+
default:
|
|
15520
|
+
return dim("-");
|
|
15521
|
+
}
|
|
15522
|
+
}
|
|
15523
|
+
function defaultDoctorDeps(cliVersion) {
|
|
15524
|
+
return {
|
|
15525
|
+
cliVersion,
|
|
15526
|
+
nodeVersion: process.version,
|
|
15527
|
+
latestVersion: async (pkg) => {
|
|
15528
|
+
try {
|
|
15529
|
+
const res = await fetch(
|
|
15530
|
+
`https://registry.npmjs.org/${pkg.replace("/", "%2f")}/latest`,
|
|
15531
|
+
{ signal: AbortSignal.timeout(4e3) }
|
|
15532
|
+
);
|
|
15533
|
+
if (!res.ok) return null;
|
|
15534
|
+
const body = await res.json();
|
|
15535
|
+
return typeof body.version === "string" ? body.version : null;
|
|
15536
|
+
} catch {
|
|
15537
|
+
return null;
|
|
15538
|
+
}
|
|
15539
|
+
},
|
|
15540
|
+
gitOrigin: (dir) => {
|
|
15541
|
+
try {
|
|
15542
|
+
return execFileSync("git", ["remote", "get-url", "origin"], {
|
|
15543
|
+
cwd: dir,
|
|
15544
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
15545
|
+
}).toString().trim() || null;
|
|
15546
|
+
} catch {
|
|
15547
|
+
return null;
|
|
15548
|
+
}
|
|
15549
|
+
}
|
|
15550
|
+
};
|
|
15551
|
+
}
|
|
15552
|
+
|
|
15024
15553
|
// src/commands/mcp.ts
|
|
15025
15554
|
init_api_client();
|
|
15026
15555
|
init_config();
|
|
@@ -15145,8 +15674,8 @@ function startMcpProxy(opts) {
|
|
|
15145
15674
|
let streamUnavailable = false;
|
|
15146
15675
|
let streamStarted = false;
|
|
15147
15676
|
let resolveDone;
|
|
15148
|
-
const done = new Promise((
|
|
15149
|
-
resolveDone =
|
|
15677
|
+
const done = new Promise((resolve15) => {
|
|
15678
|
+
resolveDone = resolve15;
|
|
15150
15679
|
});
|
|
15151
15680
|
const trace = (line) => {
|
|
15152
15681
|
if (verbose) log2(line);
|
|
@@ -15632,7 +16161,7 @@ async function revokeSession(endpointId) {
|
|
|
15632
16161
|
() => false
|
|
15633
16162
|
);
|
|
15634
16163
|
const timeout = new Promise(
|
|
15635
|
-
(
|
|
16164
|
+
(resolve15) => setTimeout(() => resolve15(false), REVOKE_TIMEOUT_MS).unref()
|
|
15636
16165
|
);
|
|
15637
16166
|
await Promise.race([attempt, timeout]);
|
|
15638
16167
|
}
|
|
@@ -15810,7 +16339,7 @@ async function mcpAddCommand(options = {}) {
|
|
|
15810
16339
|
fail(`Could not run \`claude\`: ${target.refused} Run the command above yourself.`);
|
|
15811
16340
|
return 1;
|
|
15812
16341
|
}
|
|
15813
|
-
return await new Promise((
|
|
16342
|
+
return await new Promise((resolve15) => {
|
|
15814
16343
|
const child = spawn(target.command, target.args, { shell: target.shell, stdio: "inherit" });
|
|
15815
16344
|
let settled = false;
|
|
15816
16345
|
child.on("error", (err) => {
|
|
@@ -15820,16 +16349,16 @@ async function mcpAddCommand(options = {}) {
|
|
|
15820
16349
|
fail(
|
|
15821
16350
|
`Claude Code is not on your PATH, so nothing was registered. Run the command above once ${code("claude")} is installed.`
|
|
15822
16351
|
);
|
|
15823
|
-
|
|
16352
|
+
resolve15(1);
|
|
15824
16353
|
return;
|
|
15825
16354
|
}
|
|
15826
16355
|
fail(`Could not run \`claude\`: ${err.message}`);
|
|
15827
|
-
|
|
16356
|
+
resolve15(1);
|
|
15828
16357
|
});
|
|
15829
16358
|
child.on("close", (codeOut) => {
|
|
15830
16359
|
if (settled) return;
|
|
15831
16360
|
settled = true;
|
|
15832
|
-
|
|
16361
|
+
resolve15(codeOut ?? 1);
|
|
15833
16362
|
});
|
|
15834
16363
|
});
|
|
15835
16364
|
}
|
|
@@ -15948,10 +16477,10 @@ function upgradeCommand(plugin, marketplace) {
|
|
|
15948
16477
|
var PLUGIN_NAME = "dashwise";
|
|
15949
16478
|
var MARKETPLACE_NAME = "dashwise";
|
|
15950
16479
|
var DEFAULT_MARKETPLACE_SOURCE = "Intuize/dashwise-cli";
|
|
15951
|
-
var defaultRunner = (cmd, args, opts) => new Promise((
|
|
16480
|
+
var defaultRunner = (cmd, args, opts) => new Promise((resolve15) => {
|
|
15952
16481
|
const target = spawnTarget(cmd, args);
|
|
15953
16482
|
if ("refused" in target) {
|
|
15954
|
-
|
|
16483
|
+
resolve15({ code: null, stdout: "", stderr: target.refused, missing: false });
|
|
15955
16484
|
return;
|
|
15956
16485
|
}
|
|
15957
16486
|
const child = spawn(target.command, target.args, {
|
|
@@ -15966,7 +16495,7 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolve14) => {
|
|
|
15966
16495
|
child.on("error", (err) => {
|
|
15967
16496
|
if (settled) return;
|
|
15968
16497
|
settled = true;
|
|
15969
|
-
|
|
16498
|
+
resolve15({
|
|
15970
16499
|
code: null,
|
|
15971
16500
|
stdout,
|
|
15972
16501
|
stderr: stderr || err.message,
|
|
@@ -15976,7 +16505,7 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolve14) => {
|
|
|
15976
16505
|
child.on("close", (exitCode) => {
|
|
15977
16506
|
if (settled) return;
|
|
15978
16507
|
settled = true;
|
|
15979
|
-
|
|
16508
|
+
resolve15({ code: exitCode, stdout, stderr, missing: false });
|
|
15980
16509
|
});
|
|
15981
16510
|
});
|
|
15982
16511
|
function readSkillsOnDisk() {
|
|
@@ -17670,7 +18199,14 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
17670
18199
|
try {
|
|
17671
18200
|
const installs = await listWorkspaceInstallations(workspace2.id, auth);
|
|
17672
18201
|
const byKind = selectInstallsByKind(installs, mod.id);
|
|
17673
|
-
|
|
18202
|
+
const production = byKind.all.filter((row2) => !isDevInstallKind(row2.kind));
|
|
18203
|
+
if (production.length > 1) {
|
|
18204
|
+
fail(
|
|
18205
|
+
"Several production installations exist for this module in this company. Select the installation to update in the app. Nothing was changed."
|
|
18206
|
+
);
|
|
18207
|
+
return 1;
|
|
18208
|
+
}
|
|
18209
|
+
existing = production[0];
|
|
17674
18210
|
devInstall = byKind.dev;
|
|
17675
18211
|
} catch (err) {
|
|
17676
18212
|
fail(
|
|
@@ -17753,7 +18289,8 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
17753
18289
|
mod.id,
|
|
17754
18290
|
{
|
|
17755
18291
|
workspaceId: workspace2.id,
|
|
17756
|
-
|
|
18292
|
+
version: targetVersion,
|
|
18293
|
+
...existing ? { installation_id: existing.id } : {},
|
|
17757
18294
|
...options.force !== void 0 ? { force: options.force } : {}
|
|
17758
18295
|
},
|
|
17759
18296
|
auth
|
|
@@ -17761,6 +18298,12 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
17761
18298
|
} catch (err) {
|
|
17762
18299
|
return surfaceInstallError(err, mod.slug, workspace2.slug, devInstallReplaced);
|
|
17763
18300
|
}
|
|
18301
|
+
if (existing && (isInstallGated(result) || result.installation.id !== existing.id)) {
|
|
18302
|
+
fail(
|
|
18303
|
+
`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.`
|
|
18304
|
+
);
|
|
18305
|
+
return 1;
|
|
18306
|
+
}
|
|
17764
18307
|
if (isInstallGated(result)) {
|
|
17765
18308
|
if (options.json) {
|
|
17766
18309
|
out(JSON.stringify(result));
|
|
@@ -17780,7 +18323,9 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
17780
18323
|
let verified = false;
|
|
17781
18324
|
try {
|
|
17782
18325
|
const after = await listWorkspaceInstallations(workspace2.id, auth);
|
|
17783
|
-
row =
|
|
18326
|
+
row = after.find(
|
|
18327
|
+
(candidate) => candidate.id === result.installation.id && candidate.module_id === mod.id && !isDevInstallKind(candidate.kind)
|
|
18328
|
+
);
|
|
17784
18329
|
verified = row !== void 0;
|
|
17785
18330
|
} catch {
|
|
17786
18331
|
}
|
|
@@ -18390,6 +18935,16 @@ function packedFileLines(files, cap = PACKED_FILE_LIST_CAP) {
|
|
|
18390
18935
|
if (files.length <= cap) return [...files];
|
|
18391
18936
|
return [...files.slice(0, cap), `\u2026 and ${files.length - cap} more`];
|
|
18392
18937
|
}
|
|
18938
|
+
function repositoryLinkedDecision(input) {
|
|
18939
|
+
if (!input.repositoryUrl || input.fromLocal || input.dryRun) return "proceed";
|
|
18940
|
+
return input.interactive ? "confirm" : "refuse";
|
|
18941
|
+
}
|
|
18942
|
+
function canPrompt(stdin, stdout) {
|
|
18943
|
+
return stdin.isTTY === true && stdout.isTTY === true;
|
|
18944
|
+
}
|
|
18945
|
+
function repositoryLinkedWarning(repositoryUrl) {
|
|
18946
|
+
return `These files are not in ${repositoryUrl}; the next repository build replaces them.`;
|
|
18947
|
+
}
|
|
18393
18948
|
async function modulePublishCommand(options) {
|
|
18394
18949
|
const projectRoot = resolve(options.dir ?? process.cwd());
|
|
18395
18950
|
const manifestPath2 = join(projectRoot, "module.json");
|
|
@@ -18486,6 +19041,35 @@ async function modulePublishCommand(options) {
|
|
|
18486
19041
|
);
|
|
18487
19042
|
}
|
|
18488
19043
|
for (const w of tarball.warnings) warn(w);
|
|
19044
|
+
if (mod.repository_url) {
|
|
19045
|
+
const decision = repositoryLinkedDecision({
|
|
19046
|
+
repositoryUrl: mod.repository_url,
|
|
19047
|
+
fromLocal: options.fromLocal === true,
|
|
19048
|
+
dryRun: options.dryRun === true,
|
|
19049
|
+
interactive: canPrompt(process.stdin, process.stdout)
|
|
19050
|
+
});
|
|
19051
|
+
warn(repositoryLinkedWarning(mod.repository_url));
|
|
19052
|
+
if (decision === "refuse") {
|
|
19053
|
+
fail(
|
|
19054
|
+
`${code(slug)} is linked to a repository. Re-run with ${code("--from-local")} to publish these local files anyway.`
|
|
19055
|
+
);
|
|
19056
|
+
return 1;
|
|
19057
|
+
}
|
|
19058
|
+
if (decision === "confirm") {
|
|
19059
|
+
const answer = await select({
|
|
19060
|
+
message: "Publish these local files anyway?",
|
|
19061
|
+
options: [
|
|
19062
|
+
{ value: "go", label: "Yes, publish from local files" },
|
|
19063
|
+
{ value: "cancel", label: "No, abort" }
|
|
19064
|
+
],
|
|
19065
|
+
initialValue: "cancel"
|
|
19066
|
+
});
|
|
19067
|
+
if (isCancel(answer) || answer === "cancel") {
|
|
19068
|
+
fail("Aborted.");
|
|
19069
|
+
return 130;
|
|
19070
|
+
}
|
|
19071
|
+
}
|
|
19072
|
+
}
|
|
18489
19073
|
if (!options.yes && process.stdout.isTTY && !options.dryRun) {
|
|
18490
19074
|
const answer = await select({
|
|
18491
19075
|
message: `Upload + publish ${code(`bump:${bump}`)}?`,
|
|
@@ -19022,7 +19606,7 @@ async function moduleReleaseCommand(slugArg, options) {
|
|
|
19022
19606
|
}
|
|
19023
19607
|
async function wait() {
|
|
19024
19608
|
const remaining = Math.max(0, deadline - Date.now());
|
|
19025
|
-
await new Promise((
|
|
19609
|
+
await new Promise((resolve15) => setTimeout(resolve15, Math.min(options.pollIntervalMs ?? 4e3, remaining)));
|
|
19026
19610
|
}
|
|
19027
19611
|
}
|
|
19028
19612
|
|
|
@@ -22430,7 +23014,7 @@ function formatTimestamp(iso) {
|
|
|
22430
23014
|
return iso.slice(0, 19).replace("T", " ");
|
|
22431
23015
|
}
|
|
22432
23016
|
function sleep2(ms) {
|
|
22433
|
-
return new Promise((
|
|
23017
|
+
return new Promise((resolve15) => setTimeout(resolve15, ms));
|
|
22434
23018
|
}
|
|
22435
23019
|
|
|
22436
23020
|
// src/commands/module/action-log.ts
|
|
@@ -22532,7 +23116,7 @@ async function runTail2(installationId, options) {
|
|
|
22532
23116
|
}
|
|
22533
23117
|
dim(`(tail: transient error \u2014 ${err.message})`);
|
|
22534
23118
|
}
|
|
22535
|
-
await new Promise((
|
|
23119
|
+
await new Promise((resolve15) => setTimeout(resolve15, POLL_INTERVAL_MS2));
|
|
22536
23120
|
}
|
|
22537
23121
|
}
|
|
22538
23122
|
function printTable(rows) {
|
|
@@ -22917,7 +23501,7 @@ async function sleep3(ms, aborted) {
|
|
|
22917
23501
|
const slice = 100;
|
|
22918
23502
|
let waited = 0;
|
|
22919
23503
|
while (waited < ms && !aborted()) {
|
|
22920
|
-
await new Promise((
|
|
23504
|
+
await new Promise((resolve15) => setTimeout(resolve15, Math.min(slice, ms - waited)));
|
|
22921
23505
|
waited += slice;
|
|
22922
23506
|
}
|
|
22923
23507
|
}
|
|
@@ -23467,6 +24051,17 @@ program.command("login").description("Sign in via the device-code flow.").option
|
|
|
23467
24051
|
});
|
|
23468
24052
|
process.exitCode = exit;
|
|
23469
24053
|
});
|
|
24054
|
+
program.command("doctor").description(
|
|
24055
|
+
"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."
|
|
24056
|
+
).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) => {
|
|
24057
|
+
process.exitCode = await doctorCommand(
|
|
24058
|
+
{
|
|
24059
|
+
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
24060
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
24061
|
+
},
|
|
24062
|
+
defaultDoctorDeps(getVersion())
|
|
24063
|
+
);
|
|
24064
|
+
});
|
|
23470
24065
|
program.command("logout").description("Clear the local credentials and revoke the server-side token.").action(async () => {
|
|
23471
24066
|
process.exitCode = await logoutCommand();
|
|
23472
24067
|
});
|
|
@@ -24057,6 +24652,9 @@ moduleCmd.command("release [slug]").description("Release an exact pushed reposit
|
|
|
24057
24652
|
});
|
|
24058
24653
|
});
|
|
24059
24654
|
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(
|
|
24655
|
+
"--from-local",
|
|
24656
|
+
"Publish local files to a module that has a linked repository (the next repository build replaces them)."
|
|
24657
|
+
).option(
|
|
24060
24658
|
"--exclude <pattern...>",
|
|
24061
24659
|
"Extra exclude patterns (segment-matched) appended to the defaults."
|
|
24062
24660
|
).option(
|
|
@@ -24076,6 +24674,7 @@ moduleCmd.command("publish").description("Pack the current directory + upload it
|
|
|
24076
24674
|
...cmdOpts.bump !== void 0 ? { bump: cmdOpts.bump } : {},
|
|
24077
24675
|
...cmdOpts.dryRun !== void 0 ? { dryRun: cmdOpts.dryRun } : {},
|
|
24078
24676
|
...cmdOpts.yes !== void 0 ? { yes: cmdOpts.yes } : {},
|
|
24677
|
+
...cmdOpts.fromLocal !== void 0 ? { fromLocal: cmdOpts.fromLocal } : {},
|
|
24079
24678
|
...cmdOpts.exclude !== void 0 ? { exclude: cmdOpts.exclude } : {},
|
|
24080
24679
|
// commander maps `--no-respect-gitignore` to
|
|
24081
24680
|
// `respectGitignore === false`.
|
|
@@ -24332,12 +24931,12 @@ tableCmd.command("create <slug>").description(
|
|
|
24332
24931
|
"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
24932
|
).option("--dir <path>", "Project root (default: current directory).").option(
|
|
24334
24933
|
"-f, --field <spec>",
|
|
24335
|
-
"Field spec
|
|
24934
|
+
"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
24935
|
(val, prev = []) => [...prev, val],
|
|
24337
24936
|
[]
|
|
24338
24937
|
).option(
|
|
24339
24938
|
"--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.",
|
|
24939
|
+
"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
24940
|
(val, prev = []) => [...prev, val],
|
|
24342
24941
|
[]
|
|
24343
24942
|
).action(
|
|
@@ -24401,7 +25000,7 @@ fieldCmd.command("set-type <table-slug> <field-slug> <new-type>").description(
|
|
|
24401
25000
|
}
|
|
24402
25001
|
);
|
|
24403
25002
|
function getVersion() {
|
|
24404
|
-
return "0.
|
|
25003
|
+
return "0.24.0";
|
|
24405
25004
|
}
|
|
24406
25005
|
function parseIntOption(value) {
|
|
24407
25006
|
const n = Number.parseInt(value, 10);
|