@dashai/cli 0.19.0 → 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +1148 -121
- package/dist/bin.js.map +1 -1
- package/package.json +2 -2
package/dist/bin.js
CHANGED
|
@@ -110,7 +110,7 @@ function resolveApiUrl() {
|
|
|
110
110
|
if (fromEnv) return fromEnv.replace(/\/+$/, "");
|
|
111
111
|
const cfg = safeReadConfig();
|
|
112
112
|
if (cfg?.apiUrl) return cfg.apiUrl.replace(/\/+$/, "");
|
|
113
|
-
return
|
|
113
|
+
return DEFAULT_API_URL;
|
|
114
114
|
}
|
|
115
115
|
function readSavedToken() {
|
|
116
116
|
const cfg = safeReadConfig();
|
|
@@ -172,11 +172,53 @@ function removeModuleEntry(slug) {
|
|
|
172
172
|
function isLoggedIn() {
|
|
173
173
|
return readSavedToken() !== null;
|
|
174
174
|
}
|
|
175
|
-
var FILE_NAME, DIR_NAME;
|
|
175
|
+
var FILE_NAME, DIR_NAME, DEFAULT_API_URL, KNOWN_API_URLS;
|
|
176
176
|
var init_config = __esm({
|
|
177
177
|
"src/lib/config.ts"() {
|
|
178
178
|
FILE_NAME = "auth.json";
|
|
179
179
|
DIR_NAME = "dashwise";
|
|
180
|
+
DEFAULT_API_URL = "https://api.dashwise.dev";
|
|
181
|
+
KNOWN_API_URLS = {
|
|
182
|
+
dev: "https://api.dashwise.dev",
|
|
183
|
+
prod: "https://api.dashwise.net"
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// src/lib/uses-types.ts
|
|
189
|
+
function abstractTypeForPhysical(physicalType) {
|
|
190
|
+
return PHYSICAL_TO_ABSTRACT[physicalType] ?? null;
|
|
191
|
+
}
|
|
192
|
+
function slugifyUsesName(name) {
|
|
193
|
+
return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
|
|
194
|
+
}
|
|
195
|
+
function isUsesSlug(slug) {
|
|
196
|
+
return slug.length >= USES_SLUG_MIN_LENGTH && slug.length <= USES_SLUG_MAX_LENGTH && USES_SLUG_REGEX.test(slug);
|
|
197
|
+
}
|
|
198
|
+
var PHYSICAL_TO_ABSTRACT, USES_OPERATIONS, USES_SLUG_REGEX, USES_SLUG_MIN_LENGTH, USES_SLUG_MAX_LENGTH, USES_SLUG_RULE;
|
|
199
|
+
var init_uses_types = __esm({
|
|
200
|
+
"src/lib/uses-types.ts"() {
|
|
201
|
+
PHYSICAL_TO_ABSTRACT = {
|
|
202
|
+
text: "text",
|
|
203
|
+
long_text: "long_text",
|
|
204
|
+
number: "number",
|
|
205
|
+
rating: "number",
|
|
206
|
+
boolean: "boolean",
|
|
207
|
+
// A date-with-time column has physical registry type `date` (with
|
|
208
|
+
// `config.includeTime`), NOT a distinct `datetime` type — the backend
|
|
209
|
+
// registry has no `datetime` key. So introspection only ever reports
|
|
210
|
+
// `date` here; there is no `datetime` physical entry to fold.
|
|
211
|
+
date: "date",
|
|
212
|
+
email: "email",
|
|
213
|
+
url: "url",
|
|
214
|
+
single_select: "single_select",
|
|
215
|
+
multi_select: "multi_select"
|
|
216
|
+
};
|
|
217
|
+
USES_OPERATIONS = ["create", "read", "update", "delete"];
|
|
218
|
+
USES_SLUG_REGEX = /^[a-z][a-z0-9_]*[a-z0-9]$/;
|
|
219
|
+
USES_SLUG_MIN_LENGTH = 2;
|
|
220
|
+
USES_SLUG_MAX_LENGTH = 63;
|
|
221
|
+
USES_SLUG_RULE = `^[a-z][a-z0-9_]*[a-z0-9]$ (snake_case, length ${USES_SLUG_MIN_LENGTH}\u2013${USES_SLUG_MAX_LENGTH})`;
|
|
180
222
|
}
|
|
181
223
|
});
|
|
182
224
|
|
|
@@ -422,8 +464,38 @@ async function declineBinding(installationId, usesSlug, options = {}) {
|
|
|
422
464
|
options
|
|
423
465
|
);
|
|
424
466
|
}
|
|
425
|
-
function
|
|
426
|
-
|
|
467
|
+
async function listWorkspaceTables(workspaceId, options = {}) {
|
|
468
|
+
const apps = await apiRequest(
|
|
469
|
+
"GET",
|
|
470
|
+
`/api/applications/workspace/${workspaceId}`,
|
|
471
|
+
void 0,
|
|
472
|
+
options
|
|
473
|
+
);
|
|
474
|
+
const perApp = await Promise.all(
|
|
475
|
+
apps.map(async (app) => {
|
|
476
|
+
let tables;
|
|
477
|
+
try {
|
|
478
|
+
tables = await apiRequest(
|
|
479
|
+
"GET",
|
|
480
|
+
`/api/tables/application/${app.id}`,
|
|
481
|
+
void 0,
|
|
482
|
+
options
|
|
483
|
+
);
|
|
484
|
+
} catch {
|
|
485
|
+
return [];
|
|
486
|
+
}
|
|
487
|
+
return tables.map((t) => ({
|
|
488
|
+
table_id: t.id,
|
|
489
|
+
name: t.name,
|
|
490
|
+
module_id: t.module_id,
|
|
491
|
+
application_id: app.id,
|
|
492
|
+
application_name: app.name
|
|
493
|
+
}));
|
|
494
|
+
})
|
|
495
|
+
);
|
|
496
|
+
return perApp.flat().sort(
|
|
497
|
+
(a, b) => a.application_name.localeCompare(b.application_name) || a.name.localeCompare(b.name)
|
|
498
|
+
);
|
|
427
499
|
}
|
|
428
500
|
async function introspectWorkspaceTable(_installationId, tableNameOrId, options = {}) {
|
|
429
501
|
const ref = String(tableNameOrId).trim();
|
|
@@ -515,10 +587,11 @@ async function getRuntimeLogs(installationId, opts = {}, options = {}) {
|
|
|
515
587
|
options
|
|
516
588
|
);
|
|
517
589
|
}
|
|
518
|
-
var ApiError, CODE_SHAPE;
|
|
590
|
+
var ApiError, CODE_SHAPE, slugifyFieldName;
|
|
519
591
|
var init_api_client = __esm({
|
|
520
592
|
"src/lib/api-client.ts"() {
|
|
521
593
|
init_config();
|
|
594
|
+
init_uses_types();
|
|
522
595
|
ApiError = class extends Error {
|
|
523
596
|
status;
|
|
524
597
|
code;
|
|
@@ -536,6 +609,7 @@ var init_api_client = __esm({
|
|
|
536
609
|
}
|
|
537
610
|
};
|
|
538
611
|
CODE_SHAPE = /^[A-Z][A-Z0-9_]*$/;
|
|
612
|
+
slugifyFieldName = slugifyUsesName;
|
|
539
613
|
}
|
|
540
614
|
});
|
|
541
615
|
|
|
@@ -605,10 +679,109 @@ async function safeGet(fn) {
|
|
|
605
679
|
return null;
|
|
606
680
|
}
|
|
607
681
|
}
|
|
682
|
+
function isRequiredSlot(b) {
|
|
683
|
+
return b.if_missing !== "optional";
|
|
684
|
+
}
|
|
685
|
+
function summarizeUsesReadiness(bindings) {
|
|
686
|
+
const slots = [...bindings];
|
|
687
|
+
const bound = slots.filter((b) => b.status === "bound");
|
|
688
|
+
return {
|
|
689
|
+
slots,
|
|
690
|
+
bound,
|
|
691
|
+
settings: {
|
|
692
|
+
settings_path: firstWireValue(slots.map((b) => b.settings_path)),
|
|
693
|
+
settings_url: firstWireValue(slots.map((b) => b.settings_url))
|
|
694
|
+
},
|
|
695
|
+
pending: slots.filter((b) => b.status === "unbound" && isRequiredSlot(b)),
|
|
696
|
+
optionalUnbound: slots.filter(
|
|
697
|
+
(b) => b.status === "unbound" && !isRequiredSlot(b)
|
|
698
|
+
),
|
|
699
|
+
declined: slots.filter((b) => b.status === "declined"),
|
|
700
|
+
incomplete: bound.filter((b) => (b.missing_fields?.length ?? 0) > 0),
|
|
701
|
+
setupPending: slots.some((b) => b.status === "unbound" && isRequiredSlot(b))
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
async function fetchUsesReadiness(installationId, requestOptions) {
|
|
705
|
+
const bindings = await safeGet(
|
|
706
|
+
() => getInstallationBindings(installationId, requestOptions)
|
|
707
|
+
);
|
|
708
|
+
if (bindings === null) return null;
|
|
709
|
+
return summarizeUsesReadiness(bindings);
|
|
710
|
+
}
|
|
711
|
+
function wireValue(v) {
|
|
712
|
+
const trimmed = typeof v === "string" ? v.trim() : "";
|
|
713
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
714
|
+
}
|
|
715
|
+
function firstWireValue(values) {
|
|
716
|
+
for (const v of values) {
|
|
717
|
+
const resolved = wireValue(v);
|
|
718
|
+
if (resolved !== null) return resolved;
|
|
719
|
+
}
|
|
720
|
+
return null;
|
|
721
|
+
}
|
|
722
|
+
function resolveSettingsPath(wire) {
|
|
723
|
+
return wireValue(wire) ?? SETUP_SETTINGS_PATH;
|
|
724
|
+
}
|
|
725
|
+
function resolveSettingsUrl(wire) {
|
|
726
|
+
const value = wireValue(wire);
|
|
727
|
+
if (value === null) return null;
|
|
728
|
+
return /^https?:\/\//i.test(value) ? value : null;
|
|
729
|
+
}
|
|
730
|
+
function renderUsesReadiness(readiness, wire) {
|
|
731
|
+
if (readiness.slots.length === 0) return;
|
|
732
|
+
const { slots } = readiness;
|
|
733
|
+
info(
|
|
734
|
+
`Data-plane readiness ${dim(
|
|
735
|
+
`(${readiness.bound.length}/${slots.length} slot${slots.length === 1 ? "" : "s"} bound)`
|
|
736
|
+
)}:`
|
|
737
|
+
);
|
|
738
|
+
for (const b of slots) renderReadinessSlot(b);
|
|
739
|
+
if (readiness.pending.length > 0) {
|
|
740
|
+
const names = readiness.pending.map((b) => b.uses_slug);
|
|
741
|
+
info(
|
|
742
|
+
`${names.length} required slot${names.length === 1 ? "" : "s"} await${names.length === 1 ? "s" : ""} setup \u2014 until then the module throws ${code("USES_UNBOUND")} on its first data call, which end users see as an error digest.`
|
|
743
|
+
);
|
|
744
|
+
for (const b of readiness.pending) {
|
|
745
|
+
out(` ${dim("-")} ${code(`dashwise bind ${b.uses_slug} --table <name-or-id>`)}`);
|
|
746
|
+
}
|
|
747
|
+
const path = resolveSettingsPath(
|
|
748
|
+
wireValue(wire?.settings_path) ?? readiness.settings.settings_path
|
|
749
|
+
);
|
|
750
|
+
const url = resolveSettingsUrl(wire?.settings_url) ?? resolveSettingsUrl(readiness.settings.settings_url);
|
|
751
|
+
info(
|
|
752
|
+
`A workspace admin can do the same in the Dashwise app, under ${code(`Settings \u2192 ${path}`)}, where each declared table slot is bound.`
|
|
753
|
+
);
|
|
754
|
+
if (url !== null) out(` ${dim("\u2192")} ${url}`);
|
|
755
|
+
}
|
|
756
|
+
for (const b of readiness.incomplete) {
|
|
757
|
+
info(
|
|
758
|
+
`${code(b.uses_slug)} is bound but ${(b.missing_fields ?? []).length} required field(s) are unmapped: ${(b.missing_fields ?? []).join(", ")}. Re-bind with ${code(`dashwise bind ${b.uses_slug} --map <field>=<physical>`)}.`
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
function renderReadinessSlot(b) {
|
|
763
|
+
const required = b.if_missing === "optional" ? dim(" (optional)") : "";
|
|
764
|
+
if (b.status === "bound") {
|
|
765
|
+
const tableLine = b.table ? ` ${dim(`\u2192 ${b.table.name} (id ${b.table.id})`)}` : "";
|
|
766
|
+
const gap = (b.missing_fields?.length ?? 0) > 0 ? ` ${pc.yellow(`(${b.missing_fields?.length} field(s) unmapped)`)}` : "";
|
|
767
|
+
out(` ${pc.green("\u25CF bound")} ${code(b.uses_slug)}${tableLine}${gap}`);
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
if (b.status === "declined") {
|
|
771
|
+
out(` ${pc.dim("\u2298 declined")} ${code(b.uses_slug)}`);
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
const chip = b.if_missing === "optional" ? pc.dim("\u25CB unbound") : pc.yellow("\u25CB unbound");
|
|
775
|
+
out(
|
|
776
|
+
` ${chip} ${code(b.uses_slug)}${required} ${dim(`reason: ${b.reason ?? "(unspecified)"}`)}`
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
var SETUP_SETTINGS_PATH;
|
|
608
780
|
var init_connect_status = __esm({
|
|
609
781
|
"src/lib/connect-status.ts"() {
|
|
610
782
|
init_api_client();
|
|
611
783
|
init_output();
|
|
784
|
+
SETUP_SETTINGS_PATH = "Modules \u2192 Installed modules \u2192 Technical \u2192 Data connections";
|
|
612
785
|
}
|
|
613
786
|
});
|
|
614
787
|
function manifestPath(dir) {
|
|
@@ -805,6 +978,33 @@ var init_manifest_poll = __esm({
|
|
|
805
978
|
init_manifest_merge();
|
|
806
979
|
}
|
|
807
980
|
});
|
|
981
|
+
|
|
982
|
+
// src/lib/redact.ts
|
|
983
|
+
function maskSecret(value) {
|
|
984
|
+
if (value.length <= 12) return "****";
|
|
985
|
+
return `${value.slice(0, 4)}\u2026${value.slice(-4)}`;
|
|
986
|
+
}
|
|
987
|
+
function redactSecrets(line) {
|
|
988
|
+
let out2 = line;
|
|
989
|
+
for (const pattern of SECRET_PATTERNS) {
|
|
990
|
+
pattern.lastIndex = 0;
|
|
991
|
+
out2 = out2.replace(pattern, (match) => maskSecret(match));
|
|
992
|
+
}
|
|
993
|
+
return out2;
|
|
994
|
+
}
|
|
995
|
+
var SECRET_PATTERNS;
|
|
996
|
+
var init_redact = __esm({
|
|
997
|
+
"src/lib/redact.ts"() {
|
|
998
|
+
SECRET_PATTERNS = [
|
|
999
|
+
// A JWT — three base64url segments. The SSO exchange code (C-10), and
|
|
1000
|
+
// any access token that finds its way into a URL or a header dump.
|
|
1001
|
+
/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{4,}/g,
|
|
1002
|
+
// The Dashwise credential family: dwk_ installation keys, dwc_ CLI
|
|
1003
|
+
// tokens, dwm_ endpoint keys, dwa_/dwr_/dwo_ OAuth tokens.
|
|
1004
|
+
/\bdw[kcmaro]_[A-Za-z0-9_-]{16,}/g
|
|
1005
|
+
];
|
|
1006
|
+
}
|
|
1007
|
+
});
|
|
808
1008
|
async function devCommand(options) {
|
|
809
1009
|
if (options.sdkOnly && options.nextOnly) {
|
|
810
1010
|
fail(
|
|
@@ -1078,22 +1278,19 @@ function pipeWithPrefix(src, dst, prefix) {
|
|
|
1078
1278
|
const lines = combined.split("\n");
|
|
1079
1279
|
leftover = lines.pop() ?? "";
|
|
1080
1280
|
for (const line of lines) {
|
|
1081
|
-
dst.write(`${prefix}${line}
|
|
1281
|
+
dst.write(`${prefix}${redactSecrets(line)}
|
|
1082
1282
|
`);
|
|
1083
1283
|
}
|
|
1084
1284
|
});
|
|
1085
1285
|
src.on("end", () => {
|
|
1086
1286
|
if (leftover.length > 0) {
|
|
1087
|
-
dst.write(`${prefix}${leftover}
|
|
1287
|
+
dst.write(`${prefix}${redactSecrets(leftover)}
|
|
1088
1288
|
`);
|
|
1089
1289
|
}
|
|
1090
1290
|
});
|
|
1091
1291
|
}
|
|
1092
1292
|
function maskedToken(token) {
|
|
1093
|
-
|
|
1094
|
-
const prefix = token.slice(0, 4);
|
|
1095
|
-
const tail = token.slice(-4);
|
|
1096
|
-
return `${prefix}\u2026${tail}`;
|
|
1293
|
+
return maskSecret(token);
|
|
1097
1294
|
}
|
|
1098
1295
|
async function resolveModuleApiKey(slug, entry, apiUrl, cliBearer) {
|
|
1099
1296
|
if (process.env.DASHWISE_API_KEY) return null;
|
|
@@ -1171,13 +1368,13 @@ async function resolveDevPort(requested) {
|
|
|
1171
1368
|
}
|
|
1172
1369
|
async function isPortFree(port) {
|
|
1173
1370
|
const { createServer } = await import('net');
|
|
1174
|
-
return new Promise((
|
|
1371
|
+
return new Promise((resolve14) => {
|
|
1175
1372
|
const server = createServer();
|
|
1176
1373
|
server.once("error", () => {
|
|
1177
|
-
|
|
1374
|
+
resolve14(false);
|
|
1178
1375
|
});
|
|
1179
1376
|
server.once("listening", () => {
|
|
1180
|
-
server.close(() =>
|
|
1377
|
+
server.close(() => resolve14(true));
|
|
1181
1378
|
});
|
|
1182
1379
|
server.listen(port, "0.0.0.0");
|
|
1183
1380
|
});
|
|
@@ -1248,6 +1445,7 @@ var init_dev = __esm({
|
|
|
1248
1445
|
init_manifest_io();
|
|
1249
1446
|
init_manifest_poll();
|
|
1250
1447
|
init_output();
|
|
1448
|
+
init_redact();
|
|
1251
1449
|
DEFAULT_NEXT_PORT = 3e3;
|
|
1252
1450
|
PREFIX_SDK = `\x1B[36m[sdk]\x1B[0m `;
|
|
1253
1451
|
PREFIX_NEXT = `\x1B[35m[next]\x1B[0m `;
|
|
@@ -1315,6 +1513,62 @@ var init_workspaces = __esm({
|
|
|
1315
1513
|
init_api_client();
|
|
1316
1514
|
}
|
|
1317
1515
|
});
|
|
1516
|
+
function resolveSdkPackageJson(projectRoot) {
|
|
1517
|
+
let dir = projectRoot;
|
|
1518
|
+
for (; ; ) {
|
|
1519
|
+
const candidate = join(dir, "node_modules", "@dashai", "sdk", "package.json");
|
|
1520
|
+
if (existsSync(candidate)) return candidate;
|
|
1521
|
+
const parent = dirname(dir);
|
|
1522
|
+
if (parent === dir) return null;
|
|
1523
|
+
dir = parent;
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
function readInstalledSdk(projectRoot) {
|
|
1527
|
+
const pkgPath = resolveSdkPackageJson(projectRoot);
|
|
1528
|
+
if (pkgPath === null) return null;
|
|
1529
|
+
try {
|
|
1530
|
+
const parsed = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
1531
|
+
if (typeof parsed.version !== "string" || parsed.version.length === 0) return null;
|
|
1532
|
+
return { version: parsed.version };
|
|
1533
|
+
} catch {
|
|
1534
|
+
return null;
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
function compareVersions(a, b) {
|
|
1538
|
+
const parse = (v) => {
|
|
1539
|
+
const [core = "", pre] = v.trim().replace(/^[^0-9]*/, "").split("-", 2);
|
|
1540
|
+
const nums = core.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
1541
|
+
while (nums.length < 3) nums.push(0);
|
|
1542
|
+
return [nums.slice(0, 3), pre !== void 0 && pre.length > 0];
|
|
1543
|
+
};
|
|
1544
|
+
const [an, apre] = parse(a);
|
|
1545
|
+
const [bn, bpre] = parse(b);
|
|
1546
|
+
for (let i = 0; i < 3; i++) {
|
|
1547
|
+
const d = (an[i] ?? 0) - (bn[i] ?? 0);
|
|
1548
|
+
if (d !== 0) return d;
|
|
1549
|
+
}
|
|
1550
|
+
if (apre === bpre) return 0;
|
|
1551
|
+
return apre ? -1 : 1;
|
|
1552
|
+
}
|
|
1553
|
+
function satisfiesFloor(version, floor) {
|
|
1554
|
+
return compareVersions(version, floor) >= 0;
|
|
1555
|
+
}
|
|
1556
|
+
function checkSdkFloor(projectRoot, floor) {
|
|
1557
|
+
const installed = readInstalledSdk(projectRoot);
|
|
1558
|
+
if (installed === null) return { ok: true, installed: null, floor };
|
|
1559
|
+
return {
|
|
1560
|
+
ok: satisfiesFloor(installed.version, floor),
|
|
1561
|
+
installed: installed.version,
|
|
1562
|
+
floor
|
|
1563
|
+
};
|
|
1564
|
+
}
|
|
1565
|
+
var USES_SDK_FLOOR, SCAFFOLD_SDK_RANGE;
|
|
1566
|
+
var init_sdk_version = __esm({
|
|
1567
|
+
"src/lib/sdk-version.ts"() {
|
|
1568
|
+
USES_SDK_FLOOR = "2.5.0";
|
|
1569
|
+
SCAFFOLD_SDK_RANGE = "^2.5.0";
|
|
1570
|
+
}
|
|
1571
|
+
});
|
|
1318
1572
|
|
|
1319
1573
|
// src/lib/scaffold.ts
|
|
1320
1574
|
function isDeployTarget(value) {
|
|
@@ -1395,7 +1649,12 @@ function packageJson(opts) {
|
|
|
1395
1649
|
start: "next start"
|
|
1396
1650
|
},
|
|
1397
1651
|
dependencies: {
|
|
1398
|
-
|
|
1652
|
+
// The floor every scaffold feature can actually run on
|
|
1653
|
+
// (S-02): `uses` needs >= 2.5.0, and a project resolved below
|
|
1654
|
+
// it fails with "every import from @dashai/generated throws"
|
|
1655
|
+
// — a symptom that names nothing. SCAFFOLD_SDK_RANGE is the
|
|
1656
|
+
// single place this is stated.
|
|
1657
|
+
"@dashai/sdk": SCAFFOLD_SDK_RANGE,
|
|
1399
1658
|
next: "^15.0.0",
|
|
1400
1659
|
react: "^19.0.0",
|
|
1401
1660
|
"react-dom": "^19.0.0"
|
|
@@ -2421,10 +2680,11 @@ function packageJsonCustom(opts) {
|
|
|
2421
2680
|
start: "next start"
|
|
2422
2681
|
},
|
|
2423
2682
|
dependencies: {
|
|
2424
|
-
// >=2.4.0 for `@dashai/sdk/platform`, which app/layout.tsx
|
|
2425
|
-
//
|
|
2426
|
-
//
|
|
2427
|
-
"
|
|
2683
|
+
// >=2.4.0 for `@dashai/sdk/platform`, which app/layout.tsx
|
|
2684
|
+
// imports, and >=2.5.0 for `uses` (S-02) — a project resolved
|
|
2685
|
+
// below that fails with "every import from @dashai/generated
|
|
2686
|
+
// throws", which points nowhere near the cause.
|
|
2687
|
+
"@dashai/sdk": SCAFFOLD_SDK_RANGE,
|
|
2428
2688
|
next: "^15.0.0",
|
|
2429
2689
|
react: "^19.0.0",
|
|
2430
2690
|
"react-dom": "^19.0.0",
|
|
@@ -3809,11 +4069,63 @@ primary_region = "iad"
|
|
|
3809
4069
|
var DEPLOY_TARGETS, SCAFFOLD_DEFAULT_API_URL, MODULE_SLUG_REGEX;
|
|
3810
4070
|
var init_scaffold = __esm({
|
|
3811
4071
|
"src/lib/scaffold.ts"() {
|
|
4072
|
+
init_sdk_version();
|
|
3812
4073
|
DEPLOY_TARGETS = ["railway", "vercel", "fly"];
|
|
3813
4074
|
SCAFFOLD_DEFAULT_API_URL = "http://localhost:3000";
|
|
3814
4075
|
MODULE_SLUG_REGEX = /^[a-z]([a-z0-9-]*[a-z0-9])?$/;
|
|
3815
4076
|
}
|
|
3816
4077
|
});
|
|
4078
|
+
function resolveTargetDir(slug, dirFlag) {
|
|
4079
|
+
if (dirFlag !== void 0) return { dir: resolve(dirFlag) };
|
|
4080
|
+
const cwd = process.cwd();
|
|
4081
|
+
const nested = resolve(slug);
|
|
4082
|
+
if (basename(cwd) !== slug) return { dir: nested };
|
|
4083
|
+
const manifestPath2 = join(cwd, "module.json");
|
|
4084
|
+
if (existsSync(manifestPath2)) {
|
|
4085
|
+
const existingSlug = readManifestSlug(manifestPath2);
|
|
4086
|
+
if (existingSlug === slug) {
|
|
4087
|
+
return {
|
|
4088
|
+
dir: cwd,
|
|
4089
|
+
notice: `Re-provisioning ${code(slug)} in place \u2014 ${code(cwd)} already holds its ${code("module.json")}.`
|
|
4090
|
+
};
|
|
4091
|
+
}
|
|
4092
|
+
if (existingSlug !== null) {
|
|
4093
|
+
return {
|
|
4094
|
+
dir: cwd,
|
|
4095
|
+
refuse: `${code(cwd)} is the project directory for ${code(existingSlug)}, not ${code(slug)}. Run this from the parent directory, or pass ${code(`--dir <path>`)} to say where ${code(slug)} should go.`
|
|
4096
|
+
};
|
|
4097
|
+
}
|
|
4098
|
+
return {
|
|
4099
|
+
dir: cwd,
|
|
4100
|
+
notice: `Scaffolding into ${code(cwd)} \u2014 it already holds a ${code("module.json")}.`
|
|
4101
|
+
};
|
|
4102
|
+
}
|
|
4103
|
+
let entries;
|
|
4104
|
+
try {
|
|
4105
|
+
entries = readdirSync(cwd);
|
|
4106
|
+
} catch {
|
|
4107
|
+
return { dir: nested };
|
|
4108
|
+
}
|
|
4109
|
+
if (entries.length === 0) {
|
|
4110
|
+
return {
|
|
4111
|
+
dir: cwd,
|
|
4112
|
+
notice: `Scaffolding into ${code(cwd)} \u2014 an empty directory already named ${code(slug)}.`
|
|
4113
|
+
};
|
|
4114
|
+
}
|
|
4115
|
+
return {
|
|
4116
|
+
dir: nested,
|
|
4117
|
+
nestWarning: `You are inside a directory called ${code(slug)}, so this creates ${code(nested)}. Pass ${code("--dir .")} to scaffold here instead.`
|
|
4118
|
+
};
|
|
4119
|
+
}
|
|
4120
|
+
function readManifestSlug(manifestPath2) {
|
|
4121
|
+
try {
|
|
4122
|
+
const parsed = JSON.parse(readFileSync(manifestPath2, "utf-8"));
|
|
4123
|
+
const value = parsed.module?.slug;
|
|
4124
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
4125
|
+
} catch {
|
|
4126
|
+
return null;
|
|
4127
|
+
}
|
|
4128
|
+
}
|
|
3817
4129
|
async function moduleInitCommand(slugArg, options) {
|
|
3818
4130
|
intro("Scaffolding a new Dashwise module");
|
|
3819
4131
|
let slug = slugArg;
|
|
@@ -3838,7 +4150,14 @@ async function moduleInitCommand(slugArg, options) {
|
|
|
3838
4150
|
return 1;
|
|
3839
4151
|
}
|
|
3840
4152
|
const name = options.name ?? humanizeSlug(slug);
|
|
3841
|
-
const
|
|
4153
|
+
const resolved = resolveTargetDir(slug, options.dir);
|
|
4154
|
+
if (resolved.refuse) {
|
|
4155
|
+
fail(resolved.refuse);
|
|
4156
|
+
return 1;
|
|
4157
|
+
}
|
|
4158
|
+
const dir = resolved.dir;
|
|
4159
|
+
if (resolved.notice) info(resolved.notice);
|
|
4160
|
+
if (resolved.nestWarning) warn(resolved.nestWarning);
|
|
3842
4161
|
const manifestExists = existsSync(join(dir, "module.json"));
|
|
3843
4162
|
if (options.provisionOnly && !manifestExists) {
|
|
3844
4163
|
fail(
|
|
@@ -5357,6 +5676,7 @@ function installExcessArgumentsGuard(program2) {
|
|
|
5357
5676
|
init_output();
|
|
5358
5677
|
|
|
5359
5678
|
// src/program.ts
|
|
5679
|
+
init_config();
|
|
5360
5680
|
init_dev();
|
|
5361
5681
|
|
|
5362
5682
|
// src/commands/login.ts
|
|
@@ -5807,8 +6127,8 @@ function startMcpProxy(opts) {
|
|
|
5807
6127
|
let streamUnavailable = false;
|
|
5808
6128
|
let streamStarted = false;
|
|
5809
6129
|
let resolveDone;
|
|
5810
|
-
const done = new Promise((
|
|
5811
|
-
resolveDone =
|
|
6130
|
+
const done = new Promise((resolve14) => {
|
|
6131
|
+
resolveDone = resolve14;
|
|
5812
6132
|
});
|
|
5813
6133
|
const trace = (line) => {
|
|
5814
6134
|
if (verbose) log2(line);
|
|
@@ -6108,13 +6428,13 @@ function startMcpProxy(opts) {
|
|
|
6108
6428
|
const { done: finished, value } = await reader.read();
|
|
6109
6429
|
if (finished) break;
|
|
6110
6430
|
buffer2 += decoder.decode(value, { stream: true });
|
|
6111
|
-
let
|
|
6112
|
-
while (
|
|
6113
|
-
const event = buffer2.slice(0,
|
|
6114
|
-
buffer2 = buffer2.slice(
|
|
6431
|
+
let sep3 = buffer2.indexOf("\n\n");
|
|
6432
|
+
while (sep3 !== -1) {
|
|
6433
|
+
const event = buffer2.slice(0, sep3);
|
|
6434
|
+
buffer2 = buffer2.slice(sep3 + 2);
|
|
6115
6435
|
const payload = eventData(event);
|
|
6116
6436
|
if (payload !== null) emitFrame(payload, "server stream");
|
|
6117
|
-
|
|
6437
|
+
sep3 = buffer2.indexOf("\n\n");
|
|
6118
6438
|
}
|
|
6119
6439
|
}
|
|
6120
6440
|
} catch (err) {
|
|
@@ -6294,7 +6614,7 @@ async function revokeSession(endpointId) {
|
|
|
6294
6614
|
() => false
|
|
6295
6615
|
);
|
|
6296
6616
|
const timeout = new Promise(
|
|
6297
|
-
(
|
|
6617
|
+
(resolve14) => setTimeout(() => resolve14(false), REVOKE_TIMEOUT_MS).unref()
|
|
6298
6618
|
);
|
|
6299
6619
|
await Promise.race([attempt, timeout]);
|
|
6300
6620
|
}
|
|
@@ -6472,7 +6792,7 @@ async function mcpAddCommand(options = {}) {
|
|
|
6472
6792
|
fail(`Could not run \`claude\`: ${target.refused} Run the command above yourself.`);
|
|
6473
6793
|
return 1;
|
|
6474
6794
|
}
|
|
6475
|
-
return await new Promise((
|
|
6795
|
+
return await new Promise((resolve14) => {
|
|
6476
6796
|
const child = spawn(target.command, target.args, { shell: target.shell, stdio: "inherit" });
|
|
6477
6797
|
let settled = false;
|
|
6478
6798
|
child.on("error", (err) => {
|
|
@@ -6482,16 +6802,16 @@ async function mcpAddCommand(options = {}) {
|
|
|
6482
6802
|
fail(
|
|
6483
6803
|
`Claude Code is not on your PATH, so nothing was registered. Run the command above once ${code("claude")} is installed.`
|
|
6484
6804
|
);
|
|
6485
|
-
|
|
6805
|
+
resolve14(1);
|
|
6486
6806
|
return;
|
|
6487
6807
|
}
|
|
6488
6808
|
fail(`Could not run \`claude\`: ${err.message}`);
|
|
6489
|
-
|
|
6809
|
+
resolve14(1);
|
|
6490
6810
|
});
|
|
6491
6811
|
child.on("close", (codeOut) => {
|
|
6492
6812
|
if (settled) return;
|
|
6493
6813
|
settled = true;
|
|
6494
|
-
|
|
6814
|
+
resolve14(codeOut ?? 1);
|
|
6495
6815
|
});
|
|
6496
6816
|
});
|
|
6497
6817
|
}
|
|
@@ -6499,13 +6819,121 @@ async function mcpAddCommand(options = {}) {
|
|
|
6499
6819
|
// src/commands/agent.ts
|
|
6500
6820
|
init_config();
|
|
6501
6821
|
init_output();
|
|
6822
|
+
function claudeConfigDir() {
|
|
6823
|
+
const override = process.env.CLAUDE_CONFIG_DIR;
|
|
6824
|
+
if (override !== void 0 && override.length > 0) return override;
|
|
6825
|
+
return join(homedir(), ".claude");
|
|
6826
|
+
}
|
|
6827
|
+
function pluginCacheRoot(marketplace, plugin) {
|
|
6828
|
+
return join(claudeConfigDir(), "plugins", "cache", marketplace, plugin);
|
|
6829
|
+
}
|
|
6830
|
+
function marketplacePluginRoot(marketplace, plugin) {
|
|
6831
|
+
return join(
|
|
6832
|
+
claudeConfigDir(),
|
|
6833
|
+
"plugins",
|
|
6834
|
+
"marketplaces",
|
|
6835
|
+
marketplace,
|
|
6836
|
+
"plugins",
|
|
6837
|
+
plugin
|
|
6838
|
+
);
|
|
6839
|
+
}
|
|
6840
|
+
function readSkillVersion(skillMdPath) {
|
|
6841
|
+
let text4;
|
|
6842
|
+
try {
|
|
6843
|
+
text4 = readFileSync(skillMdPath, "utf-8");
|
|
6844
|
+
} catch {
|
|
6845
|
+
return null;
|
|
6846
|
+
}
|
|
6847
|
+
const lines = text4.split(/\r?\n/);
|
|
6848
|
+
if (lines[0]?.trim() !== "---") return null;
|
|
6849
|
+
for (let i = 1; i < lines.length; i++) {
|
|
6850
|
+
const line = lines[i];
|
|
6851
|
+
if (line.trim() === "---") break;
|
|
6852
|
+
const match = /^version:\s*(.+?)\s*$/.exec(line);
|
|
6853
|
+
if (match) return match[1].replace(/^['"]|['"]$/g, "");
|
|
6854
|
+
}
|
|
6855
|
+
return null;
|
|
6856
|
+
}
|
|
6857
|
+
function readSkills(skillsDir) {
|
|
6858
|
+
if (!existsSync(skillsDir)) return [];
|
|
6859
|
+
let entries;
|
|
6860
|
+
try {
|
|
6861
|
+
entries = readdirSync(skillsDir, { withFileTypes: true });
|
|
6862
|
+
} catch {
|
|
6863
|
+
return [];
|
|
6864
|
+
}
|
|
6865
|
+
return entries.filter((e) => e.isDirectory()).map((e) => {
|
|
6866
|
+
const dir = join(skillsDir, e.name);
|
|
6867
|
+
return {
|
|
6868
|
+
name: e.name,
|
|
6869
|
+
version: readSkillVersion(join(dir, "SKILL.md")),
|
|
6870
|
+
path: dir
|
|
6871
|
+
};
|
|
6872
|
+
}).sort((a, b) => a.name.localeCompare(b.name));
|
|
6873
|
+
}
|
|
6874
|
+
function compareDotted(a, b) {
|
|
6875
|
+
const pa = a.split(".");
|
|
6876
|
+
const pb = b.split(".");
|
|
6877
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
6878
|
+
const na = Number.parseInt(pa[i] ?? "0", 10);
|
|
6879
|
+
const nb = Number.parseInt(pb[i] ?? "0", 10);
|
|
6880
|
+
if (Number.isNaN(na) || Number.isNaN(nb)) return a.localeCompare(b);
|
|
6881
|
+
if (na !== nb) return na - nb;
|
|
6882
|
+
}
|
|
6883
|
+
return 0;
|
|
6884
|
+
}
|
|
6885
|
+
function readInstalledPlugin(marketplace, plugin) {
|
|
6886
|
+
const root = pluginCacheRoot(marketplace, plugin);
|
|
6887
|
+
if (!existsSync(root)) return null;
|
|
6888
|
+
let versions;
|
|
6889
|
+
try {
|
|
6890
|
+
versions = readdirSync(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
6891
|
+
} catch {
|
|
6892
|
+
return null;
|
|
6893
|
+
}
|
|
6894
|
+
if (versions.length === 0) return null;
|
|
6895
|
+
const version = versions.sort(compareDotted)[versions.length - 1];
|
|
6896
|
+
const path = join(root, version);
|
|
6897
|
+
const skillsPath = join(path, "skills");
|
|
6898
|
+
return {
|
|
6899
|
+
version,
|
|
6900
|
+
path,
|
|
6901
|
+
skillsPath: existsSync(skillsPath) ? skillsPath : null,
|
|
6902
|
+
skills: readSkills(skillsPath)
|
|
6903
|
+
};
|
|
6904
|
+
}
|
|
6905
|
+
function readMarketplacePlugin(marketplace, plugin) {
|
|
6906
|
+
const root = marketplacePluginRoot(marketplace, plugin);
|
|
6907
|
+
const manifest = join(root, ".claude-plugin", "plugin.json");
|
|
6908
|
+
if (!existsSync(manifest)) return null;
|
|
6909
|
+
let version;
|
|
6910
|
+
try {
|
|
6911
|
+
const parsed = JSON.parse(readFileSync(manifest, "utf-8"));
|
|
6912
|
+
if (typeof parsed.version !== "string" || parsed.version.length === 0) return null;
|
|
6913
|
+
version = parsed.version;
|
|
6914
|
+
} catch {
|
|
6915
|
+
return null;
|
|
6916
|
+
}
|
|
6917
|
+
const skillsPath = join(root, "skills");
|
|
6918
|
+
return {
|
|
6919
|
+
version,
|
|
6920
|
+
path: root,
|
|
6921
|
+
skillsPath: existsSync(skillsPath) ? skillsPath : null,
|
|
6922
|
+
skills: readSkills(skillsPath)
|
|
6923
|
+
};
|
|
6924
|
+
}
|
|
6925
|
+
function upgradeCommand(plugin, marketplace) {
|
|
6926
|
+
return `claude plugin update ${plugin}@${marketplace}`;
|
|
6927
|
+
}
|
|
6928
|
+
|
|
6929
|
+
// src/commands/agent.ts
|
|
6502
6930
|
var PLUGIN_NAME = "dashwise";
|
|
6503
6931
|
var MARKETPLACE_NAME = "dashwise";
|
|
6504
6932
|
var DEFAULT_MARKETPLACE_SOURCE = "Intuize/dashwise-cli";
|
|
6505
|
-
var defaultRunner = (cmd, args, opts) => new Promise((
|
|
6933
|
+
var defaultRunner = (cmd, args, opts) => new Promise((resolve14) => {
|
|
6506
6934
|
const target = spawnTarget(cmd, args);
|
|
6507
6935
|
if ("refused" in target) {
|
|
6508
|
-
|
|
6936
|
+
resolve14({ code: null, stdout: "", stderr: target.refused, missing: false });
|
|
6509
6937
|
return;
|
|
6510
6938
|
}
|
|
6511
6939
|
const child = spawn(target.command, target.args, {
|
|
@@ -6520,7 +6948,7 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolve13) => {
|
|
|
6520
6948
|
child.on("error", (err) => {
|
|
6521
6949
|
if (settled) return;
|
|
6522
6950
|
settled = true;
|
|
6523
|
-
|
|
6951
|
+
resolve14({
|
|
6524
6952
|
code: null,
|
|
6525
6953
|
stdout,
|
|
6526
6954
|
stderr: stderr || err.message,
|
|
@@ -6530,9 +6958,35 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolve13) => {
|
|
|
6530
6958
|
child.on("close", (exitCode) => {
|
|
6531
6959
|
if (settled) return;
|
|
6532
6960
|
settled = true;
|
|
6533
|
-
|
|
6961
|
+
resolve14({ code: exitCode, stdout, stderr, missing: false });
|
|
6534
6962
|
});
|
|
6535
6963
|
});
|
|
6964
|
+
function readSkillsOnDisk() {
|
|
6965
|
+
const installed = readInstalledPlugin(MARKETPLACE_NAME, PLUGIN_NAME);
|
|
6966
|
+
const latest = readMarketplacePlugin(MARKETPLACE_NAME, PLUGIN_NAME);
|
|
6967
|
+
return {
|
|
6968
|
+
skills_path: installed?.skillsPath ?? null,
|
|
6969
|
+
installed_version: installed?.version ?? null,
|
|
6970
|
+
latest_version: latest?.version ?? null,
|
|
6971
|
+
skills: (installed?.skills ?? []).map((sk) => ({
|
|
6972
|
+
name: sk.name,
|
|
6973
|
+
version: sk.version,
|
|
6974
|
+
path: sk.path
|
|
6975
|
+
})),
|
|
6976
|
+
upgrade_command: upgradeCommand(PLUGIN_NAME, MARKETPLACE_NAME)
|
|
6977
|
+
};
|
|
6978
|
+
}
|
|
6979
|
+
function printSkillsOnDisk(disk) {
|
|
6980
|
+
if (disk.skills_path === null) return;
|
|
6981
|
+
info(
|
|
6982
|
+
`The skills are readable on disk now, without a restart: ${code(disk.skills_path)}`
|
|
6983
|
+
);
|
|
6984
|
+
for (const sk of disk.skills) {
|
|
6985
|
+
out(
|
|
6986
|
+
` ${dim("skill:")} ${sk.name}${sk.version ? ` ${dim(`v${sk.version}`)}` : ""}`
|
|
6987
|
+
);
|
|
6988
|
+
}
|
|
6989
|
+
}
|
|
6536
6990
|
function parsePluginList(text4) {
|
|
6537
6991
|
let parsed;
|
|
6538
6992
|
try {
|
|
@@ -6692,12 +7146,23 @@ async function agentSetupCommand(options = {}, deps = {}) {
|
|
|
6692
7146
|
const installed = await step(run, ["plugin", "install", pluginId], json, report);
|
|
6693
7147
|
if (installed !== 0) return failed(report, source, json, finish, installed);
|
|
6694
7148
|
report.plugin.installed = true;
|
|
7149
|
+
const disk = readSkillsOnDisk();
|
|
7150
|
+
report.on_disk = disk;
|
|
7151
|
+
if (disk.installed_version !== null) report.plugin.version = disk.installed_version;
|
|
6695
7152
|
report.next = ["Restart Claude Code so it loads the plugin."];
|
|
7153
|
+
if (disk.skills_path !== null) {
|
|
7154
|
+
report.next.push(`Or read the skills now, without restarting: ${disk.skills_path}`);
|
|
7155
|
+
}
|
|
6696
7156
|
if (!loggedIn())
|
|
6697
7157
|
report.next.push("Run `dashwise login` once, so the plugin\u2019s MCP server can start.");
|
|
6698
7158
|
if (!json) {
|
|
6699
|
-
success(
|
|
6700
|
-
|
|
7159
|
+
success(
|
|
7160
|
+
`Installed ${pluginId}${disk.installed_version ? ` v${disk.installed_version}` : ""}.`
|
|
7161
|
+
);
|
|
7162
|
+
info("Restart Claude Code so it loads the plugin.");
|
|
7163
|
+
printSkillsOnDisk(disk);
|
|
7164
|
+
if (!loggedIn())
|
|
7165
|
+
info("Run `dashwise login` once, so the plugin\u2019s MCP server can start.");
|
|
6701
7166
|
}
|
|
6702
7167
|
return finish(0);
|
|
6703
7168
|
}
|
|
@@ -6740,10 +7205,59 @@ function printStatus(report) {
|
|
|
6740
7205
|
);
|
|
6741
7206
|
if (p.others?.length) out(`Also installed from other marketplaces: ${p.others.join(", ")}`);
|
|
6742
7207
|
}
|
|
7208
|
+
async function agentStatusCommand(options = {}) {
|
|
7209
|
+
const installed = readInstalledPlugin(MARKETPLACE_NAME, PLUGIN_NAME);
|
|
7210
|
+
const latest = readMarketplacePlugin(MARKETPLACE_NAME, PLUGIN_NAME);
|
|
7211
|
+
const upgrade = upgradeCommand(PLUGIN_NAME, MARKETPLACE_NAME);
|
|
7212
|
+
const report = {
|
|
7213
|
+
plugin: `${PLUGIN_NAME}@${MARKETPLACE_NAME}`,
|
|
7214
|
+
marketplace: MARKETPLACE_NAME,
|
|
7215
|
+
installed_version: installed?.version ?? null,
|
|
7216
|
+
latest_version: latest?.version ?? null,
|
|
7217
|
+
up_to_date: installed && latest ? installed.version === latest.version : null,
|
|
7218
|
+
skills_path: installed?.skillsPath ?? null,
|
|
7219
|
+
skills: (installed?.skills ?? []).map((sk) => ({
|
|
7220
|
+
name: sk.name,
|
|
7221
|
+
version: sk.version,
|
|
7222
|
+
path: sk.path
|
|
7223
|
+
})),
|
|
7224
|
+
latest_skills: (latest?.skills ?? []).map((sk) => ({
|
|
7225
|
+
name: sk.name,
|
|
7226
|
+
version: sk.version
|
|
7227
|
+
})),
|
|
7228
|
+
upgrade_command: upgrade
|
|
7229
|
+
};
|
|
7230
|
+
if (options.json === true) {
|
|
7231
|
+
out(JSON.stringify(report, null, 2));
|
|
7232
|
+
return 0;
|
|
7233
|
+
}
|
|
7234
|
+
if (installed === null) {
|
|
7235
|
+
out(`Plugin ${report.plugin}: not installed`);
|
|
7236
|
+
info(`Install it with ${code("dashwise agent setup")}.`);
|
|
7237
|
+
return 0;
|
|
7238
|
+
}
|
|
7239
|
+
out(`Plugin ${report.plugin}: ${installed.version} installed`);
|
|
7240
|
+
out(
|
|
7241
|
+
`Marketplace copy: ${latest === null ? "could not tell (no marketplace clone on this machine)" : `${latest.version}${report.up_to_date ? " \u2014 up to date" : ""}`}`
|
|
7242
|
+
);
|
|
7243
|
+
if (report.skills_path !== null) out(`Skills: ${report.skills_path}`);
|
|
7244
|
+
for (const sk of report.skills) {
|
|
7245
|
+
const newer = report.latest_skills.find((l) => l.name === sk.name);
|
|
7246
|
+
const drift = newer && newer.version && sk.version && newer.version !== sk.version ? ` ${dim(`\u2192 ${newer.version} available`)}` : "";
|
|
7247
|
+
out(` - ${sk.name}${sk.version ? ` v${sk.version}` : dim(" (unversioned)")}${drift}`);
|
|
7248
|
+
}
|
|
7249
|
+
if (report.up_to_date === false) {
|
|
7250
|
+
info(`A newer plugin is available. Upgrade with ${code(upgrade)}.`);
|
|
7251
|
+
} else if (report.up_to_date === null) {
|
|
7252
|
+
info(`To check for a newer version: ${code(upgrade)}.`);
|
|
7253
|
+
}
|
|
7254
|
+
return 0;
|
|
7255
|
+
}
|
|
6743
7256
|
|
|
6744
7257
|
// src/commands/workspace.ts
|
|
6745
7258
|
init_api_client();
|
|
6746
7259
|
init_config();
|
|
7260
|
+
init_workspaces();
|
|
6747
7261
|
init_output();
|
|
6748
7262
|
async function workspaceListCommand(options = {}) {
|
|
6749
7263
|
const cfg = readConfig();
|
|
@@ -6839,6 +7353,111 @@ async function workspaceUseCommand(slug) {
|
|
|
6839
7353
|
}
|
|
6840
7354
|
return 0;
|
|
6841
7355
|
}
|
|
7356
|
+
async function workspaceTablesCommand(options = {}) {
|
|
7357
|
+
const cfg = readConfig();
|
|
7358
|
+
if (!cfg) {
|
|
7359
|
+
if (options.json) {
|
|
7360
|
+
out(
|
|
7361
|
+
JSON.stringify({
|
|
7362
|
+
ok: false,
|
|
7363
|
+
error: { code: "NOT_LOGGED_IN", message: "Run `dashwise login` first." }
|
|
7364
|
+
})
|
|
7365
|
+
);
|
|
7366
|
+
} else {
|
|
7367
|
+
fail("Not logged in. Run `dashwise login` first.");
|
|
7368
|
+
}
|
|
7369
|
+
return 1;
|
|
7370
|
+
}
|
|
7371
|
+
let workspace2;
|
|
7372
|
+
try {
|
|
7373
|
+
workspace2 = await selectWorkspace({
|
|
7374
|
+
apiUrl: resolveApiUrl(),
|
|
7375
|
+
apiToken: cfg.token,
|
|
7376
|
+
...options.workspace !== void 0 ? { flag: options.workspace } : cfg.workspaceSlug !== void 0 ? { flag: cfg.workspaceSlug } : {},
|
|
7377
|
+
// A table listing is a read; prompting for a workspace mid-script
|
|
7378
|
+
// would be worse than saying which flag to pass.
|
|
7379
|
+
interactive: false
|
|
7380
|
+
});
|
|
7381
|
+
} catch (err) {
|
|
7382
|
+
if (options.json) {
|
|
7383
|
+
out(
|
|
7384
|
+
JSON.stringify({
|
|
7385
|
+
ok: false,
|
|
7386
|
+
error: { code: "WORKSPACE_UNRESOLVED", message: err.message }
|
|
7387
|
+
})
|
|
7388
|
+
);
|
|
7389
|
+
} else {
|
|
7390
|
+
fail(err.message);
|
|
7391
|
+
}
|
|
7392
|
+
return 1;
|
|
7393
|
+
}
|
|
7394
|
+
let tables;
|
|
7395
|
+
try {
|
|
7396
|
+
tables = await listWorkspaceTables(workspace2.id, {
|
|
7397
|
+
baseUrl: resolveApiUrl(),
|
|
7398
|
+
auth: { token: cfg.token }
|
|
7399
|
+
});
|
|
7400
|
+
} catch (err) {
|
|
7401
|
+
if (err instanceof ApiError && err.status === 401) {
|
|
7402
|
+
if (options.json) {
|
|
7403
|
+
out(
|
|
7404
|
+
JSON.stringify({
|
|
7405
|
+
ok: false,
|
|
7406
|
+
error: { code: "TOKEN_INVALID", message: "Saved token is no longer valid." }
|
|
7407
|
+
})
|
|
7408
|
+
);
|
|
7409
|
+
} else {
|
|
7410
|
+
fail("Saved token is no longer valid. Run `dashwise login` again.");
|
|
7411
|
+
}
|
|
7412
|
+
return 1;
|
|
7413
|
+
}
|
|
7414
|
+
if (options.json) {
|
|
7415
|
+
out(
|
|
7416
|
+
JSON.stringify({
|
|
7417
|
+
ok: false,
|
|
7418
|
+
error: { code: "FETCH_FAILED", message: err.message }
|
|
7419
|
+
})
|
|
7420
|
+
);
|
|
7421
|
+
} else {
|
|
7422
|
+
fail(`Could not list tables: ${err.message}`);
|
|
7423
|
+
}
|
|
7424
|
+
return 1;
|
|
7425
|
+
}
|
|
7426
|
+
if (options.json) {
|
|
7427
|
+
out(
|
|
7428
|
+
JSON.stringify({
|
|
7429
|
+
ok: true,
|
|
7430
|
+
workspace: { id: workspace2.id, slug: workspace2.slug, name: workspace2.name },
|
|
7431
|
+
tables
|
|
7432
|
+
})
|
|
7433
|
+
);
|
|
7434
|
+
return 0;
|
|
7435
|
+
}
|
|
7436
|
+
if (tables.length === 0) {
|
|
7437
|
+
info(`No tables visible to you in workspace ${code(workspace2.slug)}.`);
|
|
7438
|
+
return 0;
|
|
7439
|
+
}
|
|
7440
|
+
out("");
|
|
7441
|
+
info(
|
|
7442
|
+
`${tables.length} table${tables.length === 1 ? "" : "s"} in ${code(workspace2.slug)} ${dim(`(id ${workspace2.id})`)}:`
|
|
7443
|
+
);
|
|
7444
|
+
let lastApp = null;
|
|
7445
|
+
for (const t of tables) {
|
|
7446
|
+
if (t.application_name !== lastApp) {
|
|
7447
|
+
out(` ${dim(t.application_name)}`);
|
|
7448
|
+
lastApp = t.application_name;
|
|
7449
|
+
}
|
|
7450
|
+
out(` ${String(t.table_id).padEnd(8)} ${t.name}`);
|
|
7451
|
+
}
|
|
7452
|
+
out("");
|
|
7453
|
+
info(
|
|
7454
|
+
`Use an id with ${code("dashwise uses add --from-table <id>")} to declare a contract on that table.`
|
|
7455
|
+
);
|
|
7456
|
+
out(
|
|
7457
|
+
` ${dim(`Not every table can back a uses slot \u2014 a module-managed one is refused; reach those with \`dashwise deps\`.`)}`
|
|
7458
|
+
);
|
|
7459
|
+
return 0;
|
|
7460
|
+
}
|
|
6842
7461
|
|
|
6843
7462
|
// src/commands/module/clone.ts
|
|
6844
7463
|
init_api_client();
|
|
@@ -7532,6 +8151,53 @@ function extractManifestIssues(err) {
|
|
|
7532
8151
|
|
|
7533
8152
|
// src/commands/module/generate.ts
|
|
7534
8153
|
init_api_client();
|
|
8154
|
+
|
|
8155
|
+
// src/lib/generator-version.ts
|
|
8156
|
+
var SUPPORTED_GENERATOR_MAJOR = 3;
|
|
8157
|
+
var STAMP_KEY = "dashwiseGeneratedBy";
|
|
8158
|
+
var STAMP_RE = /^sdk-generator@(\d+)(?:\.|$)/;
|
|
8159
|
+
function readGeneratorStamp(files) {
|
|
8160
|
+
const pkg = files["package.json"];
|
|
8161
|
+
if (typeof pkg !== "string") return { kind: "unstamped" };
|
|
8162
|
+
let parsed;
|
|
8163
|
+
try {
|
|
8164
|
+
parsed = JSON.parse(pkg);
|
|
8165
|
+
} catch {
|
|
8166
|
+
return { kind: "unstamped" };
|
|
8167
|
+
}
|
|
8168
|
+
const stamp = parsed?.[STAMP_KEY];
|
|
8169
|
+
if (typeof stamp !== "string") return { kind: "unstamped" };
|
|
8170
|
+
const match = STAMP_RE.exec(stamp);
|
|
8171
|
+
if (!match) return { kind: "unstamped" };
|
|
8172
|
+
return { kind: "stamped", stamp, major: Number.parseInt(match[1], 10) };
|
|
8173
|
+
}
|
|
8174
|
+
var ALLOW_NEWER_GENERATOR_FLAG = "--allow-newer-generator";
|
|
8175
|
+
var SUPPORTED_MAJOR_NOTE = "Generator 3: select fields in `uses` slots are typed as the wire sends them \u2014 the option id on a read, the id or the exact label on a write.";
|
|
8176
|
+
function judgeGeneratorStamp(stamp, opts = {}) {
|
|
8177
|
+
if (stamp.kind === "unstamped") return { ok: true, note: null, warning: null };
|
|
8178
|
+
if (stamp.major > SUPPORTED_GENERATOR_MAJOR) {
|
|
8179
|
+
const skew = `This backend's type generator is ${stamp.stamp} \u2014 major ${stamp.major}, and this @dashai/cli understands major ${SUPPORTED_GENERATOR_MAJOR}. A generator major changes the shape of the generated types`;
|
|
8180
|
+
if (opts.allowNewerGenerator) {
|
|
8181
|
+
return {
|
|
8182
|
+
ok: true,
|
|
8183
|
+
note: null,
|
|
8184
|
+
warning: `${skew}, and ${ALLOW_NEWER_GENERATOR_FLAG} told this CLI to write them anyway. The files are the backend's either way \u2014 generation is server-side \u2014 but nothing here can describe what changed about them, so a compile error in your own code after this is the expected shape of the skew. Upgrade with \`npm i -g @dashai/cli@latest\` when a release exists.`
|
|
8185
|
+
};
|
|
8186
|
+
}
|
|
8187
|
+
return {
|
|
8188
|
+
ok: false,
|
|
8189
|
+
code: "GENERATOR_MAJOR_UNSUPPORTED",
|
|
8190
|
+
message: `${skew}, so nothing was written: upgrade with \`npm i -g @dashai/cli@latest\`, then re-run \`dashwise module generate\`. If no release understands it yet, re-run with ${ALLOW_NEWER_GENERATOR_FLAG} to write it anyway \u2014 the output is identical either way, and you lose only this CLI's account of what changed.`
|
|
8191
|
+
};
|
|
8192
|
+
}
|
|
8193
|
+
if (stamp.major === SUPPORTED_GENERATOR_MAJOR) {
|
|
8194
|
+
return { ok: true, note: SUPPORTED_MAJOR_NOTE, warning: null };
|
|
8195
|
+
}
|
|
8196
|
+
return { ok: true, note: null, warning: null };
|
|
8197
|
+
}
|
|
8198
|
+
|
|
8199
|
+
// src/commands/module/generate.ts
|
|
8200
|
+
init_sdk_version();
|
|
7535
8201
|
init_output();
|
|
7536
8202
|
function outputDir(projectRoot) {
|
|
7537
8203
|
return join(projectRoot, "node_modules", "@dashai", "generated");
|
|
@@ -7577,6 +8243,14 @@ async function moduleGenerateCommand(options) {
|
|
|
7577
8243
|
fail(`Could not generate SDK: ${err.message}`);
|
|
7578
8244
|
return 1;
|
|
7579
8245
|
}
|
|
8246
|
+
const verdict = judgeGeneratorStamp(readGeneratorStamp(res.files), {
|
|
8247
|
+
...options.allowNewerGenerator !== void 0 ? { allowNewerGenerator: options.allowNewerGenerator } : {}
|
|
8248
|
+
});
|
|
8249
|
+
if (!verdict.ok) {
|
|
8250
|
+
fail(`${verdict.code}: ${verdict.message}`);
|
|
8251
|
+
return 1;
|
|
8252
|
+
}
|
|
8253
|
+
if (verdict.warning !== null) warn(verdict.warning);
|
|
7580
8254
|
if (options.dryRun) {
|
|
7581
8255
|
info(`Dry run: ${Object.keys(res.files).length} files`);
|
|
7582
8256
|
info(`Hash: ${code(res.contentHash)}`);
|
|
@@ -7599,8 +8273,25 @@ async function moduleGenerateCommand(options) {
|
|
|
7599
8273
|
`Generated ${Object.keys(res.files).length} files into ${code("node_modules/@dashai/generated/")}`
|
|
7600
8274
|
);
|
|
7601
8275
|
info(`Hash: ${dim(res.contentHash)}`);
|
|
8276
|
+
if (verdict.note !== null && declaresUses(manifest)) info(verdict.note);
|
|
8277
|
+
warnIfSdkBelowUsesFloor(projectRoot, manifest);
|
|
7602
8278
|
return 0;
|
|
7603
8279
|
}
|
|
8280
|
+
function declaresUses(manifest) {
|
|
8281
|
+
const uses = manifest?.uses;
|
|
8282
|
+
return Array.isArray(uses?.tables) && uses.tables.length > 0;
|
|
8283
|
+
}
|
|
8284
|
+
function warnIfSdkBelowUsesFloor(projectRoot, manifest) {
|
|
8285
|
+
if (!declaresUses(manifest)) return;
|
|
8286
|
+
const sdk = checkSdkFloor(projectRoot, USES_SDK_FLOOR);
|
|
8287
|
+
if (sdk.ok) return;
|
|
8288
|
+
warn(
|
|
8289
|
+
`This manifest declares ${code("uses")}, which needs ${code(`@dashai/sdk >= ${sdk.floor}`)} \u2014 this project has ${code(sdk.installed ?? "none")}.`
|
|
8290
|
+
);
|
|
8291
|
+
info(
|
|
8292
|
+
`Every import from ${code("@dashai/generated")} will throw until you upgrade, including your own tables: ${code(`npm install @dashai/sdk@^${sdk.floor}`)}.`
|
|
8293
|
+
);
|
|
8294
|
+
}
|
|
7604
8295
|
|
|
7605
8296
|
// src/commands/module/dev.ts
|
|
7606
8297
|
init_output();
|
|
@@ -7904,6 +8595,7 @@ async function resolveWorkspaceWithFallback(opts) {
|
|
|
7904
8595
|
|
|
7905
8596
|
// src/commands/module/install.ts
|
|
7906
8597
|
init_output();
|
|
8598
|
+
init_connect_status();
|
|
7907
8599
|
async function moduleInstallCommand(slugArg, options) {
|
|
7908
8600
|
const slug = resolveSlug(slugArg, options.dir);
|
|
7909
8601
|
if (!slug) {
|
|
@@ -8077,6 +8769,8 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
8077
8769
|
const publicUrl = row?.public_url ?? null;
|
|
8078
8770
|
const installedVersion = row?.module_version?.version ?? targetVersion;
|
|
8079
8771
|
const runtimeProvisioned = isRealRuntimeUrl(row?.runtime_url);
|
|
8772
|
+
const readiness = await fetchUsesReadiness(result.installation.id, auth);
|
|
8773
|
+
const setupPending = readiness?.setupPending ?? false;
|
|
8080
8774
|
if (options.json) {
|
|
8081
8775
|
out(
|
|
8082
8776
|
JSON.stringify({
|
|
@@ -8085,11 +8779,15 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
8085
8779
|
version: installedVersion,
|
|
8086
8780
|
was_upgrade: result.was_upgrade,
|
|
8087
8781
|
public_url: publicUrl,
|
|
8088
|
-
verified
|
|
8782
|
+
verified,
|
|
8783
|
+
// `null` when the binding surface was unreachable; `[]` when the
|
|
8784
|
+
// module declares no slots. The two are not the same thing.
|
|
8785
|
+
uses: readiness === null ? null : usesInstallJson(readiness),
|
|
8786
|
+
setup_pending: setupPending
|
|
8089
8787
|
})
|
|
8090
8788
|
);
|
|
8091
8789
|
if (devInstallReplaced) printDevInstallAftermath(mod.slug, workspace2.slug);
|
|
8092
|
-
return 0;
|
|
8790
|
+
return setupPending ? EXIT_SETUP_PENDING : 0;
|
|
8093
8791
|
}
|
|
8094
8792
|
if (!verified) {
|
|
8095
8793
|
success(
|
|
@@ -8102,9 +8800,15 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
8102
8800
|
return 0;
|
|
8103
8801
|
}
|
|
8104
8802
|
if (runtimeProvisioned && publicUrl) {
|
|
8105
|
-
|
|
8106
|
-
|
|
8107
|
-
|
|
8803
|
+
if (setupPending) {
|
|
8804
|
+
success(
|
|
8805
|
+
`Deployed ${code(mod.slug)} ${code(`v${installedVersion}`)} at ${code(publicUrl)} \u2014 ${code("setup pending")}, not usable yet.`
|
|
8806
|
+
);
|
|
8807
|
+
} else {
|
|
8808
|
+
success(
|
|
8809
|
+
`Deployed ${code(mod.slug)} ${code(`v${installedVersion}`)} \u2014 live at ${code(publicUrl)}`
|
|
8810
|
+
);
|
|
8811
|
+
}
|
|
8108
8812
|
info(
|
|
8109
8813
|
`Install ${code(`#${result.installation.id}`)} (kind=${row?.kind ?? "unknown"}) in workspace ${code(workspace2.slug)}${result.was_upgrade ? " \u2014 upgraded in place." : "."}`
|
|
8110
8814
|
);
|
|
@@ -8123,8 +8827,9 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
8123
8827
|
`No public URL yet \u2014 runtime provisioning didn't complete. Re-running ${code("dashwise module install")} is idempotent and repairs a failed provision.`
|
|
8124
8828
|
);
|
|
8125
8829
|
}
|
|
8830
|
+
if (readiness !== null) renderUsesReadiness(readiness, result);
|
|
8126
8831
|
if (devInstallReplaced) printDevInstallAftermath(mod.slug, workspace2.slug);
|
|
8127
|
-
return 0;
|
|
8832
|
+
return setupPending ? EXIT_SETUP_PENDING : 0;
|
|
8128
8833
|
}
|
|
8129
8834
|
function publishedModuleLocalDevGuidance(slug) {
|
|
8130
8835
|
return `Re-provision local dev any time with ${code(`dashwise init ${slug} --workspace <ws>`)} (or ${code("dashwise module reset-dev-install")}) \u2014 requires backend \u2265 2026-07-15; the fresh dev install COEXISTS with the production install.
|
|
@@ -8280,6 +8985,18 @@ function surfaceInstallError(err, slug, workspaceSlug, devInstallReplaced) {
|
|
|
8280
8985
|
}
|
|
8281
8986
|
return 1;
|
|
8282
8987
|
}
|
|
8988
|
+
var EXIT_SETUP_PENDING = 3;
|
|
8989
|
+
function usesInstallJson(readiness) {
|
|
8990
|
+
return {
|
|
8991
|
+
pending: readiness.pending.map((b) => b.uses_slug),
|
|
8992
|
+
slots: readiness.slots.map((b) => ({
|
|
8993
|
+
uses_slug: b.uses_slug,
|
|
8994
|
+
status: b.status,
|
|
8995
|
+
required: b.if_missing !== "optional",
|
|
8996
|
+
table: b.table ?? null
|
|
8997
|
+
}))
|
|
8998
|
+
};
|
|
8999
|
+
}
|
|
8283
9000
|
|
|
8284
9001
|
// src/commands/module/list.ts
|
|
8285
9002
|
init_api_client();
|
|
@@ -8393,6 +9110,109 @@ async function moduleListCommand(options) {
|
|
|
8393
9110
|
init_api_client();
|
|
8394
9111
|
init_config();
|
|
8395
9112
|
init_output();
|
|
9113
|
+
|
|
9114
|
+
// src/lib/gitignore.ts
|
|
9115
|
+
function escapeLiteral(ch) {
|
|
9116
|
+
return ch.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
9117
|
+
}
|
|
9118
|
+
function patternToRegex(pattern, anchored) {
|
|
9119
|
+
let out2 = "";
|
|
9120
|
+
let i = 0;
|
|
9121
|
+
while (i < pattern.length) {
|
|
9122
|
+
const ch = pattern[i];
|
|
9123
|
+
if (ch === "*") {
|
|
9124
|
+
if (pattern[i + 1] === "*") {
|
|
9125
|
+
const nextIsSlash = pattern[i + 2] === "/";
|
|
9126
|
+
if (nextIsSlash) {
|
|
9127
|
+
out2 += "(?:.*/)?";
|
|
9128
|
+
i += 3;
|
|
9129
|
+
} else {
|
|
9130
|
+
out2 += ".*";
|
|
9131
|
+
i += 2;
|
|
9132
|
+
}
|
|
9133
|
+
continue;
|
|
9134
|
+
}
|
|
9135
|
+
out2 += "[^/]*";
|
|
9136
|
+
i += 1;
|
|
9137
|
+
continue;
|
|
9138
|
+
}
|
|
9139
|
+
if (ch === "?") {
|
|
9140
|
+
out2 += "[^/]";
|
|
9141
|
+
i += 1;
|
|
9142
|
+
continue;
|
|
9143
|
+
}
|
|
9144
|
+
if (ch === "\\" && i + 1 < pattern.length) {
|
|
9145
|
+
out2 += escapeLiteral(pattern[i + 1]);
|
|
9146
|
+
i += 2;
|
|
9147
|
+
continue;
|
|
9148
|
+
}
|
|
9149
|
+
out2 += escapeLiteral(ch);
|
|
9150
|
+
i += 1;
|
|
9151
|
+
}
|
|
9152
|
+
const prefix = anchored ? "^" : "^(?:.*/)?";
|
|
9153
|
+
return new RegExp(`${prefix}${out2}(?:/.*)?$`);
|
|
9154
|
+
}
|
|
9155
|
+
function parseGitignore(content) {
|
|
9156
|
+
const rules = [];
|
|
9157
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
9158
|
+
const line = rawLine.replace(/(?<!\\)\s+$/, "");
|
|
9159
|
+
if (line.length === 0 || line.startsWith("#")) continue;
|
|
9160
|
+
let pattern = line;
|
|
9161
|
+
let negated = false;
|
|
9162
|
+
if (pattern.startsWith("!")) {
|
|
9163
|
+
negated = true;
|
|
9164
|
+
pattern = pattern.slice(1);
|
|
9165
|
+
} else if (pattern.startsWith("\\!")) {
|
|
9166
|
+
pattern = pattern.slice(1);
|
|
9167
|
+
}
|
|
9168
|
+
if (pattern.length === 0) continue;
|
|
9169
|
+
let dirOnly = false;
|
|
9170
|
+
if (pattern.endsWith("/")) {
|
|
9171
|
+
dirOnly = true;
|
|
9172
|
+
pattern = pattern.slice(0, -1);
|
|
9173
|
+
}
|
|
9174
|
+
if (pattern.length === 0) continue;
|
|
9175
|
+
let anchored = pattern.includes("/");
|
|
9176
|
+
if (pattern.startsWith("/")) {
|
|
9177
|
+
anchored = true;
|
|
9178
|
+
pattern = pattern.slice(1);
|
|
9179
|
+
}
|
|
9180
|
+
if (pattern.length === 0) continue;
|
|
9181
|
+
rules.push({
|
|
9182
|
+
negated,
|
|
9183
|
+
dirOnly,
|
|
9184
|
+
regex: patternToRegex(pattern, anchored),
|
|
9185
|
+
source: line
|
|
9186
|
+
});
|
|
9187
|
+
}
|
|
9188
|
+
return rules;
|
|
9189
|
+
}
|
|
9190
|
+
function isIgnored(relPath, isDir, scopes) {
|
|
9191
|
+
let ignored = false;
|
|
9192
|
+
for (const scope of scopes) {
|
|
9193
|
+
if (scope.base !== "" && !relPath.startsWith(`${scope.base}/`)) continue;
|
|
9194
|
+
const local = scope.base === "" ? relPath : relPath.slice(scope.base.length + 1);
|
|
9195
|
+
if (local.length === 0) continue;
|
|
9196
|
+
const segments = local.split("/");
|
|
9197
|
+
const candidates = [];
|
|
9198
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
9199
|
+
candidates.push({ path: segments.slice(0, i + 1).join("/"), isDir: true });
|
|
9200
|
+
}
|
|
9201
|
+
candidates.push({ path: local, isDir });
|
|
9202
|
+
for (const rule of scope.rules) {
|
|
9203
|
+
for (const candidate of candidates) {
|
|
9204
|
+
if (rule.dirOnly && !candidate.isDir) continue;
|
|
9205
|
+
if (rule.regex.test(candidate.path)) {
|
|
9206
|
+
ignored = !rule.negated;
|
|
9207
|
+
break;
|
|
9208
|
+
}
|
|
9209
|
+
}
|
|
9210
|
+
}
|
|
9211
|
+
}
|
|
9212
|
+
return ignored;
|
|
9213
|
+
}
|
|
9214
|
+
|
|
9215
|
+
// src/lib/tarball.ts
|
|
8396
9216
|
var DEFAULT_EXCLUDE_PATTERNS = [
|
|
8397
9217
|
"node_modules",
|
|
8398
9218
|
".next",
|
|
@@ -8413,6 +9233,7 @@ var DEFAULT_EXCLUDE_PATTERNS = [
|
|
|
8413
9233
|
"dist",
|
|
8414
9234
|
"build"
|
|
8415
9235
|
];
|
|
9236
|
+
var ALWAYS_EXCLUDE_FILES = ["seed.mjs"];
|
|
8416
9237
|
var PER_FILE_BYTE_LIMIT = 10 * 1024 * 1024;
|
|
8417
9238
|
var MAX_TARBALL_BYTES = 100 * 1024 * 1024;
|
|
8418
9239
|
async function packTarball(opts) {
|
|
@@ -8424,10 +9245,18 @@ async function packTarball(opts) {
|
|
|
8424
9245
|
...opts.extraExclude ?? []
|
|
8425
9246
|
]);
|
|
8426
9247
|
const filesIncluded = [];
|
|
9248
|
+
const alwaysExcluded = [];
|
|
8427
9249
|
const warnings = [];
|
|
8428
|
-
const
|
|
9250
|
+
const respectGitignore = opts.respectGitignore !== false;
|
|
9251
|
+
const walked = walk(opts.cwd, excludeSet, respectGitignore);
|
|
9252
|
+
const candidates = walked.files;
|
|
9253
|
+
let gitignoredCount = walked.ignoredCount;
|
|
8429
9254
|
for (const abs of candidates) {
|
|
8430
9255
|
const rel = relative(opts.cwd, abs);
|
|
9256
|
+
if (ALWAYS_EXCLUDE_FILES.includes(rel)) {
|
|
9257
|
+
alwaysExcluded.push(rel);
|
|
9258
|
+
continue;
|
|
9259
|
+
}
|
|
8431
9260
|
const size = statSync(abs).size;
|
|
8432
9261
|
if (size > PER_FILE_BYTE_LIMIT) {
|
|
8433
9262
|
warnings.push(
|
|
@@ -8443,6 +9272,7 @@ async function packTarball(opts) {
|
|
|
8443
9272
|
);
|
|
8444
9273
|
}
|
|
8445
9274
|
filesIncluded.sort();
|
|
9275
|
+
alwaysExcluded.sort();
|
|
8446
9276
|
const chunks = [];
|
|
8447
9277
|
await new Promise((resolveDone, rejectDone) => {
|
|
8448
9278
|
const stream = tar.c(
|
|
@@ -8467,14 +9297,30 @@ async function packTarball(opts) {
|
|
|
8467
9297
|
`packTarball: tarball is ${formatBytes(bytes.byteLength)}, exceeds ${formatBytes(MAX_TARBALL_BYTES)} max`
|
|
8468
9298
|
);
|
|
8469
9299
|
}
|
|
8470
|
-
return { bytes, filesIncluded, warnings };
|
|
9300
|
+
return { bytes, filesIncluded, warnings, gitignoredCount, alwaysExcluded };
|
|
8471
9301
|
}
|
|
8472
|
-
function walk(root, excludeSet) {
|
|
9302
|
+
function walk(root, excludeSet, respectGitignore) {
|
|
8473
9303
|
const out2 = [];
|
|
8474
|
-
|
|
9304
|
+
let ignoredCount = 0;
|
|
9305
|
+
const rootScopes = [];
|
|
9306
|
+
if (respectGitignore) {
|
|
9307
|
+
const rootIgnore = readIgnoreFile(root);
|
|
9308
|
+
if (rootIgnore !== null) rootScopes.push({ base: "", rules: rootIgnore });
|
|
9309
|
+
}
|
|
9310
|
+
const stack = [
|
|
9311
|
+
{ dir: root, scopes: rootScopes }
|
|
9312
|
+
];
|
|
8475
9313
|
while (stack.length > 0) {
|
|
8476
|
-
const
|
|
8477
|
-
if (!
|
|
9314
|
+
const frame = stack.pop();
|
|
9315
|
+
if (!frame) continue;
|
|
9316
|
+
const { dir } = frame;
|
|
9317
|
+
let scopes = frame.scopes;
|
|
9318
|
+
if (respectGitignore && dir !== root) {
|
|
9319
|
+
const nested = readIgnoreFile(dir);
|
|
9320
|
+
if (nested !== null) {
|
|
9321
|
+
scopes = [...scopes, { base: toPosixRel(root, dir), rules: nested }];
|
|
9322
|
+
}
|
|
9323
|
+
}
|
|
8478
9324
|
let entries;
|
|
8479
9325
|
try {
|
|
8480
9326
|
entries = readdirSync(dir, { withFileTypes: true });
|
|
@@ -8484,14 +9330,34 @@ function walk(root, excludeSet) {
|
|
|
8484
9330
|
for (const entry of entries) {
|
|
8485
9331
|
if (excludeSet.has(entry.name)) continue;
|
|
8486
9332
|
const abs = join(dir, entry.name);
|
|
8487
|
-
|
|
8488
|
-
|
|
8489
|
-
|
|
9333
|
+
const isDir = entry.isDirectory();
|
|
9334
|
+
if (!isDir && !entry.isFile()) continue;
|
|
9335
|
+
if (respectGitignore && scopes.length > 0) {
|
|
9336
|
+
if (isIgnored(toPosixRel(root, abs), isDir, scopes)) {
|
|
9337
|
+
ignoredCount += 1;
|
|
9338
|
+
continue;
|
|
9339
|
+
}
|
|
9340
|
+
}
|
|
9341
|
+
if (isDir) {
|
|
9342
|
+
stack.push({ dir: abs, scopes });
|
|
9343
|
+
} else {
|
|
8490
9344
|
out2.push(abs);
|
|
8491
9345
|
}
|
|
8492
9346
|
}
|
|
8493
9347
|
}
|
|
8494
|
-
return out2;
|
|
9348
|
+
return { files: out2, ignoredCount };
|
|
9349
|
+
}
|
|
9350
|
+
function readIgnoreFile(dir) {
|
|
9351
|
+
const path = join(dir, ".gitignore");
|
|
9352
|
+
if (!existsSync(path)) return null;
|
|
9353
|
+
try {
|
|
9354
|
+
return parseGitignore(readFileSync(path, "utf-8"));
|
|
9355
|
+
} catch {
|
|
9356
|
+
return null;
|
|
9357
|
+
}
|
|
9358
|
+
}
|
|
9359
|
+
function toPosixRel(root, abs) {
|
|
9360
|
+
return relative(root, abs).split(sep).join("/");
|
|
8495
9361
|
}
|
|
8496
9362
|
function formatBytes(n) {
|
|
8497
9363
|
if (n < 1024) return `${n}B`;
|
|
@@ -8501,6 +9367,11 @@ function formatBytes(n) {
|
|
|
8501
9367
|
|
|
8502
9368
|
// src/commands/module/publish.ts
|
|
8503
9369
|
var VALID_BUMP = /* @__PURE__ */ new Set(["patch", "minor", "major"]);
|
|
9370
|
+
var PACKED_FILE_LIST_CAP = 20;
|
|
9371
|
+
function packedFileLines(files, cap = PACKED_FILE_LIST_CAP) {
|
|
9372
|
+
if (files.length <= cap) return [...files];
|
|
9373
|
+
return [...files.slice(0, cap), `\u2026 and ${files.length - cap} more`];
|
|
9374
|
+
}
|
|
8504
9375
|
async function modulePublishCommand(options) {
|
|
8505
9376
|
const projectRoot = resolve(options.dir ?? process.cwd());
|
|
8506
9377
|
const manifestPath2 = join(projectRoot, "module.json");
|
|
@@ -8575,7 +9446,8 @@ async function modulePublishCommand(options) {
|
|
|
8575
9446
|
try {
|
|
8576
9447
|
tarball = await packTarball({
|
|
8577
9448
|
cwd: projectRoot,
|
|
8578
|
-
...options.exclude !== void 0 ? { extraExclude: options.exclude } : {}
|
|
9449
|
+
...options.exclude !== void 0 ? { extraExclude: options.exclude } : {},
|
|
9450
|
+
respectGitignore: !options.noRespectGitignore
|
|
8579
9451
|
});
|
|
8580
9452
|
} catch (err) {
|
|
8581
9453
|
fail(`Could not pack tarball: ${err.message}`);
|
|
@@ -8584,6 +9456,17 @@ async function modulePublishCommand(options) {
|
|
|
8584
9456
|
info(
|
|
8585
9457
|
`Packed ${tarball.filesIncluded.length} file${tarball.filesIncluded.length === 1 ? "" : "s"} into ${formatBytes2(tarball.bytes.byteLength)} (cap ${formatBytes2(MAX_TARBALL_BYTES)}).`
|
|
8586
9458
|
);
|
|
9459
|
+
for (const line of packedFileLines(tarball.filesIncluded)) out(` ${dim(line)}`);
|
|
9460
|
+
if (tarball.alwaysExcluded.length > 0) {
|
|
9461
|
+
info(
|
|
9462
|
+
`${tarball.alwaysExcluded.map((f) => code(f)).join(", ")} excluded \u2014 scaffold-local development ${tarball.alwaysExcluded.length === 1 ? "file, not part" : "files, not part"} of the running module, and never published.`
|
|
9463
|
+
);
|
|
9464
|
+
}
|
|
9465
|
+
if (tarball.gitignoredCount > 0) {
|
|
9466
|
+
info(
|
|
9467
|
+
`${tarball.gitignoredCount} path${tarball.gitignoredCount === 1 ? "" : "s"} excluded by ${code(".gitignore")}${options.noRespectGitignore ? "" : ` ${dim("(--no-respect-gitignore to include them)")}`}.`
|
|
9468
|
+
);
|
|
9469
|
+
}
|
|
8587
9470
|
for (const w of tarball.warnings) warn(w);
|
|
8588
9471
|
if (!options.yes && process.stdout.isTTY && !options.dryRun) {
|
|
8589
9472
|
const answer = await select({
|
|
@@ -8754,6 +9637,7 @@ init_api_client();
|
|
|
8754
9637
|
init_config();
|
|
8755
9638
|
init_manifest_io();
|
|
8756
9639
|
init_output();
|
|
9640
|
+
init_connect_status();
|
|
8757
9641
|
async function moduleStatusCommand(slugArg, options) {
|
|
8758
9642
|
let slug = slugArg;
|
|
8759
9643
|
if (!slug) {
|
|
@@ -8839,6 +9723,7 @@ async function moduleStatusCommand(slugArg, options) {
|
|
|
8839
9723
|
const state = runtimeProvisioned && row.public_url ? "live" : runtimeProvisioned ? "deployed" : "not_deployed";
|
|
8840
9724
|
const installedVersion = row.module_version?.version ?? null;
|
|
8841
9725
|
const updateAvailable = isUpdateAvailable(installedVersion, latestVersion);
|
|
9726
|
+
const readiness = await fetchUsesReadiness(row.id, auth);
|
|
8842
9727
|
if (options.json) {
|
|
8843
9728
|
out(
|
|
8844
9729
|
JSON.stringify({
|
|
@@ -8854,6 +9739,11 @@ async function moduleStatusCommand(slugArg, options) {
|
|
|
8854
9739
|
runtime_url: row.runtime_url,
|
|
8855
9740
|
public_url: row.public_url,
|
|
8856
9741
|
state,
|
|
9742
|
+
// C-09: per-slot data-plane readiness for THIS install. `null`
|
|
9743
|
+
// when the binding surface was unreachable — distinct from `[]`,
|
|
9744
|
+
// which means the module declares no slots.
|
|
9745
|
+
uses: readiness === null ? null : usesJson(readiness),
|
|
9746
|
+
setup_pending: readiness?.setupPending ?? false,
|
|
8857
9747
|
// The coexisting dev install, so a script can see BOTH slots — the
|
|
8858
9748
|
// human output reports it and `--json` used to hide it.
|
|
8859
9749
|
dev_install: siblingDevInstall ? { id: siblingDevInstall.id, kind: siblingDevInstall.kind ?? null } : null
|
|
@@ -8875,8 +9765,12 @@ async function moduleStatusCommand(slugArg, options) {
|
|
|
8875
9765
|
runtimeProvisioned ? `provisioned ${dim(`(${row.runtime_url})`)}` : "not provisioned"
|
|
8876
9766
|
);
|
|
8877
9767
|
kv("Public URL:", row.public_url ?? dim("(none)"));
|
|
8878
|
-
kv(
|
|
9768
|
+
kv(
|
|
9769
|
+
"State:",
|
|
9770
|
+
readiness?.setupPending ? `${stateLabel} ${dim("\u2014 setup pending")}` : stateLabel
|
|
9771
|
+
);
|
|
8879
9772
|
out("");
|
|
9773
|
+
if (readiness !== null) renderUsesReadiness(readiness, row);
|
|
8880
9774
|
if (siblingDevInstall) {
|
|
8881
9775
|
info(
|
|
8882
9776
|
`A ${code(`kind=${siblingDevInstall.kind ?? "unknown"}`)} dev install (#${siblingDevInstall.id}) also exists in this workspace and coexists with the production install above \u2014 that is the one ${code("dashwise dev")} talks to.`
|
|
@@ -8893,6 +9787,20 @@ async function moduleStatusCommand(slugArg, options) {
|
|
|
8893
9787
|
}
|
|
8894
9788
|
return 0;
|
|
8895
9789
|
}
|
|
9790
|
+
function usesJson(readiness) {
|
|
9791
|
+
return {
|
|
9792
|
+
setup_pending: readiness.setupPending,
|
|
9793
|
+
slots: readiness.slots.map((b) => ({
|
|
9794
|
+
uses_slug: b.uses_slug,
|
|
9795
|
+
status: b.status,
|
|
9796
|
+
if_missing: b.if_missing ?? null,
|
|
9797
|
+
required: b.if_missing !== "optional",
|
|
9798
|
+
table: b.table ?? null,
|
|
9799
|
+
missing_fields: b.missing_fields ?? [],
|
|
9800
|
+
reason: b.reason ?? null
|
|
9801
|
+
}))
|
|
9802
|
+
};
|
|
9803
|
+
}
|
|
8896
9804
|
|
|
8897
9805
|
// src/commands/module/uninstall.ts
|
|
8898
9806
|
init_api_client();
|
|
@@ -10499,30 +11407,8 @@ function surfaceBindingError(err) {
|
|
|
10499
11407
|
init_api_client();
|
|
10500
11408
|
init_config();
|
|
10501
11409
|
init_manifest_io();
|
|
10502
|
-
|
|
10503
|
-
|
|
10504
|
-
var PHYSICAL_TO_ABSTRACT = {
|
|
10505
|
-
text: "text",
|
|
10506
|
-
long_text: "long_text",
|
|
10507
|
-
number: "number",
|
|
10508
|
-
rating: "number",
|
|
10509
|
-
boolean: "boolean",
|
|
10510
|
-
// A date-with-time column has physical registry type `date` (with
|
|
10511
|
-
// `config.includeTime`), NOT a distinct `datetime` type — the backend
|
|
10512
|
-
// registry has no `datetime` key. So introspection only ever reports
|
|
10513
|
-
// `date` here; there is no `datetime` physical entry to fold.
|
|
10514
|
-
date: "date",
|
|
10515
|
-
email: "email",
|
|
10516
|
-
url: "url",
|
|
10517
|
-
single_select: "single_select",
|
|
10518
|
-
multi_select: "multi_select"
|
|
10519
|
-
};
|
|
10520
|
-
function abstractTypeForPhysical(physicalType) {
|
|
10521
|
-
return PHYSICAL_TO_ABSTRACT[physicalType] ?? null;
|
|
10522
|
-
}
|
|
10523
|
-
var USES_OPERATIONS = ["create", "read", "update", "delete"];
|
|
10524
|
-
|
|
10525
|
-
// src/commands/uses/add.ts
|
|
11410
|
+
init_uses_types();
|
|
11411
|
+
init_sdk_version();
|
|
10526
11412
|
init_output();
|
|
10527
11413
|
async function usesAddCommand(options) {
|
|
10528
11414
|
if (!options.fromTable) {
|
|
@@ -10543,6 +11429,18 @@ async function usesAddCommand(options) {
|
|
|
10543
11429
|
fail(err.message);
|
|
10544
11430
|
return 1;
|
|
10545
11431
|
}
|
|
11432
|
+
const sdk = checkSdkFloor(resolve(options.dir ?? process.cwd()), USES_SDK_FLOOR);
|
|
11433
|
+
if (!sdk.ok) {
|
|
11434
|
+
fail(
|
|
11435
|
+
`A ${code("uses")} contract needs ${code(`@dashai/sdk >= ${sdk.floor}`)}; this project has ${code(sdk.installed ?? "none")}.`
|
|
11436
|
+
);
|
|
11437
|
+
info(
|
|
11438
|
+
`Below ${code(sdk.floor)} every import from ${code("@dashai/generated")} throws \u2014 including your own tables. Upgrade first: ${code(`npm install @dashai/sdk@^${sdk.floor}`)}.`
|
|
11439
|
+
);
|
|
11440
|
+
return 1;
|
|
11441
|
+
}
|
|
11442
|
+
const scopeExit = await assertTableInInstallWorkspace(ctx, options.fromTable);
|
|
11443
|
+
if (scopeExit !== 0) return scopeExit;
|
|
10546
11444
|
let table;
|
|
10547
11445
|
try {
|
|
10548
11446
|
table = await introspectWorkspaceTable(
|
|
@@ -10553,10 +11451,10 @@ async function usesAddCommand(options) {
|
|
|
10553
11451
|
} catch (err) {
|
|
10554
11452
|
return surfaceIntrospectError(err, options.fromTable);
|
|
10555
11453
|
}
|
|
10556
|
-
const slotSlug = options.slug ??
|
|
10557
|
-
if (!
|
|
11454
|
+
const slotSlug = options.slug ?? slugifyUsesName(table.name);
|
|
11455
|
+
if (!isUsesSlug(slotSlug)) {
|
|
10558
11456
|
fail(
|
|
10559
|
-
`
|
|
11457
|
+
`Slot slug ${code(slotSlug || "(empty)")} doesn't match ${code(USES_SLUG_RULE)}. Pass ${code("--slug <snake_case_slug>")} explicitly.`
|
|
10560
11458
|
);
|
|
10561
11459
|
return 1;
|
|
10562
11460
|
}
|
|
@@ -10567,6 +11465,37 @@ async function usesAddCommand(options) {
|
|
|
10567
11465
|
);
|
|
10568
11466
|
return 1;
|
|
10569
11467
|
}
|
|
11468
|
+
const badFields = fields.filter((f) => !isUsesSlug(f.slug));
|
|
11469
|
+
if (badFields.length > 0) {
|
|
11470
|
+
fail(
|
|
11471
|
+
`${badFields.length} field name(s) on ${code(table.name)} don't fold into a legal ${code("uses")} field slug (${code(USES_SLUG_RULE)}):`
|
|
11472
|
+
);
|
|
11473
|
+
for (const f of badFields) {
|
|
11474
|
+
const physical = table.fields.find((p) => p.slug === f.slug);
|
|
11475
|
+
const name = physical?.name ?? f.slug;
|
|
11476
|
+
out(` ${dim("-")} ${code(name)} \u2192 ${code(f.slug || "(empty)")}`);
|
|
11477
|
+
}
|
|
11478
|
+
info(
|
|
11479
|
+
`Rename the field(s) in Dashwise, or declare the slot by hand in ${code("module.json#uses.tables")}. Nothing was written.`
|
|
11480
|
+
);
|
|
11481
|
+
return 1;
|
|
11482
|
+
}
|
|
11483
|
+
const slugCounts = /* @__PURE__ */ new Map();
|
|
11484
|
+
for (const f of fields) slugCounts.set(f.slug, (slugCounts.get(f.slug) ?? 0) + 1);
|
|
11485
|
+
const collidingSlugs = [...slugCounts.entries()].filter(([, n]) => n > 1).map(([slug]) => slug);
|
|
11486
|
+
if (collidingSlugs.length > 0) {
|
|
11487
|
+
fail(
|
|
11488
|
+
`${collidingSlugs.length} generated ${code("uses")} field slug(s) on ${code(table.name)} collide \u2014 the backend refuses a contract with a duplicate field slug:`
|
|
11489
|
+
);
|
|
11490
|
+
for (const slug of collidingSlugs) {
|
|
11491
|
+
const names = table.fields.filter((p) => p.slug === slug && abstractTypeForPhysical(p.type) !== null).map((p) => p.name ?? p.slug);
|
|
11492
|
+
out(` ${dim("-")} ${names.map((n) => code(n)).join(" + ")} \u2192 ${code(slug)}`);
|
|
11493
|
+
}
|
|
11494
|
+
info(
|
|
11495
|
+
`Rename one of each pair in Dashwise, or declare the slot by hand in ${code("module.json#uses.tables")}. Nothing was written.`
|
|
11496
|
+
);
|
|
11497
|
+
return 1;
|
|
11498
|
+
}
|
|
10570
11499
|
const slot = {
|
|
10571
11500
|
slug: slotSlug,
|
|
10572
11501
|
ops,
|
|
@@ -10603,6 +11532,13 @@ async function usesAddCommand(options) {
|
|
|
10603
11532
|
}
|
|
10604
11533
|
uses.tables = tables;
|
|
10605
11534
|
ctx.manifest.uses = uses;
|
|
11535
|
+
const pushed = await pushManifest(ctx);
|
|
11536
|
+
if (!pushed.ok && pushed.rejected) {
|
|
11537
|
+
info(
|
|
11538
|
+
`${code("module.json")} was left unchanged \u2014 the generated slot was rejected before it could be written.`
|
|
11539
|
+
);
|
|
11540
|
+
return 1;
|
|
11541
|
+
}
|
|
10606
11542
|
try {
|
|
10607
11543
|
writeManifest(ctx.manifest, ctx.dir);
|
|
10608
11544
|
} catch (err) {
|
|
@@ -10612,8 +11548,7 @@ async function usesAddCommand(options) {
|
|
|
10612
11548
|
success(
|
|
10613
11549
|
`${existingIdx >= 0 ? "Replaced" : "Added"} ${code(`uses.tables[${slotSlug}]`)} in module.json.`
|
|
10614
11550
|
);
|
|
10615
|
-
|
|
10616
|
-
if (!pushed) {
|
|
11551
|
+
if (!pushed.ok) {
|
|
10617
11552
|
info(
|
|
10618
11553
|
`The manifest edit is saved locally. Push it (${code("dashwise dev")} watches + applies, or re-run once the backend is reachable), then ${code(`dashwise bind ${slotSlug} --table ${table.table_id}`)}.`
|
|
10619
11554
|
);
|
|
@@ -10638,6 +11573,52 @@ async function usesAddCommand(options) {
|
|
|
10638
11573
|
}
|
|
10639
11574
|
return 0;
|
|
10640
11575
|
}
|
|
11576
|
+
async function assertTableInInstallWorkspace(ctx, fromTable) {
|
|
11577
|
+
if (ctx.workspaceId === void 0) return 0;
|
|
11578
|
+
const wanted = Number(fromTable);
|
|
11579
|
+
if (!Number.isInteger(wanted) || wanted <= 0) return 0;
|
|
11580
|
+
let tables;
|
|
11581
|
+
try {
|
|
11582
|
+
tables = await listWorkspaceTables(ctx.workspaceId, ctx.requestOptions);
|
|
11583
|
+
} catch {
|
|
11584
|
+
return 0;
|
|
11585
|
+
}
|
|
11586
|
+
const wantedRow = tables.find((t) => t.table_id === wanted);
|
|
11587
|
+
if (wantedRow !== void 0 && !isModuleManaged(wantedRow)) return 0;
|
|
11588
|
+
if (wantedRow !== void 0) {
|
|
11589
|
+
fail(
|
|
11590
|
+
`Table ${code(String(wanted))} (${wantedRow.name}) is managed by a module, so a ${code("uses")} contract on it could never be bound (${code("TABLE_IS_MODULE_MANAGED")}).`
|
|
11591
|
+
);
|
|
11592
|
+
info(
|
|
11593
|
+
`A ${code("uses")} slot binds a table the WORKSPACE owns. Reach another module's tables as a dependency instead: ${code("dashwise deps")}.`
|
|
11594
|
+
);
|
|
11595
|
+
return 1;
|
|
11596
|
+
}
|
|
11597
|
+
const bindable = tables.filter((t) => !isModuleManaged(t));
|
|
11598
|
+
const hidden = tables.length - bindable.length;
|
|
11599
|
+
fail(
|
|
11600
|
+
`Table ${code(String(wanted))} is not in this install's workspace, so a ${code("uses")} contract on it could never be bound.`
|
|
11601
|
+
);
|
|
11602
|
+
if (bindable.length === 0) {
|
|
11603
|
+
info(
|
|
11604
|
+
hidden > 0 ? `Every table visible to you in that workspace (${hidden}) is managed by a module, so none of them can back a ${code("uses")} slot either. Create a workspace table in Dashwise, or reach a module's tables with ${code("dashwise deps")}.` : `No tables are visible to you in that workspace. Create one in Dashwise, or re-provision the dev install with ${code("dashwise init")} against the right workspace.`
|
|
11605
|
+
);
|
|
11606
|
+
return 1;
|
|
11607
|
+
}
|
|
11608
|
+
out(` ${dim(`${bindable.length} table(s) in this workspace:`)}`);
|
|
11609
|
+
for (const t of bindable) {
|
|
11610
|
+
out(
|
|
11611
|
+
` - ${t.name} ${dim(`(id ${t.table_id}, in ${t.application_name})`)}`
|
|
11612
|
+
);
|
|
11613
|
+
}
|
|
11614
|
+
info(
|
|
11615
|
+
hidden > 0 ? `That is the workspace's table list \u2014 ${hidden} module-managed table(s) are not shown, because a ${code("uses")} slot cannot bind one (${code("TABLE_IS_MODULE_MANAGED")}); reach those with ${code("dashwise deps")}.` : `That is the workspace's table list \u2014 the server decides which of them can back a ${code("uses")} slot, and refuses a module-managed one (${code("TABLE_IS_MODULE_MANAGED")}); reach those with ${code("dashwise deps")}.`
|
|
11616
|
+
);
|
|
11617
|
+
return 1;
|
|
11618
|
+
}
|
|
11619
|
+
function isModuleManaged(t) {
|
|
11620
|
+
return typeof t.module_id === "number";
|
|
11621
|
+
}
|
|
10641
11622
|
function loadContext3(opts) {
|
|
10642
11623
|
const manifest = readManifestOrThrow(opts.dir);
|
|
10643
11624
|
const moduleSlug = manifest.module?.slug;
|
|
@@ -10656,6 +11637,7 @@ function loadContext3(opts) {
|
|
|
10656
11637
|
}
|
|
10657
11638
|
return {
|
|
10658
11639
|
installationId: entry.installationId,
|
|
11640
|
+
...entry.workspaceId !== void 0 ? { workspaceId: entry.workspaceId } : {},
|
|
10659
11641
|
moduleSlug,
|
|
10660
11642
|
...opts.dir !== void 0 ? { dir: opts.dir } : {},
|
|
10661
11643
|
manifest,
|
|
@@ -10675,10 +11657,10 @@ async function pushManifest(ctx) {
|
|
|
10675
11657
|
warn(
|
|
10676
11658
|
`Module ${code(ctx.moduleSlug)} isn't registered on the backend yet. Run ${code("dashwise module register")} (or ${code("dashwise init")}) before on-ramping a table.`
|
|
10677
11659
|
);
|
|
10678
|
-
return false;
|
|
11660
|
+
return { ok: false, rejected: false };
|
|
10679
11661
|
}
|
|
10680
11662
|
warn(`Couldn't resolve the module id to push the manifest: ${err.message}`);
|
|
10681
|
-
return false;
|
|
11663
|
+
return { ok: false, rejected: false };
|
|
10682
11664
|
}
|
|
10683
11665
|
try {
|
|
10684
11666
|
await applyDraftManifest(
|
|
@@ -10687,20 +11669,20 @@ async function pushManifest(ctx) {
|
|
|
10687
11669
|
ctx.requestOptions
|
|
10688
11670
|
);
|
|
10689
11671
|
success("Pushed the manifest \u2014 the backend parked the new slot, ready to bind.");
|
|
10690
|
-
return true;
|
|
11672
|
+
return { ok: true };
|
|
10691
11673
|
} catch (err) {
|
|
10692
11674
|
if (err instanceof ApiError) {
|
|
10693
11675
|
if (err.code === "MANIFEST_INVALID" || err.status === 400) {
|
|
10694
11676
|
fail(`Manifest rejected by the backend validator: ${err.message}`);
|
|
10695
11677
|
const issues = extractIssues(err.context);
|
|
10696
11678
|
for (const issue of issues) out(` ${dim("-")} ${issue}`);
|
|
10697
|
-
|
|
10698
|
-
fail(`Manifest push failed: ${err.code} ${err.status} \u2014 ${err.message}`);
|
|
11679
|
+
return { ok: false, rejected: true };
|
|
10699
11680
|
}
|
|
10700
|
-
|
|
10701
|
-
|
|
11681
|
+
fail(`Manifest push failed: ${err.code} ${err.status} \u2014 ${err.message}`);
|
|
11682
|
+
return { ok: false, rejected: false };
|
|
10702
11683
|
}
|
|
10703
|
-
|
|
11684
|
+
fail(`Manifest push failed: ${err.message}`);
|
|
11685
|
+
return { ok: false, rejected: false };
|
|
10704
11686
|
}
|
|
10705
11687
|
}
|
|
10706
11688
|
function extractIssues(context) {
|
|
@@ -10750,13 +11732,6 @@ function parseOps(opsCsv) {
|
|
|
10750
11732
|
}
|
|
10751
11733
|
return USES_OPERATIONS.filter((o) => seen.has(o));
|
|
10752
11734
|
}
|
|
10753
|
-
function slugify(name) {
|
|
10754
|
-
return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
10755
|
-
}
|
|
10756
|
-
var KEBAB_REGEX = /^[a-z][a-z0-9-]*[a-z0-9]$/;
|
|
10757
|
-
function isKebab(slug) {
|
|
10758
|
-
return KEBAB_REGEX.test(slug);
|
|
10759
|
-
}
|
|
10760
11735
|
function surfaceIntrospectError(err, tableRef) {
|
|
10761
11736
|
if (err instanceof ApiError) {
|
|
10762
11737
|
switch (err.code) {
|
|
@@ -12061,28 +13036,36 @@ function formatTimestamp(iso) {
|
|
|
12061
13036
|
return iso.slice(0, 19).replace("T", " ");
|
|
12062
13037
|
}
|
|
12063
13038
|
function sleep2(ms) {
|
|
12064
|
-
return new Promise((
|
|
13039
|
+
return new Promise((resolve14) => setTimeout(resolve14, ms));
|
|
12065
13040
|
}
|
|
12066
13041
|
|
|
12067
13042
|
// src/commands/module/action-log.ts
|
|
12068
13043
|
init_api_client();
|
|
13044
|
+
init_manifest_io();
|
|
12069
13045
|
init_output();
|
|
12070
13046
|
var POLL_INTERVAL_MS2 = 5e3;
|
|
12071
13047
|
async function moduleActionLogCommand(options) {
|
|
12072
|
-
if (options.installation === void 0 && options.module === void 0) {
|
|
12073
|
-
fail("Must pass either `--installation <id>` or `--module <slug>`.");
|
|
12074
|
-
return 1;
|
|
12075
|
-
}
|
|
12076
13048
|
if (options.installation !== void 0 && options.module !== void 0) {
|
|
12077
13049
|
fail("`--installation` and `--module` are mutually exclusive \u2014 pass one or the other.");
|
|
12078
13050
|
return 1;
|
|
12079
13051
|
}
|
|
13052
|
+
let moduleSlug = options.module;
|
|
13053
|
+
if (options.installation === void 0 && moduleSlug === void 0) {
|
|
13054
|
+
const inferred = inferModuleSlug(options.dir);
|
|
13055
|
+
if (inferred === null) {
|
|
13056
|
+
fail(
|
|
13057
|
+
"Pass `--installation <id>` or `--module <slug>`, or run this inside a module project directory containing module.json."
|
|
13058
|
+
);
|
|
13059
|
+
return 1;
|
|
13060
|
+
}
|
|
13061
|
+
moduleSlug = inferred;
|
|
13062
|
+
}
|
|
12080
13063
|
let installationId = options.installation;
|
|
12081
|
-
if (installationId === void 0 &&
|
|
13064
|
+
if (installationId === void 0 && moduleSlug !== void 0) {
|
|
12082
13065
|
try {
|
|
12083
13066
|
const resolved = await apiRequest(
|
|
12084
13067
|
"GET",
|
|
12085
|
-
`/api/modules/by-slug/${encodeURIComponent(
|
|
13068
|
+
`/api/modules/by-slug/${encodeURIComponent(moduleSlug)}/my-installation`
|
|
12086
13069
|
);
|
|
12087
13070
|
installationId = resolved.installation_id;
|
|
12088
13071
|
} catch (err) {
|
|
@@ -12155,7 +13138,7 @@ async function runTail2(installationId, options) {
|
|
|
12155
13138
|
}
|
|
12156
13139
|
dim(`(tail: transient error \u2014 ${err.message})`);
|
|
12157
13140
|
}
|
|
12158
|
-
await new Promise((
|
|
13141
|
+
await new Promise((resolve14) => setTimeout(resolve14, POLL_INTERVAL_MS2));
|
|
12159
13142
|
}
|
|
12160
13143
|
}
|
|
12161
13144
|
function printTable(rows) {
|
|
@@ -12220,6 +13203,17 @@ function mapAndPrintError(err, json) {
|
|
|
12220
13203
|
else fail(msg);
|
|
12221
13204
|
return 1;
|
|
12222
13205
|
}
|
|
13206
|
+
function inferModuleSlug(dir) {
|
|
13207
|
+
try {
|
|
13208
|
+
const manifest = readManifestOrThrow(
|
|
13209
|
+
dir ?? process.cwd()
|
|
13210
|
+
);
|
|
13211
|
+
const slug = manifest?.module?.slug;
|
|
13212
|
+
return typeof slug === "string" && slug.length > 0 ? slug : null;
|
|
13213
|
+
} catch {
|
|
13214
|
+
return null;
|
|
13215
|
+
}
|
|
13216
|
+
}
|
|
12223
13217
|
|
|
12224
13218
|
// src/commands/module/logs.ts
|
|
12225
13219
|
init_api_client();
|
|
@@ -12529,7 +13523,7 @@ async function sleep3(ms, aborted) {
|
|
|
12529
13523
|
const slice = 100;
|
|
12530
13524
|
let waited = 0;
|
|
12531
13525
|
while (waited < ms && !aborted()) {
|
|
12532
|
-
await new Promise((
|
|
13526
|
+
await new Promise((resolve14) => setTimeout(resolve14, Math.min(slice, ms - waited)));
|
|
12533
13527
|
waited += slice;
|
|
12534
13528
|
}
|
|
12535
13529
|
}
|
|
@@ -12539,7 +13533,7 @@ init_output();
|
|
|
12539
13533
|
var program = new Command();
|
|
12540
13534
|
program.name("dashwise").description("Dashwise CLI \u2014 log in, author modules, publish to the marketplace.").version(getVersion(), "-v, --version", "Show the CLI version + exit").option(
|
|
12541
13535
|
"--api-url <url>",
|
|
12542
|
-
|
|
13536
|
+
`Override Dashwise backend URL (default: $DASHWISE_API_URL, else your saved login, else ${KNOWN_API_URLS.dev}). Prod is ${KNOWN_API_URLS.prod}; pass a localhost URL to target a backend you are running yourself.`
|
|
12543
13537
|
);
|
|
12544
13538
|
program.hook("preAction", () => {
|
|
12545
13539
|
const apiUrl = program.opts().apiUrl?.trim();
|
|
@@ -12630,7 +13624,9 @@ mcp.command("add").description(
|
|
|
12630
13624
|
});
|
|
12631
13625
|
}
|
|
12632
13626
|
);
|
|
12633
|
-
var agent = program.command("agent").description(
|
|
13627
|
+
var agent = program.command("agent").description(
|
|
13628
|
+
"Set up a coding agent to work with Dashwise (`dashwise agent setup`, `dashwise agent status`)."
|
|
13629
|
+
);
|
|
12634
13630
|
agent.command("setup").description(
|
|
12635
13631
|
"Install the Dashwise plugin into Claude Code \u2014 its skills, the `dashwise` MCP server and the secrets guard \u2014 by running `claude plugin marketplace add` and `claude plugin install`. Without Claude Code, prints the two commands instead."
|
|
12636
13632
|
).option(
|
|
@@ -12643,12 +13639,27 @@ agent.command("setup").description(
|
|
|
12643
13639
|
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
12644
13640
|
});
|
|
12645
13641
|
});
|
|
13642
|
+
agent.command("status").description(
|
|
13643
|
+
"Report the installed Dashwise plugin and skills: their versions, the path the skills are readable at without a restart, the newest version this machine has fetched, and the command that upgrades. Reads the filesystem only \u2014 it does not need Claude Code on the PATH."
|
|
13644
|
+
).option("--json", "Print one JSON object on stdout instead of the report.").action(async (cmdOpts) => {
|
|
13645
|
+
process.exitCode = await agentStatusCommand({
|
|
13646
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
13647
|
+
});
|
|
13648
|
+
});
|
|
12646
13649
|
var workspace = program.command("workspace").description("Workspace context commands.");
|
|
12647
13650
|
workspace.command("list").description("List accessible workspaces.").option("--json", "Emit JSON to stdout instead of human-readable columns.").action(async (cmdOpts) => {
|
|
12648
13651
|
process.exitCode = await workspaceListCommand({
|
|
12649
13652
|
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
12650
13653
|
});
|
|
12651
13654
|
});
|
|
13655
|
+
workspace.command("tables").description(
|
|
13656
|
+
"List a workspace's tables with their numeric ids \u2014 the ids `dashwise uses add --from-table <id>` needs. Defaults to the saved default workspace."
|
|
13657
|
+
).option("--workspace <slug>", "Workspace slug or numeric id (default: the saved default).").option("--json", "Emit JSON to stdout instead of human-readable columns.").action(async (cmdOpts) => {
|
|
13658
|
+
process.exitCode = await workspaceTablesCommand({
|
|
13659
|
+
...cmdOpts.workspace !== void 0 ? { workspace: cmdOpts.workspace } : {},
|
|
13660
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
13661
|
+
});
|
|
13662
|
+
});
|
|
12652
13663
|
workspace.command("use <slug>").description("Set the default workspace for subsequent commands.").action(async (slug) => {
|
|
12653
13664
|
process.exitCode = await workspaceUseCommand(slug);
|
|
12654
13665
|
});
|
|
@@ -12780,12 +13791,18 @@ moduleCmd.command("export [dir]").description(
|
|
|
12780
13791
|
});
|
|
12781
13792
|
}
|
|
12782
13793
|
);
|
|
12783
|
-
moduleCmd.command("generate").description("Regenerate typed wrappers from the local module.json into node_modules/@dashai/generated/.").option("--dir <path>", "Project root (default: current directory).").option("--dry-run", "Print summary without writing files.").
|
|
12784
|
-
|
|
12785
|
-
|
|
12786
|
-
|
|
12787
|
-
|
|
12788
|
-
|
|
13794
|
+
moduleCmd.command("generate").description("Regenerate typed wrappers from the local module.json into node_modules/@dashai/generated/. Refuses output stamped with a generator major this CLI does not know (GENERATOR_MAJOR_UNSUPPORTED) before writing anything.").option("--dir <path>", "Project root (default: current directory).").option("--dry-run", "Print summary without writing files.").option(
|
|
13795
|
+
"--allow-newer-generator",
|
|
13796
|
+
"Write output from a generator major this CLI does not know, with a warning, instead of refusing (GENERATOR_MAJOR_UNSUPPORTED). Generation is server-side, so the files are identical to what a newer CLI would write \u2014 you lose only this CLI's account of what changed. For a backend that has rolled a generator major ahead of the CLI release."
|
|
13797
|
+
).action(
|
|
13798
|
+
async (cmdOpts) => {
|
|
13799
|
+
process.exitCode = await moduleGenerateCommand({
|
|
13800
|
+
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
13801
|
+
...cmdOpts.dryRun !== void 0 ? { dryRun: cmdOpts.dryRun } : {},
|
|
13802
|
+
...cmdOpts.allowNewerGenerator !== void 0 ? { allowNewerGenerator: cmdOpts.allowNewerGenerator } : {}
|
|
13803
|
+
});
|
|
13804
|
+
}
|
|
13805
|
+
);
|
|
12789
13806
|
moduleCmd.command("dev").description("Watch module.json + regenerate typed wrappers on every save. Ctrl-C to stop.").option("--dir <path>", "Project root (default: current directory).").option("--debounce-ms <n>", "Filesystem change debounce window (default: 250ms).", parseIntOption).action(async (cmdOpts) => {
|
|
12790
13807
|
process.exitCode = await moduleDevCommand({
|
|
12791
13808
|
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
@@ -12936,7 +13953,7 @@ usesCmd.command("add").description(
|
|
|
12936
13953
|
"Comma-separated operations to declare (create,read,update,delete). Default: read."
|
|
12937
13954
|
).option(
|
|
12938
13955
|
"--purpose <text>",
|
|
12939
|
-
|
|
13956
|
+
`What your module does with these fields, in the admin's words \u2014 shown at bind/consent (up to 500 chars). Be specific: "Reads invoice totals to draw the revenue chart", not "access data".`
|
|
12940
13957
|
).option(
|
|
12941
13958
|
"--no-bind",
|
|
12942
13959
|
"Only write + push the manifest entry; don't attempt to bind it to the source table."
|
|
@@ -13119,6 +14136,9 @@ envCmd.command("export").description(
|
|
|
13119
14136
|
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(
|
|
13120
14137
|
"--exclude <pattern...>",
|
|
13121
14138
|
"Extra exclude patterns (segment-matched) appended to the defaults."
|
|
14139
|
+
).option(
|
|
14140
|
+
"--no-respect-gitignore",
|
|
14141
|
+
"Pack files the project's .gitignore excludes (default: respect it)."
|
|
13122
14142
|
).option(
|
|
13123
14143
|
"--no-wait",
|
|
13124
14144
|
"Enqueue the build and exit immediately (print the build id) instead of polling to completion."
|
|
@@ -13134,6 +14154,9 @@ moduleCmd.command("publish").description("Pack the current directory + upload it
|
|
|
13134
14154
|
...cmdOpts.dryRun !== void 0 ? { dryRun: cmdOpts.dryRun } : {},
|
|
13135
14155
|
...cmdOpts.yes !== void 0 ? { yes: cmdOpts.yes } : {},
|
|
13136
14156
|
...cmdOpts.exclude !== void 0 ? { exclude: cmdOpts.exclude } : {},
|
|
14157
|
+
// commander maps `--no-respect-gitignore` to
|
|
14158
|
+
// `respectGitignore === false`.
|
|
14159
|
+
...cmdOpts.respectGitignore === false ? { noRespectGitignore: true } : {},
|
|
13137
14160
|
// commander maps `--no-wait` to `wait === false`.
|
|
13138
14161
|
...cmdOpts.wait === false ? { noWait: true } : {},
|
|
13139
14162
|
...timeoutSec !== void 0 && Number.isFinite(timeoutSec) ? { timeoutSec } : {},
|
|
@@ -13142,7 +14165,7 @@ moduleCmd.command("publish").description("Pack the current directory + upload it
|
|
|
13142
14165
|
}
|
|
13143
14166
|
);
|
|
13144
14167
|
moduleCmd.command("install [slug]").alias("deploy").description(
|
|
13145
|
-
|
|
14168
|
+
'Install (deploy) a published module into a workspace \u2014 provisions the runtime and prints the public URL. Upgrades/repairs an existing production install idempotently. When only a dev install exists it asks whether to install alongside it (--keep-dev-install) or replace it (--replace-dev-install), rather than silently landing on the dev row. Exits 3 \u2014 "deployed, setup pending" \u2014 when a required `uses` slot is still unbound: the deploy succeeded but the module throws on its first data call until an admin binds it.'
|
|
13146
14169
|
).option("--workspace <slug-or-id>", "Target workspace (default: cached module workspace, config default, or interactive picker).").option("--version <semver>", "Install a specific published version (default: latest).").option("--force", "Proceed even when the upgrade breaks consumer modules (UPGRADE_BREAKS_CONSUMERS).").option(
|
|
13147
14170
|
"--keep-dev-install",
|
|
13148
14171
|
"Install production ALONGSIDE the workspace's kind=local dev install, leaving local dev working. Non-destructive. Backends >= 2026-07-15 keep a separate live-install slot per kind group, so the two coexist."
|
|
@@ -13211,7 +14234,10 @@ moduleCmd.command("query-log").description(
|
|
|
13211
14234
|
}
|
|
13212
14235
|
);
|
|
13213
14236
|
moduleCmd.command("action-log").description(
|
|
13214
|
-
"Read the per-installation server-action audit log. Shows both cross-module and own-module dispatches. Filter by role (caller / provider), slow, since, cross-module-only. `--tail` streams new entries every 5s. Pass
|
|
14237
|
+
"Read the per-installation server-action audit log. Shows both cross-module and own-module dispatches. Filter by role (caller / provider), slow, since, cross-module-only. `--tail` streams new entries every 5s. Pass `--installation <id>` or `--module <slug>`, or run it inside a module project and the slug is read from module.json like every other module command."
|
|
14238
|
+
).option(
|
|
14239
|
+
"--dir <path>",
|
|
14240
|
+
"Project root to read module.json from when neither --installation nor --module is given (default: current directory)."
|
|
13215
14241
|
).option(
|
|
13216
14242
|
"--installation <id>",
|
|
13217
14243
|
"Installation id to inspect. Mutually exclusive with `--module`.",
|
|
@@ -13233,6 +14259,7 @@ moduleCmd.command("action-log").description(
|
|
|
13233
14259
|
return;
|
|
13234
14260
|
}
|
|
13235
14261
|
process.exitCode = await moduleActionLogCommand({
|
|
14262
|
+
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
13236
14263
|
...cmdOpts.installation !== void 0 ? { installation: cmdOpts.installation } : {},
|
|
13237
14264
|
...cmdOpts.module !== void 0 ? { module: cmdOpts.module } : {},
|
|
13238
14265
|
...cmdOpts.role !== void 0 ? { role: cmdOpts.role } : {},
|
|
@@ -13390,7 +14417,7 @@ fieldCmd.command("set-type <table-slug> <field-slug> <new-type>").description(
|
|
|
13390
14417
|
}
|
|
13391
14418
|
);
|
|
13392
14419
|
function getVersion() {
|
|
13393
|
-
return "0.
|
|
14420
|
+
return "0.20.1";
|
|
13394
14421
|
}
|
|
13395
14422
|
function parseIntOption(value) {
|
|
13396
14423
|
const n = Number.parseInt(value, 10);
|