@dashai/cli 0.31.1 → 0.32.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/bin.js +704 -103
- package/dist/bin.js.map +1 -1
- package/package.json +2 -2
package/dist/bin.js
CHANGED
|
@@ -548,7 +548,7 @@ async function introspectWorkspaceTable(_installationId, tableNameOrId, options
|
|
|
548
548
|
const message = `"${ref}" is not a numeric table id. Pass the workspace table's numeric id (find it in the Dashwise UI URL, or via \`GET /api/tables/application/:applicationId\`). Resolving a table by name is not yet supported by this command.`;
|
|
549
549
|
throw new ApiError(0, { code: "NON_NUMERIC_TABLE_REF", message }, message);
|
|
550
550
|
}
|
|
551
|
-
const
|
|
551
|
+
const table2 = await apiRequest(
|
|
552
552
|
"GET",
|
|
553
553
|
`/api/tables/${asNumber}`,
|
|
554
554
|
void 0,
|
|
@@ -568,9 +568,9 @@ async function introspectWorkspaceTable(_installationId, tableNameOrId, options
|
|
|
568
568
|
...f.is_required !== void 0 ? { required: f.is_required } : {}
|
|
569
569
|
}));
|
|
570
570
|
return {
|
|
571
|
-
table_id:
|
|
572
|
-
name:
|
|
573
|
-
slug: slugifyFieldName(
|
|
571
|
+
table_id: table2.id,
|
|
572
|
+
name: table2.name,
|
|
573
|
+
slug: slugifyFieldName(table2.name),
|
|
574
574
|
fields
|
|
575
575
|
};
|
|
576
576
|
}
|
|
@@ -639,6 +639,66 @@ async function getRuntimeLogs(installationId, opts = {}, options = {}) {
|
|
|
639
639
|
options
|
|
640
640
|
);
|
|
641
641
|
}
|
|
642
|
+
async function getModuleSchedules(installationId, options = {}) {
|
|
643
|
+
return apiRequest(
|
|
644
|
+
"GET",
|
|
645
|
+
`/api/modules/installations/${installationId}/schedules`,
|
|
646
|
+
void 0,
|
|
647
|
+
options
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
async function listJobRuns(installationId, opts = {}, options = {}) {
|
|
651
|
+
const params = new URLSearchParams();
|
|
652
|
+
if (opts.kind) params.set("kind", opts.kind);
|
|
653
|
+
if (opts.key) params.set("key", opts.key);
|
|
654
|
+
if (opts.cursor !== void 0) params.set("cursor", String(opts.cursor));
|
|
655
|
+
if (opts.limit !== void 0) params.set("limit", String(opts.limit));
|
|
656
|
+
const qs = params.toString();
|
|
657
|
+
return apiRequest(
|
|
658
|
+
"GET",
|
|
659
|
+
`/api/modules/installations/${installationId}/job-runs${qs ? `?${qs}` : ""}`,
|
|
660
|
+
void 0,
|
|
661
|
+
options
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
async function runModuleSchedule(installationId, key, options = {}) {
|
|
665
|
+
return apiRequest(
|
|
666
|
+
"POST",
|
|
667
|
+
`/api/modules/installations/${installationId}/schedules/${encodeURIComponent(key)}/run`,
|
|
668
|
+
{},
|
|
669
|
+
options
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
async function getModuleWebhooks(installationId, options = {}) {
|
|
673
|
+
return apiRequest(
|
|
674
|
+
"GET",
|
|
675
|
+
`/api/modules/installations/${installationId}/webhooks`,
|
|
676
|
+
void 0,
|
|
677
|
+
options
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
async function listWebhookDeliveries(installationId, opts = {}, options = {}) {
|
|
681
|
+
const params = new URLSearchParams();
|
|
682
|
+
if (opts.key) params.set("key", opts.key);
|
|
683
|
+
if (opts.status) params.set("status", opts.status);
|
|
684
|
+
if (opts.cursor !== void 0) params.set("cursor", String(opts.cursor));
|
|
685
|
+
if (opts.limit !== void 0) params.set("limit", String(opts.limit));
|
|
686
|
+
const qs = params.toString();
|
|
687
|
+
return apiRequest(
|
|
688
|
+
"GET",
|
|
689
|
+
`/api/modules/installations/${installationId}/webhook-deliveries${qs ? `?${qs}` : ""}`,
|
|
690
|
+
void 0,
|
|
691
|
+
options
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
async function redeliverWebhookDelivery(installationId, deliveryId, options = {}) {
|
|
695
|
+
return apiRequest(
|
|
696
|
+
"POST",
|
|
697
|
+
`/api/modules/installations/${installationId}/webhook-deliveries/${deliveryId}/redeliver`,
|
|
698
|
+
{},
|
|
699
|
+
options
|
|
700
|
+
);
|
|
701
|
+
}
|
|
642
702
|
async function getInstallationRuntime(installationId, options = {}) {
|
|
643
703
|
return apiRequest(
|
|
644
704
|
"GET",
|
|
@@ -967,6 +1027,99 @@ var init_manifest_io = __esm({
|
|
|
967
1027
|
"src/lib/manifest-io.ts"() {
|
|
968
1028
|
}
|
|
969
1029
|
});
|
|
1030
|
+
function resolveSdkPackageJson(projectRoot) {
|
|
1031
|
+
let dir = projectRoot;
|
|
1032
|
+
for (; ; ) {
|
|
1033
|
+
const candidate = join(dir, "node_modules", "@dashai", "sdk", "package.json");
|
|
1034
|
+
if (existsSync(candidate)) return candidate;
|
|
1035
|
+
const parent = dirname(dir);
|
|
1036
|
+
if (parent === dir) return null;
|
|
1037
|
+
dir = parent;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
function readInstalledSdk(projectRoot) {
|
|
1041
|
+
const pkgPath = resolveSdkPackageJson(projectRoot);
|
|
1042
|
+
if (pkgPath === null) return null;
|
|
1043
|
+
try {
|
|
1044
|
+
const parsed = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
1045
|
+
if (typeof parsed.version !== "string" || parsed.version.length === 0) return null;
|
|
1046
|
+
return { version: parsed.version };
|
|
1047
|
+
} catch {
|
|
1048
|
+
return null;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
function compareVersions(a, b) {
|
|
1052
|
+
const parse = (v) => {
|
|
1053
|
+
const [core = "", pre] = v.trim().replace(/^[^0-9]*/, "").split("-", 2);
|
|
1054
|
+
const nums = core.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
1055
|
+
while (nums.length < 3) nums.push(0);
|
|
1056
|
+
return [nums.slice(0, 3), pre !== void 0 && pre.length > 0];
|
|
1057
|
+
};
|
|
1058
|
+
const [an, apre] = parse(a);
|
|
1059
|
+
const [bn, bpre] = parse(b);
|
|
1060
|
+
for (let i = 0; i < 3; i++) {
|
|
1061
|
+
const d = (an[i] ?? 0) - (bn[i] ?? 0);
|
|
1062
|
+
if (d !== 0) return d;
|
|
1063
|
+
}
|
|
1064
|
+
if (apre === bpre) return 0;
|
|
1065
|
+
return apre ? -1 : 1;
|
|
1066
|
+
}
|
|
1067
|
+
function satisfiesFloor(version, floor) {
|
|
1068
|
+
return compareVersions(version, floor) >= 0;
|
|
1069
|
+
}
|
|
1070
|
+
function checkSdkFloor(projectRoot, floor) {
|
|
1071
|
+
const installed = readInstalledSdk(projectRoot);
|
|
1072
|
+
if (installed === null) return { ok: true, installed: null, floor };
|
|
1073
|
+
return {
|
|
1074
|
+
ok: satisfiesFloor(installed.version, floor),
|
|
1075
|
+
installed: installed.version,
|
|
1076
|
+
floor
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
function sdkAcceptsSnakeUsesSlugs(version) {
|
|
1080
|
+
return compareVersions(version, LAST_SDK_WITHOUT_SNAKE_USES_SLUGS) > 0;
|
|
1081
|
+
}
|
|
1082
|
+
function multiWordUsesSlots(manifest) {
|
|
1083
|
+
const tables = manifest?.uses?.tables;
|
|
1084
|
+
if (!Array.isArray(tables)) return [];
|
|
1085
|
+
return tables.map((t) => t?.slug).filter((slug) => typeof slug === "string" && slug.includes("_"));
|
|
1086
|
+
}
|
|
1087
|
+
function checkUsesSlotsAgainstSdk(projectRoot, slots) {
|
|
1088
|
+
const multiWord = slots.filter((slug) => slug.includes("_"));
|
|
1089
|
+
if (multiWord.length === 0) return null;
|
|
1090
|
+
const installed = readInstalledSdk(projectRoot);
|
|
1091
|
+
if (installed === null || sdkAcceptsSnakeUsesSlugs(installed.version)) return null;
|
|
1092
|
+
return { installed: installed.version, slots: multiWord };
|
|
1093
|
+
}
|
|
1094
|
+
var USES_SDK_FLOOR, SCAFFOLD_SDK_RANGE, LAST_SDK_WITHOUT_SNAKE_USES_SLUGS;
|
|
1095
|
+
var init_sdk_version = __esm({
|
|
1096
|
+
"src/lib/sdk-version.ts"() {
|
|
1097
|
+
USES_SDK_FLOOR = "2.5.0";
|
|
1098
|
+
SCAFFOLD_SDK_RANGE = "^2.5.0";
|
|
1099
|
+
LAST_SDK_WITHOUT_SNAKE_USES_SLUGS = "2.14.0";
|
|
1100
|
+
}
|
|
1101
|
+
});
|
|
1102
|
+
|
|
1103
|
+
// src/lib/uses-slot-sdk-warning.ts
|
|
1104
|
+
function warnIfSdkRefusesUsesSlots(projectRoot, slots, alternative) {
|
|
1105
|
+
const check = checkUsesSlotsAgainstSdk(projectRoot, slots);
|
|
1106
|
+
if (check === null) return false;
|
|
1107
|
+
const one = check.slots.length === 1;
|
|
1108
|
+
const names = check.slots.map((s) => code(s)).join(", ");
|
|
1109
|
+
warn(
|
|
1110
|
+
`${names} ${one ? "is a multi-word uses slot" : "are multi-word uses slots"}, and the installed ${code(`@dashai/sdk ${check.installed}`)} refuses ${one ? "it" : "them"}: every import from ${code("@dashai/generated")} throws, including your own tables.`
|
|
1111
|
+
);
|
|
1112
|
+
info(
|
|
1113
|
+
`Upgrade to the first SDK with the fix (any release after ${LAST_SDK_WITHOUT_SNAKE_USES_SLUGS}): ${code("npm install @dashai/sdk@latest")}.${alternative ? ` ${alternative}` : ""}`
|
|
1114
|
+
);
|
|
1115
|
+
return true;
|
|
1116
|
+
}
|
|
1117
|
+
var init_uses_slot_sdk_warning = __esm({
|
|
1118
|
+
"src/lib/uses-slot-sdk-warning.ts"() {
|
|
1119
|
+
init_output();
|
|
1120
|
+
init_sdk_version();
|
|
1121
|
+
}
|
|
1122
|
+
});
|
|
970
1123
|
|
|
971
1124
|
// src/lib/manifest-merge.ts
|
|
972
1125
|
function tablesBySlug(manifest) {
|
|
@@ -1017,9 +1170,9 @@ function mergeRemoteManifest(local, remote) {
|
|
|
1017
1170
|
}
|
|
1018
1171
|
return { merged, preserved };
|
|
1019
1172
|
}
|
|
1020
|
-
function fieldSlugs(
|
|
1173
|
+
function fieldSlugs(table2) {
|
|
1021
1174
|
const set = /* @__PURE__ */ new Set();
|
|
1022
|
-
const fields =
|
|
1175
|
+
const fields = table2?.fields;
|
|
1023
1176
|
if (!Array.isArray(fields)) return set;
|
|
1024
1177
|
for (const f of fields) {
|
|
1025
1178
|
if (f && typeof f === "object" && typeof f.slug === "string") {
|
|
@@ -1293,6 +1446,10 @@ async function devCommand(options) {
|
|
|
1293
1446
|
requestOptions: { baseUrl: apiUrl, auth: { token: apiToken } }
|
|
1294
1447
|
});
|
|
1295
1448
|
}
|
|
1449
|
+
try {
|
|
1450
|
+
warnIfSdkRefusesUsesSlots(projectRoot, multiWordUsesSlots(readManifest(projectRoot)));
|
|
1451
|
+
} catch {
|
|
1452
|
+
}
|
|
1296
1453
|
const children = [];
|
|
1297
1454
|
if (!options.nextOnly) {
|
|
1298
1455
|
const sdkChild = spawnSdkWatcher(projectRoot, childEnv);
|
|
@@ -1601,6 +1758,8 @@ var init_dev = __esm({
|
|
|
1601
1758
|
init_config();
|
|
1602
1759
|
init_declared_secrets();
|
|
1603
1760
|
init_manifest_io();
|
|
1761
|
+
init_sdk_version();
|
|
1762
|
+
init_uses_slot_sdk_warning();
|
|
1604
1763
|
init_manifest_poll();
|
|
1605
1764
|
init_output();
|
|
1606
1765
|
init_redact();
|
|
@@ -1613,62 +1772,6 @@ var init_dev = __esm({
|
|
|
1613
1772
|
PORT_PROBE_LIMIT = 10;
|
|
1614
1773
|
}
|
|
1615
1774
|
});
|
|
1616
|
-
function resolveSdkPackageJson(projectRoot) {
|
|
1617
|
-
let dir = projectRoot;
|
|
1618
|
-
for (; ; ) {
|
|
1619
|
-
const candidate = join(dir, "node_modules", "@dashai", "sdk", "package.json");
|
|
1620
|
-
if (existsSync(candidate)) return candidate;
|
|
1621
|
-
const parent = dirname(dir);
|
|
1622
|
-
if (parent === dir) return null;
|
|
1623
|
-
dir = parent;
|
|
1624
|
-
}
|
|
1625
|
-
}
|
|
1626
|
-
function readInstalledSdk(projectRoot) {
|
|
1627
|
-
const pkgPath = resolveSdkPackageJson(projectRoot);
|
|
1628
|
-
if (pkgPath === null) return null;
|
|
1629
|
-
try {
|
|
1630
|
-
const parsed = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
1631
|
-
if (typeof parsed.version !== "string" || parsed.version.length === 0) return null;
|
|
1632
|
-
return { version: parsed.version };
|
|
1633
|
-
} catch {
|
|
1634
|
-
return null;
|
|
1635
|
-
}
|
|
1636
|
-
}
|
|
1637
|
-
function compareVersions(a, b) {
|
|
1638
|
-
const parse = (v) => {
|
|
1639
|
-
const [core = "", pre] = v.trim().replace(/^[^0-9]*/, "").split("-", 2);
|
|
1640
|
-
const nums = core.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
1641
|
-
while (nums.length < 3) nums.push(0);
|
|
1642
|
-
return [nums.slice(0, 3), pre !== void 0 && pre.length > 0];
|
|
1643
|
-
};
|
|
1644
|
-
const [an, apre] = parse(a);
|
|
1645
|
-
const [bn, bpre] = parse(b);
|
|
1646
|
-
for (let i = 0; i < 3; i++) {
|
|
1647
|
-
const d = (an[i] ?? 0) - (bn[i] ?? 0);
|
|
1648
|
-
if (d !== 0) return d;
|
|
1649
|
-
}
|
|
1650
|
-
if (apre === bpre) return 0;
|
|
1651
|
-
return apre ? -1 : 1;
|
|
1652
|
-
}
|
|
1653
|
-
function satisfiesFloor(version, floor) {
|
|
1654
|
-
return compareVersions(version, floor) >= 0;
|
|
1655
|
-
}
|
|
1656
|
-
function checkSdkFloor(projectRoot, floor) {
|
|
1657
|
-
const installed = readInstalledSdk(projectRoot);
|
|
1658
|
-
if (installed === null) return { ok: true, installed: null, floor };
|
|
1659
|
-
return {
|
|
1660
|
-
ok: satisfiesFloor(installed.version, floor),
|
|
1661
|
-
installed: installed.version,
|
|
1662
|
-
floor
|
|
1663
|
-
};
|
|
1664
|
-
}
|
|
1665
|
-
var USES_SDK_FLOOR, SCAFFOLD_SDK_RANGE;
|
|
1666
|
-
var init_sdk_version = __esm({
|
|
1667
|
-
"src/lib/sdk-version.ts"() {
|
|
1668
|
-
USES_SDK_FLOOR = "2.5.0";
|
|
1669
|
-
SCAFFOLD_SDK_RANGE = "^2.5.0";
|
|
1670
|
-
}
|
|
1671
|
-
});
|
|
1672
1775
|
async function selectWorkspace(opts) {
|
|
1673
1776
|
const workspaces = await apiRequest(
|
|
1674
1777
|
"GET",
|
|
@@ -14512,17 +14615,17 @@ function referenceFieldWarnings(manifest) {
|
|
|
14512
14615
|
const tables = manifest?.owns?.tables;
|
|
14513
14616
|
if (!Array.isArray(tables)) return [];
|
|
14514
14617
|
const warnings = [];
|
|
14515
|
-
for (const
|
|
14516
|
-
if (typeof
|
|
14517
|
-
for (const field of
|
|
14618
|
+
for (const table2 of tables) {
|
|
14619
|
+
if (typeof table2?.slug !== "string" || !Array.isArray(table2.fields)) continue;
|
|
14620
|
+
for (const field of table2.fields) {
|
|
14518
14621
|
if (typeof field?.slug !== "string" || typeof field.type !== "string") continue;
|
|
14519
14622
|
if (field.slug === "id" || !REFERENCE_NAME.test(field.slug)) continue;
|
|
14520
14623
|
if (!PLAIN_TYPES.has(field.type)) continue;
|
|
14521
14624
|
warnings.push({
|
|
14522
|
-
table:
|
|
14625
|
+
table: table2.slug,
|
|
14523
14626
|
field: field.slug,
|
|
14524
14627
|
type: field.type,
|
|
14525
|
-
message: `${
|
|
14628
|
+
message: `${table2.slug}.${field.slug} is a ${field.type} field named like a reference. If it points at a row in Dashwise, use a link_row field: plain ids break silently when data is copied to another installation. Ignore this for ids from outside systems.`
|
|
14526
14629
|
});
|
|
14527
14630
|
}
|
|
14528
14631
|
}
|
|
@@ -15156,9 +15259,9 @@ function printApplySummary(applied) {
|
|
|
15156
15259
|
const message = strProp(warning, "message");
|
|
15157
15260
|
if (!message) continue;
|
|
15158
15261
|
const warningCode = strProp(warning, "code");
|
|
15159
|
-
const
|
|
15262
|
+
const table2 = strProp(warning, "table");
|
|
15160
15263
|
const field = strProp(warning, "field");
|
|
15161
|
-
const location =
|
|
15264
|
+
const location = table2 ? `${table2}${field ? `.${field}` : ""}` : field;
|
|
15162
15265
|
const prefix = [warningCode, location].filter(Boolean).join(" ");
|
|
15163
15266
|
warn(`${prefix ? `${code(prefix)}: ` : ""}${message}`);
|
|
15164
15267
|
}
|
|
@@ -15277,8 +15380,8 @@ var init_push = __esm({
|
|
|
15277
15380
|
});
|
|
15278
15381
|
|
|
15279
15382
|
// ../sdk/dist/index.js
|
|
15280
|
-
function primaryDeclarationIssues(
|
|
15281
|
-
const invalid =
|
|
15383
|
+
function primaryDeclarationIssues(table2) {
|
|
15384
|
+
const invalid = table2.fields.findIndex(
|
|
15282
15385
|
(field2) => field2.primary !== void 0 && field2.primary !== true
|
|
15283
15386
|
);
|
|
15284
15387
|
if (invalid !== -1) {
|
|
@@ -15286,17 +15389,17 @@ function primaryDeclarationIssues(table) {
|
|
|
15286
15389
|
{ path: ["fields", invalid, "primary"], message: "primary must be literal true or omitted" }
|
|
15287
15390
|
];
|
|
15288
15391
|
}
|
|
15289
|
-
const marked =
|
|
15392
|
+
const marked = table2.fields.filter((field2) => field2.primary === true);
|
|
15290
15393
|
if (marked.length !== 1) {
|
|
15291
15394
|
return [
|
|
15292
15395
|
{
|
|
15293
15396
|
path: ["fields"],
|
|
15294
|
-
message: `Table ${
|
|
15397
|
+
message: `Table ${table2.slug}: mark exactly one field as primary: true (the row's label in links, pickers, titles)`
|
|
15295
15398
|
}
|
|
15296
15399
|
];
|
|
15297
15400
|
}
|
|
15298
15401
|
const field = marked[0];
|
|
15299
|
-
const path = ["fields",
|
|
15402
|
+
const path = ["fields", table2.fields.indexOf(field), "primary"];
|
|
15300
15403
|
if (field.type === "formula") {
|
|
15301
15404
|
return [{ path, message: "formula fields cannot be the primary of a module table yet" }];
|
|
15302
15405
|
}
|
|
@@ -15304,14 +15407,14 @@ function primaryDeclarationIssues(table) {
|
|
|
15304
15407
|
return [
|
|
15305
15408
|
{
|
|
15306
15409
|
path,
|
|
15307
|
-
message: `Table ${
|
|
15410
|
+
message: `Table ${table2.slug}: mark a field that can be the primary with primary: true (${field.slug} of type ${field.type} cannot be primary)`
|
|
15308
15411
|
}
|
|
15309
15412
|
];
|
|
15310
15413
|
}
|
|
15311
15414
|
return [];
|
|
15312
15415
|
}
|
|
15313
|
-
function weakPrimaryWarning(
|
|
15314
|
-
const field =
|
|
15416
|
+
function weakPrimaryWarning(table2) {
|
|
15417
|
+
const field = table2.fields.find((field2) => field2.primary === true);
|
|
15315
15418
|
if (!field || !WEAK_PRIMARY_TYPES.has(field.type)) return void 0;
|
|
15316
15419
|
return `${field.slug} is a weak row label; prefer a short text field such as a name or title`;
|
|
15317
15420
|
}
|
|
@@ -15529,14 +15632,14 @@ async function tableCreateCommand(slug, options) {
|
|
|
15529
15632
|
}
|
|
15530
15633
|
seen.add(f.slug);
|
|
15531
15634
|
}
|
|
15532
|
-
const
|
|
15533
|
-
const issues = primaryDeclarationIssues(
|
|
15635
|
+
const table2 = { slug, fields };
|
|
15636
|
+
const issues = primaryDeclarationIssues(table2);
|
|
15534
15637
|
if (issues.length > 0) {
|
|
15535
15638
|
for (const issue of issues) fail(issue.message);
|
|
15536
15639
|
info("Mark the row name explicitly, e.g. --field title:text:primary.");
|
|
15537
15640
|
return 1;
|
|
15538
15641
|
}
|
|
15539
|
-
const primaryWarning = weakPrimaryWarning(
|
|
15642
|
+
const primaryWarning = weakPrimaryWarning(table2);
|
|
15540
15643
|
if (primaryWarning) warn(primaryWarning);
|
|
15541
15644
|
let ctx;
|
|
15542
15645
|
try {
|
|
@@ -23361,13 +23464,13 @@ async function buildFieldMap(ctx, candidate, overrides) {
|
|
|
23361
23464
|
if (overrideKeys.length === 0) {
|
|
23362
23465
|
return Object.keys(candidate.auto_field_map).length > 0 ? { ...candidate.auto_field_map } : void 0;
|
|
23363
23466
|
}
|
|
23364
|
-
const
|
|
23467
|
+
const table2 = await introspectWorkspaceTable(
|
|
23365
23468
|
ctx.installationId,
|
|
23366
23469
|
String(candidate.table_id),
|
|
23367
23470
|
ctx.requestOptions
|
|
23368
23471
|
);
|
|
23369
23472
|
const byName = /* @__PURE__ */ new Map();
|
|
23370
|
-
for (const f of
|
|
23473
|
+
for (const f of table2.fields) {
|
|
23371
23474
|
byName.set(f.slug, f.id);
|
|
23372
23475
|
if (f.name) byName.set(f.name, f.id);
|
|
23373
23476
|
}
|
|
@@ -23375,9 +23478,9 @@ async function buildFieldMap(ctx, candidate, overrides) {
|
|
|
23375
23478
|
for (const [usesField, physicalName] of Object.entries(overrides)) {
|
|
23376
23479
|
const id = byName.get(physicalName);
|
|
23377
23480
|
if (id === void 0) {
|
|
23378
|
-
const available =
|
|
23481
|
+
const available = table2.fields.map((f) => f.name && f.name !== f.slug ? `${f.slug} (${f.name})` : f.slug).join(", ");
|
|
23379
23482
|
throw new Error(
|
|
23380
|
-
`--map ${usesField}=${physicalName}: table "${
|
|
23483
|
+
`--map ${usesField}=${physicalName}: table "${table2.name}" has no field named "${physicalName}". Available: ${available}.`
|
|
23381
23484
|
);
|
|
23382
23485
|
}
|
|
23383
23486
|
merged[usesField] = id;
|
|
@@ -23584,6 +23687,7 @@ init_config();
|
|
|
23584
23687
|
init_manifest_io();
|
|
23585
23688
|
init_uses_types();
|
|
23586
23689
|
init_sdk_version();
|
|
23690
|
+
init_uses_slot_sdk_warning();
|
|
23587
23691
|
init_output();
|
|
23588
23692
|
async function usesAddCommand(options) {
|
|
23589
23693
|
if (!options.fromTable) {
|
|
@@ -23616,9 +23720,9 @@ async function usesAddCommand(options) {
|
|
|
23616
23720
|
}
|
|
23617
23721
|
const scopeExit = await assertTableInInstallWorkspace(ctx, options.fromTable);
|
|
23618
23722
|
if (scopeExit !== 0) return scopeExit;
|
|
23619
|
-
let
|
|
23723
|
+
let table2;
|
|
23620
23724
|
try {
|
|
23621
|
-
|
|
23725
|
+
table2 = await introspectWorkspaceTable(
|
|
23622
23726
|
ctx.installationId,
|
|
23623
23727
|
options.fromTable,
|
|
23624
23728
|
ctx.requestOptions
|
|
@@ -23626,27 +23730,32 @@ async function usesAddCommand(options) {
|
|
|
23626
23730
|
} catch (err) {
|
|
23627
23731
|
return surfaceIntrospectError(err, options.fromTable);
|
|
23628
23732
|
}
|
|
23629
|
-
const slotSlug = options.slug ?? slugifyUsesName(
|
|
23733
|
+
const slotSlug = options.slug ?? slugifyUsesName(table2.name);
|
|
23630
23734
|
if (!isUsesSlug(slotSlug)) {
|
|
23631
23735
|
fail(
|
|
23632
23736
|
`Slot slug ${code(slotSlug || "(empty)")} doesn't match ${code(USES_SLUG_RULE)}. Pass ${code("--slug <snake_case_slug>")} explicitly.`
|
|
23633
23737
|
);
|
|
23634
23738
|
return 1;
|
|
23635
23739
|
}
|
|
23636
|
-
|
|
23740
|
+
warnIfSdkRefusesUsesSlots(
|
|
23741
|
+
resolve(options.dir ?? process.cwd()),
|
|
23742
|
+
[slotSlug],
|
|
23743
|
+
`Or give the slot a one-word slug: ${code("--slug <slug>")}.`
|
|
23744
|
+
);
|
|
23745
|
+
const { fields, skipped } = generateFields(table2.fields);
|
|
23637
23746
|
if (fields.length === 0) {
|
|
23638
23747
|
fail(
|
|
23639
|
-
`Table ${code(
|
|
23748
|
+
`Table ${code(table2.name)} has no fields representable as a v1 ${code("uses")} contract (all are link_row / formula / ai / file). Nothing to declare.`
|
|
23640
23749
|
);
|
|
23641
23750
|
return 1;
|
|
23642
23751
|
}
|
|
23643
23752
|
const badFields = fields.filter((f) => !isUsesSlug(f.slug));
|
|
23644
23753
|
if (badFields.length > 0) {
|
|
23645
23754
|
fail(
|
|
23646
|
-
`${badFields.length} field name(s) on ${code(
|
|
23755
|
+
`${badFields.length} field name(s) on ${code(table2.name)} don't fold into a legal ${code("uses")} field slug (${code(USES_SLUG_RULE)}):`
|
|
23647
23756
|
);
|
|
23648
23757
|
for (const f of badFields) {
|
|
23649
|
-
const physical =
|
|
23758
|
+
const physical = table2.fields.find((p) => p.slug === f.slug);
|
|
23650
23759
|
const name = physical?.name ?? f.slug;
|
|
23651
23760
|
out(` ${dim("-")} ${code(name)} \u2192 ${code(f.slug || "(empty)")}`);
|
|
23652
23761
|
}
|
|
@@ -23660,10 +23769,10 @@ async function usesAddCommand(options) {
|
|
|
23660
23769
|
const collidingSlugs = [...slugCounts.entries()].filter(([, n]) => n > 1).map(([slug]) => slug);
|
|
23661
23770
|
if (collidingSlugs.length > 0) {
|
|
23662
23771
|
fail(
|
|
23663
|
-
`${collidingSlugs.length} generated ${code("uses")} field slug(s) on ${code(
|
|
23772
|
+
`${collidingSlugs.length} generated ${code("uses")} field slug(s) on ${code(table2.name)} collide \u2014 the backend refuses a contract with a duplicate field slug:`
|
|
23664
23773
|
);
|
|
23665
23774
|
for (const slug of collidingSlugs) {
|
|
23666
|
-
const names =
|
|
23775
|
+
const names = table2.fields.filter((p) => p.slug === slug && abstractTypeForPhysical(p.type) !== null).map((p) => p.name ?? p.slug);
|
|
23667
23776
|
out(` ${dim("-")} ${names.map((n) => code(n)).join(" + ")} \u2192 ${code(slug)}`);
|
|
23668
23777
|
}
|
|
23669
23778
|
info(
|
|
@@ -23679,7 +23788,7 @@ async function usesAddCommand(options) {
|
|
|
23679
23788
|
};
|
|
23680
23789
|
if (options.purpose) slot.purpose = options.purpose;
|
|
23681
23790
|
info(
|
|
23682
|
-
`Generated a ${code("uses")} contract ${code(slotSlug)} from table ${code(
|
|
23791
|
+
`Generated a ${code("uses")} contract ${code(slotSlug)} from table ${code(table2.name)} ${dim(`(id ${table2.table_id})`)}:`
|
|
23683
23792
|
);
|
|
23684
23793
|
out(` ${dim("ops:")} ${ops.join(", ")}`);
|
|
23685
23794
|
for (const f of fields) {
|
|
@@ -23725,24 +23834,24 @@ async function usesAddCommand(options) {
|
|
|
23725
23834
|
);
|
|
23726
23835
|
if (!pushed.ok) {
|
|
23727
23836
|
info(
|
|
23728
|
-
`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 ${
|
|
23837
|
+
`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 ${table2.table_id}`)}.`
|
|
23729
23838
|
);
|
|
23730
23839
|
return 1;
|
|
23731
23840
|
}
|
|
23732
23841
|
if (options.noBind) {
|
|
23733
23842
|
info(
|
|
23734
|
-
`Skipped auto-bind (${code("--no-bind")}). Connect it when ready: ${code(`dashwise bind ${slotSlug} --table ${
|
|
23843
|
+
`Skipped auto-bind (${code("--no-bind")}). Connect it when ready: ${code(`dashwise bind ${slotSlug} --table ${table2.table_id}`)}.`
|
|
23735
23844
|
);
|
|
23736
23845
|
return 0;
|
|
23737
23846
|
}
|
|
23738
|
-
info(`Binding ${code(slotSlug)} to ${code(
|
|
23847
|
+
info(`Binding ${code(slotSlug)} to ${code(table2.name)}\u2026`);
|
|
23739
23848
|
const bindExit = await bindCommand(slotSlug, {
|
|
23740
23849
|
...ctx.dir !== void 0 ? { dir: ctx.dir } : {},
|
|
23741
|
-
table: String(
|
|
23850
|
+
table: String(table2.table_id)
|
|
23742
23851
|
});
|
|
23743
23852
|
if (bindExit !== 0) {
|
|
23744
23853
|
warn(
|
|
23745
|
-
`The ${code("uses")} slot was declared + pushed, but the auto-bind didn't complete. Retry with ${code(`dashwise bind ${slotSlug} --table ${
|
|
23854
|
+
`The ${code("uses")} slot was declared + pushed, but the auto-bind didn't complete. Retry with ${code(`dashwise bind ${slotSlug} --table ${table2.table_id}`)}.`
|
|
23746
23855
|
);
|
|
23747
23856
|
return bindExit;
|
|
23748
23857
|
}
|
|
@@ -25889,7 +25998,10 @@ async function moduleLogsCommand(slugArg, options) {
|
|
|
25889
25998
|
}
|
|
25890
25999
|
return runOnce3(installationId, label, options, since, auth);
|
|
25891
26000
|
}
|
|
25892
|
-
async function resolveInstallationFromSlug(slugArg, options, cfg, auth
|
|
26001
|
+
async function resolveInstallationFromSlug(slugArg, options, cfg, auth, wording = {
|
|
26002
|
+
command: "module logs",
|
|
26003
|
+
purpose: "read logs from"
|
|
26004
|
+
}) {
|
|
25893
26005
|
let slug = slugArg;
|
|
25894
26006
|
if (!slug) {
|
|
25895
26007
|
try {
|
|
@@ -25902,7 +26014,7 @@ async function resolveInstallationFromSlug(slugArg, options, cfg, auth) {
|
|
|
25902
26014
|
}
|
|
25903
26015
|
if (!slug) {
|
|
25904
26016
|
fail(
|
|
25905
|
-
`No module slug \u2014 pass one (${code(
|
|
26017
|
+
`No module slug \u2014 pass one (${code(`dashwise ${wording.command} <slug>`)}), run from a directory with a valid module.json, or target an install directly with ${code("--install <id>")}.`
|
|
25906
26018
|
);
|
|
25907
26019
|
return 1;
|
|
25908
26020
|
}
|
|
@@ -25944,7 +26056,7 @@ async function resolveInstallationFromSlug(slugArg, options, cfg, auth) {
|
|
|
25944
26056
|
}
|
|
25945
26057
|
if (!row) {
|
|
25946
26058
|
info(
|
|
25947
|
-
`${code(modSlug)} is not installed in workspace ${code(workspace2.slug)} \u2014 nothing to
|
|
26059
|
+
`${code(modSlug)} is not installed in workspace ${code(workspace2.slug)} \u2014 nothing to ${wording.purpose}. Deploy it with ${code(`dashwise module install ${modSlug} --workspace ${workspace2.slug}`)}.`
|
|
25948
26060
|
);
|
|
25949
26061
|
return 0;
|
|
25950
26062
|
}
|
|
@@ -26137,6 +26249,432 @@ async function sleep3(ms, aborted) {
|
|
|
26137
26249
|
}
|
|
26138
26250
|
}
|
|
26139
26251
|
|
|
26252
|
+
// src/commands/jobs.ts
|
|
26253
|
+
init_api_client();
|
|
26254
|
+
init_config();
|
|
26255
|
+
init_output();
|
|
26256
|
+
var KINDS = ["schedule", "webhook", "manual", "migration", "replay"];
|
|
26257
|
+
var CONTROL = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g;
|
|
26258
|
+
function safeExcerpt(text4) {
|
|
26259
|
+
return text4 === null ? null : text4.replace(CONTROL, " ");
|
|
26260
|
+
}
|
|
26261
|
+
function safeRun(run) {
|
|
26262
|
+
return run ? { ...run, error_excerpt: safeExcerpt(run.error_excerpt) } : run;
|
|
26263
|
+
}
|
|
26264
|
+
var KEY = /^[a-z0-9][a-z0-9-]{0,39}$/;
|
|
26265
|
+
async function resolveJobsTarget(slugArg, options, command, purpose = "run jobs on") {
|
|
26266
|
+
if (!isLoggedIn()) {
|
|
26267
|
+
fail(`Not logged in. Run ${code("dashwise login")} first.`);
|
|
26268
|
+
return 1;
|
|
26269
|
+
}
|
|
26270
|
+
const cfg = readConfig();
|
|
26271
|
+
if (!cfg) {
|
|
26272
|
+
fail(`CLI config missing. Run ${code("dashwise login")}.`);
|
|
26273
|
+
return 1;
|
|
26274
|
+
}
|
|
26275
|
+
const auth = { baseUrl: resolveApiUrl(), auth: { token: cfg.token } };
|
|
26276
|
+
if (options.install !== void 0) {
|
|
26277
|
+
if (!Number.isInteger(options.install) || options.install <= 0) {
|
|
26278
|
+
fail(`--install expects a positive integer installation id (got "${options.install}").`);
|
|
26279
|
+
return 1;
|
|
26280
|
+
}
|
|
26281
|
+
return { installationId: options.install, label: `install #${options.install}`, auth };
|
|
26282
|
+
}
|
|
26283
|
+
const resolved = await resolveInstallationFromSlug(slugArg, options, cfg, auth, {
|
|
26284
|
+
command,
|
|
26285
|
+
purpose
|
|
26286
|
+
});
|
|
26287
|
+
if (typeof resolved === "number") return resolved;
|
|
26288
|
+
return { ...resolved, auth };
|
|
26289
|
+
}
|
|
26290
|
+
async function jobsListCommand(slugArg, options) {
|
|
26291
|
+
const t = await resolveJobsTarget(slugArg, options, "jobs list");
|
|
26292
|
+
if (typeof t === "number") return t;
|
|
26293
|
+
let body;
|
|
26294
|
+
try {
|
|
26295
|
+
body = await getModuleSchedules(t.installationId, t.auth);
|
|
26296
|
+
} catch (err) {
|
|
26297
|
+
return mapJobsError(err, options.json, "read the schedules");
|
|
26298
|
+
}
|
|
26299
|
+
if (options.json) {
|
|
26300
|
+
out(
|
|
26301
|
+
JSON.stringify({
|
|
26302
|
+
...body,
|
|
26303
|
+
schedules: body.schedules.map((s) => ({ ...s, last_run: safeRun(s.last_run) }))
|
|
26304
|
+
})
|
|
26305
|
+
);
|
|
26306
|
+
return 0;
|
|
26307
|
+
}
|
|
26308
|
+
if (body.schedules.length === 0) {
|
|
26309
|
+
info(`The installed version of ${t.label} declares no schedules.`);
|
|
26310
|
+
return 0;
|
|
26311
|
+
}
|
|
26312
|
+
if (!body.enabled) {
|
|
26313
|
+
warn("Schedules do not run on this platform yet (not enabled, or no job queue). Manual runs may still work.");
|
|
26314
|
+
} else if (!body.active) {
|
|
26315
|
+
warn("This installation does not run its schedules now (offline, held, without a runtime, or on a Workbench).");
|
|
26316
|
+
}
|
|
26317
|
+
const rows = body.schedules.map((s) => [
|
|
26318
|
+
s.key,
|
|
26319
|
+
s.interval ? `every ${s.interval}` : `${s.pattern} UTC`,
|
|
26320
|
+
s.route,
|
|
26321
|
+
s.next_run_at ? formatTs2(s.next_run_at) : "\u2014",
|
|
26322
|
+
s.last_run ? `${colorStatus(s.last_run.status)} ${dim(formatTs2(s.last_run.created_at))}` : dim("never"),
|
|
26323
|
+
s.running ? pc.yellow("running") : ""
|
|
26324
|
+
]);
|
|
26325
|
+
table(["KEY", "SCHEDULE", "ROUTE", "NEXT RUN", "LAST RUN", ""], rows);
|
|
26326
|
+
out("");
|
|
26327
|
+
info(`${body.schedules.length} ${body.schedules.length === 1 ? "schedule" : "schedules"} (${t.label}).`);
|
|
26328
|
+
return 0;
|
|
26329
|
+
}
|
|
26330
|
+
async function jobsRunsCommand(slugArg, options) {
|
|
26331
|
+
if (options.kind !== void 0 && !KINDS.includes(options.kind)) {
|
|
26332
|
+
fail(`--kind must be one of ${KINDS.join(", ")} (got "${options.kind}").`);
|
|
26333
|
+
return 1;
|
|
26334
|
+
}
|
|
26335
|
+
if (options.key !== void 0 && !KEY.test(options.key)) {
|
|
26336
|
+
fail(`--key must be a schedule key like ${code("lease-sweep")} (got "${options.key}").`);
|
|
26337
|
+
return 1;
|
|
26338
|
+
}
|
|
26339
|
+
if (options.limit !== void 0 && (!Number.isInteger(options.limit) || options.limit < 1 || options.limit > 200)) {
|
|
26340
|
+
fail(`--limit must be between 1 and 200 (got "${options.limit}").`);
|
|
26341
|
+
return 1;
|
|
26342
|
+
}
|
|
26343
|
+
const t = await resolveJobsTarget(slugArg, options, "jobs runs");
|
|
26344
|
+
if (typeof t === "number") return t;
|
|
26345
|
+
let body;
|
|
26346
|
+
try {
|
|
26347
|
+
body = await listJobRuns(
|
|
26348
|
+
t.installationId,
|
|
26349
|
+
{
|
|
26350
|
+
...options.kind ? { kind: options.kind } : {},
|
|
26351
|
+
...options.key ? { key: options.key } : {},
|
|
26352
|
+
...options.before !== void 0 ? { cursor: options.before } : {},
|
|
26353
|
+
...options.limit !== void 0 ? { limit: options.limit } : {}
|
|
26354
|
+
},
|
|
26355
|
+
t.auth
|
|
26356
|
+
);
|
|
26357
|
+
} catch (err) {
|
|
26358
|
+
return mapJobsError(err, options.json, "read the run log");
|
|
26359
|
+
}
|
|
26360
|
+
if (options.json) {
|
|
26361
|
+
out(JSON.stringify({ ...body, runs: body.runs.map((r) => safeRun(r)) }));
|
|
26362
|
+
return 0;
|
|
26363
|
+
}
|
|
26364
|
+
if (body.runs.length === 0) {
|
|
26365
|
+
info(`No runs for ${t.label}${options.key ? ` (${options.key})` : ""}.`);
|
|
26366
|
+
return 0;
|
|
26367
|
+
}
|
|
26368
|
+
table(
|
|
26369
|
+
["TIME", "KEY", "KIND", "TRY", "STATUS", "HTTP", "MS", "ERROR"],
|
|
26370
|
+
body.runs.map((r) => [
|
|
26371
|
+
formatTs2(r.created_at),
|
|
26372
|
+
r.key,
|
|
26373
|
+
r.kind,
|
|
26374
|
+
String(r.attempt),
|
|
26375
|
+
colorStatus(r.status),
|
|
26376
|
+
r.http_status === null ? "" : String(r.http_status),
|
|
26377
|
+
r.duration_ms === null ? "" : String(r.duration_ms),
|
|
26378
|
+
r.error_code ?? ""
|
|
26379
|
+
])
|
|
26380
|
+
);
|
|
26381
|
+
out("");
|
|
26382
|
+
info(
|
|
26383
|
+
`${body.runs.length} ${body.runs.length === 1 ? "run" : "runs"} (${t.label}).` + (body.next_cursor !== null ? ` Older: ${code(`--before ${body.next_cursor}`)}.` : "")
|
|
26384
|
+
);
|
|
26385
|
+
return 0;
|
|
26386
|
+
}
|
|
26387
|
+
async function jobsRunCommand(key, slugArg, options) {
|
|
26388
|
+
if (!KEY.test(key)) {
|
|
26389
|
+
fail(`"${key}" is not a schedule key (expected ${KEY.source}).`);
|
|
26390
|
+
return 1;
|
|
26391
|
+
}
|
|
26392
|
+
const t = await resolveJobsTarget(slugArg, options, `jobs run ${key}`);
|
|
26393
|
+
if (typeof t === "number") return t;
|
|
26394
|
+
let body;
|
|
26395
|
+
try {
|
|
26396
|
+
body = await runModuleSchedule(t.installationId, key, t.auth);
|
|
26397
|
+
} catch (err) {
|
|
26398
|
+
return mapJobsError(err, options.json, `run ${key}`);
|
|
26399
|
+
}
|
|
26400
|
+
if (options.json) {
|
|
26401
|
+
out(JSON.stringify({ ...body, run: safeRun(body.run) }));
|
|
26402
|
+
return body.run && body.run.status !== "ok" ? 2 : 0;
|
|
26403
|
+
}
|
|
26404
|
+
if (body.queued) {
|
|
26405
|
+
success(`Queued a run of ${code(key)} on ${t.label} (invocation ${body.invocation_id}).`);
|
|
26406
|
+
info(`Follow it with ${code(`dashwise jobs runs --key ${key}`)}.`);
|
|
26407
|
+
return 0;
|
|
26408
|
+
}
|
|
26409
|
+
const run = body.run;
|
|
26410
|
+
if (!run) {
|
|
26411
|
+
warn(`The run of ${code(key)} left no log row (invocation ${body.invocation_id}).`);
|
|
26412
|
+
return 2;
|
|
26413
|
+
}
|
|
26414
|
+
const detail = [
|
|
26415
|
+
run.http_status !== null ? `HTTP ${run.http_status}` : null,
|
|
26416
|
+
run.duration_ms !== null ? `${run.duration_ms} ms` : null,
|
|
26417
|
+
run.error_code
|
|
26418
|
+
].filter(Boolean).join(", ");
|
|
26419
|
+
if (run.status === "ok") {
|
|
26420
|
+
success(`Ran ${code(key)} on ${t.label}: ok${detail ? ` (${detail})` : ""}.`);
|
|
26421
|
+
return 0;
|
|
26422
|
+
}
|
|
26423
|
+
fail(`Ran ${code(key)} on ${t.label}: ${run.status}${detail ? ` (${detail})` : ""}.`);
|
|
26424
|
+
const excerpt = safeExcerpt(run.error_excerpt);
|
|
26425
|
+
if (excerpt) out(dim(excerpt));
|
|
26426
|
+
return 2;
|
|
26427
|
+
}
|
|
26428
|
+
function formatTs2(iso) {
|
|
26429
|
+
const d = new Date(iso);
|
|
26430
|
+
return Number.isNaN(d.getTime()) ? iso : d.toISOString().replace("T", " ").replace(/\.\d{3}Z$/, "Z");
|
|
26431
|
+
}
|
|
26432
|
+
function colorStatus(status) {
|
|
26433
|
+
switch (status) {
|
|
26434
|
+
case "ok":
|
|
26435
|
+
return pc.green(status);
|
|
26436
|
+
case "skipped":
|
|
26437
|
+
return pc.dim(status);
|
|
26438
|
+
default:
|
|
26439
|
+
return pc.red(status);
|
|
26440
|
+
}
|
|
26441
|
+
}
|
|
26442
|
+
function table(head, rows) {
|
|
26443
|
+
const visible = (s) => s.replace(/\u001b\[[0-9;]*m/g, "").length;
|
|
26444
|
+
const widths = head.map((h, i) => Math.max(visible(h), ...rows.map((r) => visible(r[i] ?? ""))));
|
|
26445
|
+
const line = (cells) => cells.map((c2, i) => c2 + " ".repeat(Math.max(0, widths[i] - visible(c2)))).join(" ").trimEnd();
|
|
26446
|
+
out(dim(line(head)));
|
|
26447
|
+
for (const r of rows) out(line(r));
|
|
26448
|
+
}
|
|
26449
|
+
function mapJobsError(err, json, what) {
|
|
26450
|
+
const emit = (c2, message2) => {
|
|
26451
|
+
if (json) out(JSON.stringify({ ok: false, error: { code: c2, message: message2 } }));
|
|
26452
|
+
else fail(message2);
|
|
26453
|
+
};
|
|
26454
|
+
if (err instanceof ApiError) {
|
|
26455
|
+
if (err.status === 401 || err.code === "UNAUTHENTICATED") {
|
|
26456
|
+
emit("NOT_LOGGED_IN", `Not logged in. Run ${code("dashwise login")} first.`);
|
|
26457
|
+
return 1;
|
|
26458
|
+
}
|
|
26459
|
+
if (err.code === "SCHEDULE_NOT_FOUND") {
|
|
26460
|
+
emit(err.code, `${err.message} See ${code("dashwise jobs list")}.`);
|
|
26461
|
+
return 1;
|
|
26462
|
+
}
|
|
26463
|
+
if (err.status === 404 || err.code === "INSTALLATION_NOT_FOUND") {
|
|
26464
|
+
emit(
|
|
26465
|
+
"INSTALLATION_NOT_FOUND",
|
|
26466
|
+
`Installation not found, or you may not open it. Check the id (${code("dashwise module status <slug>")}).`
|
|
26467
|
+
);
|
|
26468
|
+
return 1;
|
|
26469
|
+
}
|
|
26470
|
+
if (err.status === 403) {
|
|
26471
|
+
emit(
|
|
26472
|
+
err.code || "FORBIDDEN",
|
|
26473
|
+
`You may not ${what} on this installation: reading needs its logs permission, running needs its settings permission (an installation manager or a company admin).`
|
|
26474
|
+
);
|
|
26475
|
+
return 1;
|
|
26476
|
+
}
|
|
26477
|
+
if (err.code === "FEATURE_NOT_ENABLED" || err.code === "COPY_NOT_RUNNING_JOBS") {
|
|
26478
|
+
emit(err.code, err.message);
|
|
26479
|
+
return 1;
|
|
26480
|
+
}
|
|
26481
|
+
emit(err.code, `Could not ${what}: ${err.code} ${err.status} \u2014 ${err.message}`);
|
|
26482
|
+
return 1;
|
|
26483
|
+
}
|
|
26484
|
+
const message = `Could not ${what}: ${err.message}`;
|
|
26485
|
+
emit("FETCH_FAILED", message);
|
|
26486
|
+
return 1;
|
|
26487
|
+
}
|
|
26488
|
+
|
|
26489
|
+
// src/commands/webhooks.ts
|
|
26490
|
+
init_api_client();
|
|
26491
|
+
init_output();
|
|
26492
|
+
var STATUSES = [
|
|
26493
|
+
"pending",
|
|
26494
|
+
"forwarding",
|
|
26495
|
+
"delivered",
|
|
26496
|
+
"failed",
|
|
26497
|
+
"skipped",
|
|
26498
|
+
"ignored"
|
|
26499
|
+
];
|
|
26500
|
+
var KEY2 = /^[a-z0-9][a-z0-9-]{0,39}$/;
|
|
26501
|
+
async function webhooksListCommand(slugArg, options) {
|
|
26502
|
+
const t = await resolveJobsTarget(slugArg, options, "webhooks list", "read the webhooks of");
|
|
26503
|
+
if (typeof t === "number") return t;
|
|
26504
|
+
let body;
|
|
26505
|
+
try {
|
|
26506
|
+
body = await getModuleWebhooks(t.installationId, t.auth);
|
|
26507
|
+
} catch (err) {
|
|
26508
|
+
return mapWebhooksError(err, options.json, "read the webhooks");
|
|
26509
|
+
}
|
|
26510
|
+
if (options.json) {
|
|
26511
|
+
out(JSON.stringify(body));
|
|
26512
|
+
return 0;
|
|
26513
|
+
}
|
|
26514
|
+
if (body.webhooks.length === 0) {
|
|
26515
|
+
info(`The installed version of ${t.label} declares no webhooks.`);
|
|
26516
|
+
return 0;
|
|
26517
|
+
}
|
|
26518
|
+
if (!body.enabled) {
|
|
26519
|
+
warn("Webhooks are not received on this platform yet (not enabled): there is no URL to give a provider.");
|
|
26520
|
+
} else if (!body.active) {
|
|
26521
|
+
warn("This installation does not receive webhooks now (offline, held, without a runtime, or on a Workbench).");
|
|
26522
|
+
}
|
|
26523
|
+
table(
|
|
26524
|
+
["KEY", "PROVIDER", "EVENTS", "ROUTE", "LAST DELIVERY"],
|
|
26525
|
+
body.webhooks.map((w) => [
|
|
26526
|
+
w.key,
|
|
26527
|
+
w.provider,
|
|
26528
|
+
w.events ? w.events.join(",") : dim("all"),
|
|
26529
|
+
w.route,
|
|
26530
|
+
w.last_delivery ? `${colorStatus2(w.last_delivery.status)} ${dim(formatTs2(w.last_delivery.received_at))}` : dim("never")
|
|
26531
|
+
])
|
|
26532
|
+
);
|
|
26533
|
+
out("");
|
|
26534
|
+
for (const w of body.webhooks) {
|
|
26535
|
+
if (w.url) out(`${w.key}: ${w.url} ${dim(`(signed with ${w.secret})`)}`);
|
|
26536
|
+
}
|
|
26537
|
+
info(`${body.webhooks.length} ${body.webhooks.length === 1 ? "webhook" : "webhooks"} (${t.label}).`);
|
|
26538
|
+
return 0;
|
|
26539
|
+
}
|
|
26540
|
+
async function webhooksDeliveriesCommand(slugArg, options) {
|
|
26541
|
+
if (options.status !== void 0 && !STATUSES.includes(options.status)) {
|
|
26542
|
+
fail(`--status must be one of ${STATUSES.join(", ")} (got "${options.status}").`);
|
|
26543
|
+
return 1;
|
|
26544
|
+
}
|
|
26545
|
+
if (options.key !== void 0 && !KEY2.test(options.key)) {
|
|
26546
|
+
fail(`--key must be a webhook key like ${code("github")} (got "${options.key}").`);
|
|
26547
|
+
return 1;
|
|
26548
|
+
}
|
|
26549
|
+
if (options.limit !== void 0 && (!Number.isInteger(options.limit) || options.limit < 1 || options.limit > 200)) {
|
|
26550
|
+
fail(`--limit must be between 1 and 200 (got "${options.limit}").`);
|
|
26551
|
+
return 1;
|
|
26552
|
+
}
|
|
26553
|
+
const t = await resolveJobsTarget(slugArg, options, "webhooks deliveries", "read the webhooks of");
|
|
26554
|
+
if (typeof t === "number") return t;
|
|
26555
|
+
let body;
|
|
26556
|
+
try {
|
|
26557
|
+
body = await listWebhookDeliveries(
|
|
26558
|
+
t.installationId,
|
|
26559
|
+
{
|
|
26560
|
+
...options.key ? { key: options.key } : {},
|
|
26561
|
+
...options.status ? { status: options.status } : {},
|
|
26562
|
+
...options.before !== void 0 ? { cursor: options.before } : {},
|
|
26563
|
+
...options.limit !== void 0 ? { limit: options.limit } : {}
|
|
26564
|
+
},
|
|
26565
|
+
t.auth
|
|
26566
|
+
);
|
|
26567
|
+
} catch (err) {
|
|
26568
|
+
return mapWebhooksError(err, options.json, "read the deliveries");
|
|
26569
|
+
}
|
|
26570
|
+
if (options.json) {
|
|
26571
|
+
out(JSON.stringify(body));
|
|
26572
|
+
return 0;
|
|
26573
|
+
}
|
|
26574
|
+
if (body.deliveries.length === 0) {
|
|
26575
|
+
info(`No deliveries for ${t.label}${options.key ? ` (${options.key})` : ""}.`);
|
|
26576
|
+
return 0;
|
|
26577
|
+
}
|
|
26578
|
+
table(
|
|
26579
|
+
["RECEIVED", "ID", "KEY", "EVENT", "STATUS", "KIND", "TRIES", "HTTP", "ERROR"],
|
|
26580
|
+
body.deliveries.map((d) => [
|
|
26581
|
+
formatTs2(d.received_at),
|
|
26582
|
+
String(d.id),
|
|
26583
|
+
d.webhook_key,
|
|
26584
|
+
d.event ?? "",
|
|
26585
|
+
colorStatus2(d.status),
|
|
26586
|
+
d.kind,
|
|
26587
|
+
String(d.attempts),
|
|
26588
|
+
d.last_http_status === null ? "" : String(d.last_http_status),
|
|
26589
|
+
d.last_error_code ?? ""
|
|
26590
|
+
])
|
|
26591
|
+
);
|
|
26592
|
+
out("");
|
|
26593
|
+
info(
|
|
26594
|
+
`${body.deliveries.length} ${body.deliveries.length === 1 ? "delivery" : "deliveries"} (${t.label}).` + (body.next_cursor !== null ? ` Older: ${code(`--before ${body.next_cursor}`)}.` : "")
|
|
26595
|
+
);
|
|
26596
|
+
return 0;
|
|
26597
|
+
}
|
|
26598
|
+
async function webhooksRedeliverCommand(deliveryId, slugArg, options) {
|
|
26599
|
+
if (!/^[1-9][0-9]{0,15}$/.test(deliveryId)) {
|
|
26600
|
+
fail(`"${deliveryId}" is not a delivery id (the ID column of ${code("dashwise webhooks deliveries")}).`);
|
|
26601
|
+
return 1;
|
|
26602
|
+
}
|
|
26603
|
+
const t = await resolveJobsTarget(slugArg, options, `webhooks redeliver ${deliveryId}`, "redeliver webhooks on");
|
|
26604
|
+
if (typeof t === "number") return t;
|
|
26605
|
+
let body;
|
|
26606
|
+
try {
|
|
26607
|
+
body = await redeliverWebhookDelivery(t.installationId, Number(deliveryId), t.auth);
|
|
26608
|
+
} catch (err) {
|
|
26609
|
+
return mapWebhooksError(err, options.json, `redeliver ${deliveryId}`);
|
|
26610
|
+
}
|
|
26611
|
+
if (options.json) {
|
|
26612
|
+
out(JSON.stringify(body));
|
|
26613
|
+
return 0;
|
|
26614
|
+
}
|
|
26615
|
+
success(
|
|
26616
|
+
`Redelivering #${body.delivery.id} (${body.delivery.webhook_key}${body.delivery.event ? `, ${body.delivery.event}` : ""}) to ${t.label} as a replay (invocation ${body.delivery.invocation_id}).`
|
|
26617
|
+
);
|
|
26618
|
+
info(`Follow it with ${code(`dashwise webhooks deliveries --key ${body.delivery.webhook_key}`)}.`);
|
|
26619
|
+
return 0;
|
|
26620
|
+
}
|
|
26621
|
+
function colorStatus2(status) {
|
|
26622
|
+
switch (status) {
|
|
26623
|
+
case "delivered":
|
|
26624
|
+
return pc.green(status);
|
|
26625
|
+
case "pending":
|
|
26626
|
+
case "forwarding":
|
|
26627
|
+
return pc.yellow(status);
|
|
26628
|
+
case "ignored":
|
|
26629
|
+
case "skipped":
|
|
26630
|
+
return pc.dim(status);
|
|
26631
|
+
default:
|
|
26632
|
+
return pc.red(status);
|
|
26633
|
+
}
|
|
26634
|
+
}
|
|
26635
|
+
var PASS_THROUGH = /* @__PURE__ */ new Set([
|
|
26636
|
+
"FEATURE_NOT_ENABLED",
|
|
26637
|
+
"COPY_NOT_RUNNING_JOBS",
|
|
26638
|
+
"WEBHOOK_DELIVERY_NOT_STORED",
|
|
26639
|
+
"WEBHOOK_DELIVERY_IN_PROGRESS",
|
|
26640
|
+
"WEBHOOK_DELIVERY_NOT_FOUND",
|
|
26641
|
+
"WEBHOOK_REDELIVERY_IMPERSONATION_REFUSED"
|
|
26642
|
+
]);
|
|
26643
|
+
function mapWebhooksError(err, json, what) {
|
|
26644
|
+
const emit = (c2, message) => {
|
|
26645
|
+
if (json) out(JSON.stringify({ ok: false, error: { code: c2, message } }));
|
|
26646
|
+
else fail(message);
|
|
26647
|
+
};
|
|
26648
|
+
if (err instanceof ApiError) {
|
|
26649
|
+
if (err.status === 401 || err.code === "UNAUTHENTICATED") {
|
|
26650
|
+
emit("NOT_LOGGED_IN", `Not logged in. Run ${code("dashwise login")} first.`);
|
|
26651
|
+
return 1;
|
|
26652
|
+
}
|
|
26653
|
+
if (PASS_THROUGH.has(err.code)) {
|
|
26654
|
+
emit(err.code, err.message);
|
|
26655
|
+
return 1;
|
|
26656
|
+
}
|
|
26657
|
+
if (err.status === 404 || err.code === "INSTALLATION_NOT_FOUND") {
|
|
26658
|
+
emit(
|
|
26659
|
+
"INSTALLATION_NOT_FOUND",
|
|
26660
|
+
`Installation not found, or you may not open it. Check the id (${code("dashwise module status <slug>")}).`
|
|
26661
|
+
);
|
|
26662
|
+
return 1;
|
|
26663
|
+
}
|
|
26664
|
+
if (err.status === 403) {
|
|
26665
|
+
emit(
|
|
26666
|
+
err.code || "FORBIDDEN",
|
|
26667
|
+
`You may not ${what} on this installation: reading needs its logs permission, a redelivery its settings permission (an installation manager or a company admin).`
|
|
26668
|
+
);
|
|
26669
|
+
return 1;
|
|
26670
|
+
}
|
|
26671
|
+
emit(err.code, `Could not ${what}: ${err.code} ${err.status} \u2014 ${err.message}`);
|
|
26672
|
+
return 1;
|
|
26673
|
+
}
|
|
26674
|
+
emit("FETCH_FAILED", `Could not ${what}: ${err.message}`);
|
|
26675
|
+
return 1;
|
|
26676
|
+
}
|
|
26677
|
+
|
|
26140
26678
|
// src/commands/module/rebuild.ts
|
|
26141
26679
|
init_api_client();
|
|
26142
26680
|
init_config();
|
|
@@ -27006,6 +27544,69 @@ moduleCmd.command("clone <slug>").description("Clone a published module locally.
|
|
|
27006
27544
|
});
|
|
27007
27545
|
}
|
|
27008
27546
|
);
|
|
27547
|
+
var jobsCmd = program.command("jobs").description(
|
|
27548
|
+
"A module installation's scheduled jobs (`schedules[]` in module.json): `list` the schedules with their next and last run, read the `runs` log, or `run` one now. The installation is resolved like `module logs`: the slug (argument or module.json) and the workspace, or `--install <id>`."
|
|
27549
|
+
);
|
|
27550
|
+
var jobsTargetFlags = (cmd) => cmd.option("--dir <path>", "Project root for the module.json slug lookup (default: current directory).").option("--workspace <slug-or-id>", "Target workspace (default: cached module workspace, config default, or interactive picker).").option("--install <id>", "Target an installation id directly (skips slug + workspace resolution).", parseIntOption).option("--json", "Emit the raw JSON response.");
|
|
27551
|
+
var jobsTarget = (o) => ({
|
|
27552
|
+
...o.dir !== void 0 ? { dir: o.dir } : {},
|
|
27553
|
+
...o.workspace !== void 0 ? { workspace: o.workspace } : {},
|
|
27554
|
+
...o.install !== void 0 ? { install: o.install } : {},
|
|
27555
|
+
...o.json !== void 0 ? { json: o.json } : {}
|
|
27556
|
+
});
|
|
27557
|
+
jobsTargetFlags(
|
|
27558
|
+
jobsCmd.command("list [slug]").description("List the schedules the installed version declares: cron (UTC), route, next run, last run.")
|
|
27559
|
+
).action(async (slug, cmdOpts) => {
|
|
27560
|
+
process.exitCode = await jobsListCommand(slug, jobsTarget(cmdOpts));
|
|
27561
|
+
});
|
|
27562
|
+
jobsTargetFlags(
|
|
27563
|
+
jobsCmd.command("runs [slug]").description("The run log, newest first: one line per attempt (status, HTTP status, duration, error code).").option("--key <key>", "Only this schedule key.").option("--kind <kind>", "Only this kind: schedule, manual, webhook, migration or replay.").option("--limit <n>", "Rows per page (1-200, default 50).", parseIntOption).option("--before <id>", "Older rows: the id the previous page printed.", parseIntOption)
|
|
27564
|
+
).action(
|
|
27565
|
+
async (slug, cmdOpts) => {
|
|
27566
|
+
process.exitCode = await jobsRunsCommand(slug, {
|
|
27567
|
+
...jobsTarget(cmdOpts),
|
|
27568
|
+
...cmdOpts.key !== void 0 ? { key: cmdOpts.key } : {},
|
|
27569
|
+
...cmdOpts.kind !== void 0 ? { kind: cmdOpts.kind } : {},
|
|
27570
|
+
...cmdOpts.limit !== void 0 ? { limit: cmdOpts.limit } : {},
|
|
27571
|
+
...cmdOpts.before !== void 0 ? { before: cmdOpts.before } : {}
|
|
27572
|
+
});
|
|
27573
|
+
}
|
|
27574
|
+
);
|
|
27575
|
+
jobsTargetFlags(
|
|
27576
|
+
jobsCmd.command("run <key> [slug]").description(
|
|
27577
|
+
"Run schedule <key> now, outside its cron (audited). Needs the installation settings permission. Exit 2 when the run did not succeed."
|
|
27578
|
+
)
|
|
27579
|
+
).action(async (key, slug, cmdOpts) => {
|
|
27580
|
+
process.exitCode = await jobsRunCommand(key, slug, jobsTarget(cmdOpts));
|
|
27581
|
+
});
|
|
27582
|
+
var webhooksCmd = program.command("webhooks").description(
|
|
27583
|
+
"A module installation's webhooks (`webhooks[]` in module.json): `list` them with the URL to give each provider, read the stored `deliveries`, or `redeliver` one. The platform verifies, deduplicates and stores each delivery before forwarding it to your route."
|
|
27584
|
+
);
|
|
27585
|
+
jobsTargetFlags(
|
|
27586
|
+
webhooksCmd.command("list [slug]").description("List the webhooks the installed version declares: provider, events, route, last delivery, URL.")
|
|
27587
|
+
).action(async (slug, cmdOpts) => {
|
|
27588
|
+
process.exitCode = await webhooksListCommand(slug, jobsTarget(cmdOpts));
|
|
27589
|
+
});
|
|
27590
|
+
jobsTargetFlags(
|
|
27591
|
+
webhooksCmd.command("deliveries [slug]").description("The stored deliveries, newest first (kept 7 days): event, status, kind, tries, last HTTP status.").option("--key <key>", "Only this webhook key.").option("--status <status>", "Only this status: pending, forwarding, delivered, failed, skipped or ignored.").option("--limit <n>", "Rows per page (1-200, default 50).", parseIntOption).option("--before <id>", "Older rows: the id the previous page printed.", parseIntOption)
|
|
27592
|
+
).action(
|
|
27593
|
+
async (slug, cmdOpts) => {
|
|
27594
|
+
process.exitCode = await webhooksDeliveriesCommand(slug, {
|
|
27595
|
+
...jobsTarget(cmdOpts),
|
|
27596
|
+
...cmdOpts.key !== void 0 ? { key: cmdOpts.key } : {},
|
|
27597
|
+
...cmdOpts.status !== void 0 ? { status: cmdOpts.status } : {},
|
|
27598
|
+
...cmdOpts.limit !== void 0 ? { limit: cmdOpts.limit } : {},
|
|
27599
|
+
...cmdOpts.before !== void 0 ? { before: cmdOpts.before } : {}
|
|
27600
|
+
});
|
|
27601
|
+
}
|
|
27602
|
+
);
|
|
27603
|
+
jobsTargetFlags(
|
|
27604
|
+
webhooksCmd.command("redeliver <delivery-id> [slug]").description(
|
|
27605
|
+
"Forward a stored delivery again (a replay with a new invocation id; audited). Needs the installation settings permission."
|
|
27606
|
+
)
|
|
27607
|
+
).action(async (deliveryId, slug, cmdOpts) => {
|
|
27608
|
+
process.exitCode = await webhooksRedeliverCommand(deliveryId, slug, jobsTarget(cmdOpts));
|
|
27609
|
+
});
|
|
27009
27610
|
var depsCmd = program.command("deps").description(
|
|
27010
27611
|
"Cross-module dependency commands. Bare `dashwise deps` shows the live connect status (resolved / unbound) of each declared dep on your installation; the subcommands manage module.json#dependencies (add / remove / list / check) and the connect lifecycle (bind / unbind)."
|
|
27011
27612
|
).option("--dir <path>", "Project root (default: current directory).").option("--json", "Emit the connect-status list as JSON instead of the human-readable view.").action(async (cmdOpts) => {
|
|
@@ -27829,7 +28430,7 @@ fieldCmd.command("set-type <table-slug> <field-slug> <new-type>").description(
|
|
|
27829
28430
|
}
|
|
27830
28431
|
);
|
|
27831
28432
|
function getVersion() {
|
|
27832
|
-
return "0.
|
|
28433
|
+
return "0.32.1";
|
|
27833
28434
|
}
|
|
27834
28435
|
function parsePositiveIdOption(value) {
|
|
27835
28436
|
if (!/^[1-9][0-9]*$/.test(value.trim())) {
|