@dashai/cli 0.31.0 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/bin.js +612 -48
- 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",
|
|
@@ -1017,9 +1077,9 @@ function mergeRemoteManifest(local, remote) {
|
|
|
1017
1077
|
}
|
|
1018
1078
|
return { merged, preserved };
|
|
1019
1079
|
}
|
|
1020
|
-
function fieldSlugs(
|
|
1080
|
+
function fieldSlugs(table2) {
|
|
1021
1081
|
const set = /* @__PURE__ */ new Set();
|
|
1022
|
-
const fields =
|
|
1082
|
+
const fields = table2?.fields;
|
|
1023
1083
|
if (!Array.isArray(fields)) return set;
|
|
1024
1084
|
for (const f of fields) {
|
|
1025
1085
|
if (f && typeof f === "object" && typeof f.slug === "string") {
|
|
@@ -14512,17 +14572,17 @@ function referenceFieldWarnings(manifest) {
|
|
|
14512
14572
|
const tables = manifest?.owns?.tables;
|
|
14513
14573
|
if (!Array.isArray(tables)) return [];
|
|
14514
14574
|
const warnings = [];
|
|
14515
|
-
for (const
|
|
14516
|
-
if (typeof
|
|
14517
|
-
for (const field of
|
|
14575
|
+
for (const table2 of tables) {
|
|
14576
|
+
if (typeof table2?.slug !== "string" || !Array.isArray(table2.fields)) continue;
|
|
14577
|
+
for (const field of table2.fields) {
|
|
14518
14578
|
if (typeof field?.slug !== "string" || typeof field.type !== "string") continue;
|
|
14519
14579
|
if (field.slug === "id" || !REFERENCE_NAME.test(field.slug)) continue;
|
|
14520
14580
|
if (!PLAIN_TYPES.has(field.type)) continue;
|
|
14521
14581
|
warnings.push({
|
|
14522
|
-
table:
|
|
14582
|
+
table: table2.slug,
|
|
14523
14583
|
field: field.slug,
|
|
14524
14584
|
type: field.type,
|
|
14525
|
-
message: `${
|
|
14585
|
+
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
14586
|
});
|
|
14527
14587
|
}
|
|
14528
14588
|
}
|
|
@@ -15156,9 +15216,9 @@ function printApplySummary(applied) {
|
|
|
15156
15216
|
const message = strProp(warning, "message");
|
|
15157
15217
|
if (!message) continue;
|
|
15158
15218
|
const warningCode = strProp(warning, "code");
|
|
15159
|
-
const
|
|
15219
|
+
const table2 = strProp(warning, "table");
|
|
15160
15220
|
const field = strProp(warning, "field");
|
|
15161
|
-
const location =
|
|
15221
|
+
const location = table2 ? `${table2}${field ? `.${field}` : ""}` : field;
|
|
15162
15222
|
const prefix = [warningCode, location].filter(Boolean).join(" ");
|
|
15163
15223
|
warn(`${prefix ? `${code(prefix)}: ` : ""}${message}`);
|
|
15164
15224
|
}
|
|
@@ -15277,8 +15337,8 @@ var init_push = __esm({
|
|
|
15277
15337
|
});
|
|
15278
15338
|
|
|
15279
15339
|
// ../sdk/dist/index.js
|
|
15280
|
-
function primaryDeclarationIssues(
|
|
15281
|
-
const invalid =
|
|
15340
|
+
function primaryDeclarationIssues(table2) {
|
|
15341
|
+
const invalid = table2.fields.findIndex(
|
|
15282
15342
|
(field2) => field2.primary !== void 0 && field2.primary !== true
|
|
15283
15343
|
);
|
|
15284
15344
|
if (invalid !== -1) {
|
|
@@ -15286,17 +15346,17 @@ function primaryDeclarationIssues(table) {
|
|
|
15286
15346
|
{ path: ["fields", invalid, "primary"], message: "primary must be literal true or omitted" }
|
|
15287
15347
|
];
|
|
15288
15348
|
}
|
|
15289
|
-
const marked =
|
|
15349
|
+
const marked = table2.fields.filter((field2) => field2.primary === true);
|
|
15290
15350
|
if (marked.length !== 1) {
|
|
15291
15351
|
return [
|
|
15292
15352
|
{
|
|
15293
15353
|
path: ["fields"],
|
|
15294
|
-
message: `Table ${
|
|
15354
|
+
message: `Table ${table2.slug}: mark exactly one field as primary: true (the row's label in links, pickers, titles)`
|
|
15295
15355
|
}
|
|
15296
15356
|
];
|
|
15297
15357
|
}
|
|
15298
15358
|
const field = marked[0];
|
|
15299
|
-
const path = ["fields",
|
|
15359
|
+
const path = ["fields", table2.fields.indexOf(field), "primary"];
|
|
15300
15360
|
if (field.type === "formula") {
|
|
15301
15361
|
return [{ path, message: "formula fields cannot be the primary of a module table yet" }];
|
|
15302
15362
|
}
|
|
@@ -15304,14 +15364,14 @@ function primaryDeclarationIssues(table) {
|
|
|
15304
15364
|
return [
|
|
15305
15365
|
{
|
|
15306
15366
|
path,
|
|
15307
|
-
message: `Table ${
|
|
15367
|
+
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
15368
|
}
|
|
15309
15369
|
];
|
|
15310
15370
|
}
|
|
15311
15371
|
return [];
|
|
15312
15372
|
}
|
|
15313
|
-
function weakPrimaryWarning(
|
|
15314
|
-
const field =
|
|
15373
|
+
function weakPrimaryWarning(table2) {
|
|
15374
|
+
const field = table2.fields.find((field2) => field2.primary === true);
|
|
15315
15375
|
if (!field || !WEAK_PRIMARY_TYPES.has(field.type)) return void 0;
|
|
15316
15376
|
return `${field.slug} is a weak row label; prefer a short text field such as a name or title`;
|
|
15317
15377
|
}
|
|
@@ -15529,14 +15589,14 @@ async function tableCreateCommand(slug, options) {
|
|
|
15529
15589
|
}
|
|
15530
15590
|
seen.add(f.slug);
|
|
15531
15591
|
}
|
|
15532
|
-
const
|
|
15533
|
-
const issues = primaryDeclarationIssues(
|
|
15592
|
+
const table2 = { slug, fields };
|
|
15593
|
+
const issues = primaryDeclarationIssues(table2);
|
|
15534
15594
|
if (issues.length > 0) {
|
|
15535
15595
|
for (const issue of issues) fail(issue.message);
|
|
15536
15596
|
info("Mark the row name explicitly, e.g. --field title:text:primary.");
|
|
15537
15597
|
return 1;
|
|
15538
15598
|
}
|
|
15539
|
-
const primaryWarning = weakPrimaryWarning(
|
|
15599
|
+
const primaryWarning = weakPrimaryWarning(table2);
|
|
15540
15600
|
if (primaryWarning) warn(primaryWarning);
|
|
15541
15601
|
let ctx;
|
|
15542
15602
|
try {
|
|
@@ -18910,7 +18970,13 @@ async function moduleGenerateCommand(options) {
|
|
|
18910
18970
|
{
|
|
18911
18971
|
manifest,
|
|
18912
18972
|
generation: options.generation ?? 0
|
|
18913
|
-
}
|
|
18973
|
+
},
|
|
18974
|
+
// The installed @dashai/sdk decides the client's bootstrap (backend
|
|
18975
|
+
// generator 3.3.0): from 2.13.0 it reads DASHWISE_API_KEY_FILE (a
|
|
18976
|
+
// Workbench's key); older or none, the client every 2.x SDK links. A
|
|
18977
|
+
// header, not a body field: an older backend refuses unknown body
|
|
18978
|
+
// fields (forbidNonWhitelisted) but ignores an unknown header.
|
|
18979
|
+
{ headers: sdkVersionHeader(projectRoot) }
|
|
18914
18980
|
);
|
|
18915
18981
|
} catch (err) {
|
|
18916
18982
|
if (err instanceof ApiError && err.status === 401) {
|
|
@@ -18964,6 +19030,12 @@ async function moduleGenerateCommand(options) {
|
|
|
18964
19030
|
);
|
|
18965
19031
|
return 0;
|
|
18966
19032
|
}
|
|
19033
|
+
var SDK_VERSION_HEADER = "Dashwise-Sdk-Version";
|
|
19034
|
+
var SDK_VERSION_PATTERN = /^v?\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$/;
|
|
19035
|
+
function sdkVersionHeader(projectRoot) {
|
|
19036
|
+
const version = readInstalledSdk(projectRoot)?.version;
|
|
19037
|
+
return version !== void 0 && version.length <= 64 && SDK_VERSION_PATTERN.test(version) ? { [SDK_VERSION_HEADER]: version } : {};
|
|
19038
|
+
}
|
|
18967
19039
|
function declaresUses(manifest) {
|
|
18968
19040
|
const uses = manifest?.uses;
|
|
18969
19041
|
return Array.isArray(uses?.tables) && uses.tables.length > 0;
|
|
@@ -23349,13 +23421,13 @@ async function buildFieldMap(ctx, candidate, overrides) {
|
|
|
23349
23421
|
if (overrideKeys.length === 0) {
|
|
23350
23422
|
return Object.keys(candidate.auto_field_map).length > 0 ? { ...candidate.auto_field_map } : void 0;
|
|
23351
23423
|
}
|
|
23352
|
-
const
|
|
23424
|
+
const table2 = await introspectWorkspaceTable(
|
|
23353
23425
|
ctx.installationId,
|
|
23354
23426
|
String(candidate.table_id),
|
|
23355
23427
|
ctx.requestOptions
|
|
23356
23428
|
);
|
|
23357
23429
|
const byName = /* @__PURE__ */ new Map();
|
|
23358
|
-
for (const f of
|
|
23430
|
+
for (const f of table2.fields) {
|
|
23359
23431
|
byName.set(f.slug, f.id);
|
|
23360
23432
|
if (f.name) byName.set(f.name, f.id);
|
|
23361
23433
|
}
|
|
@@ -23363,9 +23435,9 @@ async function buildFieldMap(ctx, candidate, overrides) {
|
|
|
23363
23435
|
for (const [usesField, physicalName] of Object.entries(overrides)) {
|
|
23364
23436
|
const id = byName.get(physicalName);
|
|
23365
23437
|
if (id === void 0) {
|
|
23366
|
-
const available =
|
|
23438
|
+
const available = table2.fields.map((f) => f.name && f.name !== f.slug ? `${f.slug} (${f.name})` : f.slug).join(", ");
|
|
23367
23439
|
throw new Error(
|
|
23368
|
-
`--map ${usesField}=${physicalName}: table "${
|
|
23440
|
+
`--map ${usesField}=${physicalName}: table "${table2.name}" has no field named "${physicalName}". Available: ${available}.`
|
|
23369
23441
|
);
|
|
23370
23442
|
}
|
|
23371
23443
|
merged[usesField] = id;
|
|
@@ -23604,9 +23676,9 @@ async function usesAddCommand(options) {
|
|
|
23604
23676
|
}
|
|
23605
23677
|
const scopeExit = await assertTableInInstallWorkspace(ctx, options.fromTable);
|
|
23606
23678
|
if (scopeExit !== 0) return scopeExit;
|
|
23607
|
-
let
|
|
23679
|
+
let table2;
|
|
23608
23680
|
try {
|
|
23609
|
-
|
|
23681
|
+
table2 = await introspectWorkspaceTable(
|
|
23610
23682
|
ctx.installationId,
|
|
23611
23683
|
options.fromTable,
|
|
23612
23684
|
ctx.requestOptions
|
|
@@ -23614,27 +23686,27 @@ async function usesAddCommand(options) {
|
|
|
23614
23686
|
} catch (err) {
|
|
23615
23687
|
return surfaceIntrospectError(err, options.fromTable);
|
|
23616
23688
|
}
|
|
23617
|
-
const slotSlug = options.slug ?? slugifyUsesName(
|
|
23689
|
+
const slotSlug = options.slug ?? slugifyUsesName(table2.name);
|
|
23618
23690
|
if (!isUsesSlug(slotSlug)) {
|
|
23619
23691
|
fail(
|
|
23620
23692
|
`Slot slug ${code(slotSlug || "(empty)")} doesn't match ${code(USES_SLUG_RULE)}. Pass ${code("--slug <snake_case_slug>")} explicitly.`
|
|
23621
23693
|
);
|
|
23622
23694
|
return 1;
|
|
23623
23695
|
}
|
|
23624
|
-
const { fields, skipped } = generateFields(
|
|
23696
|
+
const { fields, skipped } = generateFields(table2.fields);
|
|
23625
23697
|
if (fields.length === 0) {
|
|
23626
23698
|
fail(
|
|
23627
|
-
`Table ${code(
|
|
23699
|
+
`Table ${code(table2.name)} has no fields representable as a v1 ${code("uses")} contract (all are link_row / formula / ai / file). Nothing to declare.`
|
|
23628
23700
|
);
|
|
23629
23701
|
return 1;
|
|
23630
23702
|
}
|
|
23631
23703
|
const badFields = fields.filter((f) => !isUsesSlug(f.slug));
|
|
23632
23704
|
if (badFields.length > 0) {
|
|
23633
23705
|
fail(
|
|
23634
|
-
`${badFields.length} field name(s) on ${code(
|
|
23706
|
+
`${badFields.length} field name(s) on ${code(table2.name)} don't fold into a legal ${code("uses")} field slug (${code(USES_SLUG_RULE)}):`
|
|
23635
23707
|
);
|
|
23636
23708
|
for (const f of badFields) {
|
|
23637
|
-
const physical =
|
|
23709
|
+
const physical = table2.fields.find((p) => p.slug === f.slug);
|
|
23638
23710
|
const name = physical?.name ?? f.slug;
|
|
23639
23711
|
out(` ${dim("-")} ${code(name)} \u2192 ${code(f.slug || "(empty)")}`);
|
|
23640
23712
|
}
|
|
@@ -23648,10 +23720,10 @@ async function usesAddCommand(options) {
|
|
|
23648
23720
|
const collidingSlugs = [...slugCounts.entries()].filter(([, n]) => n > 1).map(([slug]) => slug);
|
|
23649
23721
|
if (collidingSlugs.length > 0) {
|
|
23650
23722
|
fail(
|
|
23651
|
-
`${collidingSlugs.length} generated ${code("uses")} field slug(s) on ${code(
|
|
23723
|
+
`${collidingSlugs.length} generated ${code("uses")} field slug(s) on ${code(table2.name)} collide \u2014 the backend refuses a contract with a duplicate field slug:`
|
|
23652
23724
|
);
|
|
23653
23725
|
for (const slug of collidingSlugs) {
|
|
23654
|
-
const names =
|
|
23726
|
+
const names = table2.fields.filter((p) => p.slug === slug && abstractTypeForPhysical(p.type) !== null).map((p) => p.name ?? p.slug);
|
|
23655
23727
|
out(` ${dim("-")} ${names.map((n) => code(n)).join(" + ")} \u2192 ${code(slug)}`);
|
|
23656
23728
|
}
|
|
23657
23729
|
info(
|
|
@@ -23667,7 +23739,7 @@ async function usesAddCommand(options) {
|
|
|
23667
23739
|
};
|
|
23668
23740
|
if (options.purpose) slot.purpose = options.purpose;
|
|
23669
23741
|
info(
|
|
23670
|
-
`Generated a ${code("uses")} contract ${code(slotSlug)} from table ${code(
|
|
23742
|
+
`Generated a ${code("uses")} contract ${code(slotSlug)} from table ${code(table2.name)} ${dim(`(id ${table2.table_id})`)}:`
|
|
23671
23743
|
);
|
|
23672
23744
|
out(` ${dim("ops:")} ${ops.join(", ")}`);
|
|
23673
23745
|
for (const f of fields) {
|
|
@@ -23713,24 +23785,24 @@ async function usesAddCommand(options) {
|
|
|
23713
23785
|
);
|
|
23714
23786
|
if (!pushed.ok) {
|
|
23715
23787
|
info(
|
|
23716
|
-
`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 ${
|
|
23788
|
+
`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}`)}.`
|
|
23717
23789
|
);
|
|
23718
23790
|
return 1;
|
|
23719
23791
|
}
|
|
23720
23792
|
if (options.noBind) {
|
|
23721
23793
|
info(
|
|
23722
|
-
`Skipped auto-bind (${code("--no-bind")}). Connect it when ready: ${code(`dashwise bind ${slotSlug} --table ${
|
|
23794
|
+
`Skipped auto-bind (${code("--no-bind")}). Connect it when ready: ${code(`dashwise bind ${slotSlug} --table ${table2.table_id}`)}.`
|
|
23723
23795
|
);
|
|
23724
23796
|
return 0;
|
|
23725
23797
|
}
|
|
23726
|
-
info(`Binding ${code(slotSlug)} to ${code(
|
|
23798
|
+
info(`Binding ${code(slotSlug)} to ${code(table2.name)}\u2026`);
|
|
23727
23799
|
const bindExit = await bindCommand(slotSlug, {
|
|
23728
23800
|
...ctx.dir !== void 0 ? { dir: ctx.dir } : {},
|
|
23729
|
-
table: String(
|
|
23801
|
+
table: String(table2.table_id)
|
|
23730
23802
|
});
|
|
23731
23803
|
if (bindExit !== 0) {
|
|
23732
23804
|
warn(
|
|
23733
|
-
`The ${code("uses")} slot was declared + pushed, but the auto-bind didn't complete. Retry with ${code(`dashwise bind ${slotSlug} --table ${
|
|
23805
|
+
`The ${code("uses")} slot was declared + pushed, but the auto-bind didn't complete. Retry with ${code(`dashwise bind ${slotSlug} --table ${table2.table_id}`)}.`
|
|
23734
23806
|
);
|
|
23735
23807
|
return bindExit;
|
|
23736
23808
|
}
|
|
@@ -25877,7 +25949,10 @@ async function moduleLogsCommand(slugArg, options) {
|
|
|
25877
25949
|
}
|
|
25878
25950
|
return runOnce3(installationId, label, options, since, auth);
|
|
25879
25951
|
}
|
|
25880
|
-
async function resolveInstallationFromSlug(slugArg, options, cfg, auth
|
|
25952
|
+
async function resolveInstallationFromSlug(slugArg, options, cfg, auth, wording = {
|
|
25953
|
+
command: "module logs",
|
|
25954
|
+
purpose: "read logs from"
|
|
25955
|
+
}) {
|
|
25881
25956
|
let slug = slugArg;
|
|
25882
25957
|
if (!slug) {
|
|
25883
25958
|
try {
|
|
@@ -25890,7 +25965,7 @@ async function resolveInstallationFromSlug(slugArg, options, cfg, auth) {
|
|
|
25890
25965
|
}
|
|
25891
25966
|
if (!slug) {
|
|
25892
25967
|
fail(
|
|
25893
|
-
`No module slug \u2014 pass one (${code(
|
|
25968
|
+
`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>")}.`
|
|
25894
25969
|
);
|
|
25895
25970
|
return 1;
|
|
25896
25971
|
}
|
|
@@ -25932,7 +26007,7 @@ async function resolveInstallationFromSlug(slugArg, options, cfg, auth) {
|
|
|
25932
26007
|
}
|
|
25933
26008
|
if (!row) {
|
|
25934
26009
|
info(
|
|
25935
|
-
`${code(modSlug)} is not installed in workspace ${code(workspace2.slug)} \u2014 nothing to
|
|
26010
|
+
`${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}`)}.`
|
|
25936
26011
|
);
|
|
25937
26012
|
return 0;
|
|
25938
26013
|
}
|
|
@@ -26125,6 +26200,432 @@ async function sleep3(ms, aborted) {
|
|
|
26125
26200
|
}
|
|
26126
26201
|
}
|
|
26127
26202
|
|
|
26203
|
+
// src/commands/jobs.ts
|
|
26204
|
+
init_api_client();
|
|
26205
|
+
init_config();
|
|
26206
|
+
init_output();
|
|
26207
|
+
var KINDS = ["schedule", "webhook", "manual", "migration", "replay"];
|
|
26208
|
+
var CONTROL = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g;
|
|
26209
|
+
function safeExcerpt(text4) {
|
|
26210
|
+
return text4 === null ? null : text4.replace(CONTROL, " ");
|
|
26211
|
+
}
|
|
26212
|
+
function safeRun(run) {
|
|
26213
|
+
return run ? { ...run, error_excerpt: safeExcerpt(run.error_excerpt) } : run;
|
|
26214
|
+
}
|
|
26215
|
+
var KEY = /^[a-z0-9][a-z0-9-]{0,39}$/;
|
|
26216
|
+
async function resolveJobsTarget(slugArg, options, command, purpose = "run jobs on") {
|
|
26217
|
+
if (!isLoggedIn()) {
|
|
26218
|
+
fail(`Not logged in. Run ${code("dashwise login")} first.`);
|
|
26219
|
+
return 1;
|
|
26220
|
+
}
|
|
26221
|
+
const cfg = readConfig();
|
|
26222
|
+
if (!cfg) {
|
|
26223
|
+
fail(`CLI config missing. Run ${code("dashwise login")}.`);
|
|
26224
|
+
return 1;
|
|
26225
|
+
}
|
|
26226
|
+
const auth = { baseUrl: resolveApiUrl(), auth: { token: cfg.token } };
|
|
26227
|
+
if (options.install !== void 0) {
|
|
26228
|
+
if (!Number.isInteger(options.install) || options.install <= 0) {
|
|
26229
|
+
fail(`--install expects a positive integer installation id (got "${options.install}").`);
|
|
26230
|
+
return 1;
|
|
26231
|
+
}
|
|
26232
|
+
return { installationId: options.install, label: `install #${options.install}`, auth };
|
|
26233
|
+
}
|
|
26234
|
+
const resolved = await resolveInstallationFromSlug(slugArg, options, cfg, auth, {
|
|
26235
|
+
command,
|
|
26236
|
+
purpose
|
|
26237
|
+
});
|
|
26238
|
+
if (typeof resolved === "number") return resolved;
|
|
26239
|
+
return { ...resolved, auth };
|
|
26240
|
+
}
|
|
26241
|
+
async function jobsListCommand(slugArg, options) {
|
|
26242
|
+
const t = await resolveJobsTarget(slugArg, options, "jobs list");
|
|
26243
|
+
if (typeof t === "number") return t;
|
|
26244
|
+
let body;
|
|
26245
|
+
try {
|
|
26246
|
+
body = await getModuleSchedules(t.installationId, t.auth);
|
|
26247
|
+
} catch (err) {
|
|
26248
|
+
return mapJobsError(err, options.json, "read the schedules");
|
|
26249
|
+
}
|
|
26250
|
+
if (options.json) {
|
|
26251
|
+
out(
|
|
26252
|
+
JSON.stringify({
|
|
26253
|
+
...body,
|
|
26254
|
+
schedules: body.schedules.map((s) => ({ ...s, last_run: safeRun(s.last_run) }))
|
|
26255
|
+
})
|
|
26256
|
+
);
|
|
26257
|
+
return 0;
|
|
26258
|
+
}
|
|
26259
|
+
if (body.schedules.length === 0) {
|
|
26260
|
+
info(`The installed version of ${t.label} declares no schedules.`);
|
|
26261
|
+
return 0;
|
|
26262
|
+
}
|
|
26263
|
+
if (!body.enabled) {
|
|
26264
|
+
warn("Schedules do not run on this platform yet (not enabled, or no job queue). Manual runs may still work.");
|
|
26265
|
+
} else if (!body.active) {
|
|
26266
|
+
warn("This installation does not run its schedules now (offline, held, without a runtime, or on a Workbench).");
|
|
26267
|
+
}
|
|
26268
|
+
const rows = body.schedules.map((s) => [
|
|
26269
|
+
s.key,
|
|
26270
|
+
s.interval ? `every ${s.interval}` : `${s.pattern} UTC`,
|
|
26271
|
+
s.route,
|
|
26272
|
+
s.next_run_at ? formatTs2(s.next_run_at) : "\u2014",
|
|
26273
|
+
s.last_run ? `${colorStatus(s.last_run.status)} ${dim(formatTs2(s.last_run.created_at))}` : dim("never"),
|
|
26274
|
+
s.running ? pc.yellow("running") : ""
|
|
26275
|
+
]);
|
|
26276
|
+
table(["KEY", "SCHEDULE", "ROUTE", "NEXT RUN", "LAST RUN", ""], rows);
|
|
26277
|
+
out("");
|
|
26278
|
+
info(`${body.schedules.length} ${body.schedules.length === 1 ? "schedule" : "schedules"} (${t.label}).`);
|
|
26279
|
+
return 0;
|
|
26280
|
+
}
|
|
26281
|
+
async function jobsRunsCommand(slugArg, options) {
|
|
26282
|
+
if (options.kind !== void 0 && !KINDS.includes(options.kind)) {
|
|
26283
|
+
fail(`--kind must be one of ${KINDS.join(", ")} (got "${options.kind}").`);
|
|
26284
|
+
return 1;
|
|
26285
|
+
}
|
|
26286
|
+
if (options.key !== void 0 && !KEY.test(options.key)) {
|
|
26287
|
+
fail(`--key must be a schedule key like ${code("lease-sweep")} (got "${options.key}").`);
|
|
26288
|
+
return 1;
|
|
26289
|
+
}
|
|
26290
|
+
if (options.limit !== void 0 && (!Number.isInteger(options.limit) || options.limit < 1 || options.limit > 200)) {
|
|
26291
|
+
fail(`--limit must be between 1 and 200 (got "${options.limit}").`);
|
|
26292
|
+
return 1;
|
|
26293
|
+
}
|
|
26294
|
+
const t = await resolveJobsTarget(slugArg, options, "jobs runs");
|
|
26295
|
+
if (typeof t === "number") return t;
|
|
26296
|
+
let body;
|
|
26297
|
+
try {
|
|
26298
|
+
body = await listJobRuns(
|
|
26299
|
+
t.installationId,
|
|
26300
|
+
{
|
|
26301
|
+
...options.kind ? { kind: options.kind } : {},
|
|
26302
|
+
...options.key ? { key: options.key } : {},
|
|
26303
|
+
...options.before !== void 0 ? { cursor: options.before } : {},
|
|
26304
|
+
...options.limit !== void 0 ? { limit: options.limit } : {}
|
|
26305
|
+
},
|
|
26306
|
+
t.auth
|
|
26307
|
+
);
|
|
26308
|
+
} catch (err) {
|
|
26309
|
+
return mapJobsError(err, options.json, "read the run log");
|
|
26310
|
+
}
|
|
26311
|
+
if (options.json) {
|
|
26312
|
+
out(JSON.stringify({ ...body, runs: body.runs.map((r) => safeRun(r)) }));
|
|
26313
|
+
return 0;
|
|
26314
|
+
}
|
|
26315
|
+
if (body.runs.length === 0) {
|
|
26316
|
+
info(`No runs for ${t.label}${options.key ? ` (${options.key})` : ""}.`);
|
|
26317
|
+
return 0;
|
|
26318
|
+
}
|
|
26319
|
+
table(
|
|
26320
|
+
["TIME", "KEY", "KIND", "TRY", "STATUS", "HTTP", "MS", "ERROR"],
|
|
26321
|
+
body.runs.map((r) => [
|
|
26322
|
+
formatTs2(r.created_at),
|
|
26323
|
+
r.key,
|
|
26324
|
+
r.kind,
|
|
26325
|
+
String(r.attempt),
|
|
26326
|
+
colorStatus(r.status),
|
|
26327
|
+
r.http_status === null ? "" : String(r.http_status),
|
|
26328
|
+
r.duration_ms === null ? "" : String(r.duration_ms),
|
|
26329
|
+
r.error_code ?? ""
|
|
26330
|
+
])
|
|
26331
|
+
);
|
|
26332
|
+
out("");
|
|
26333
|
+
info(
|
|
26334
|
+
`${body.runs.length} ${body.runs.length === 1 ? "run" : "runs"} (${t.label}).` + (body.next_cursor !== null ? ` Older: ${code(`--before ${body.next_cursor}`)}.` : "")
|
|
26335
|
+
);
|
|
26336
|
+
return 0;
|
|
26337
|
+
}
|
|
26338
|
+
async function jobsRunCommand(key, slugArg, options) {
|
|
26339
|
+
if (!KEY.test(key)) {
|
|
26340
|
+
fail(`"${key}" is not a schedule key (expected ${KEY.source}).`);
|
|
26341
|
+
return 1;
|
|
26342
|
+
}
|
|
26343
|
+
const t = await resolveJobsTarget(slugArg, options, `jobs run ${key}`);
|
|
26344
|
+
if (typeof t === "number") return t;
|
|
26345
|
+
let body;
|
|
26346
|
+
try {
|
|
26347
|
+
body = await runModuleSchedule(t.installationId, key, t.auth);
|
|
26348
|
+
} catch (err) {
|
|
26349
|
+
return mapJobsError(err, options.json, `run ${key}`);
|
|
26350
|
+
}
|
|
26351
|
+
if (options.json) {
|
|
26352
|
+
out(JSON.stringify({ ...body, run: safeRun(body.run) }));
|
|
26353
|
+
return body.run && body.run.status !== "ok" ? 2 : 0;
|
|
26354
|
+
}
|
|
26355
|
+
if (body.queued) {
|
|
26356
|
+
success(`Queued a run of ${code(key)} on ${t.label} (invocation ${body.invocation_id}).`);
|
|
26357
|
+
info(`Follow it with ${code(`dashwise jobs runs --key ${key}`)}.`);
|
|
26358
|
+
return 0;
|
|
26359
|
+
}
|
|
26360
|
+
const run = body.run;
|
|
26361
|
+
if (!run) {
|
|
26362
|
+
warn(`The run of ${code(key)} left no log row (invocation ${body.invocation_id}).`);
|
|
26363
|
+
return 2;
|
|
26364
|
+
}
|
|
26365
|
+
const detail = [
|
|
26366
|
+
run.http_status !== null ? `HTTP ${run.http_status}` : null,
|
|
26367
|
+
run.duration_ms !== null ? `${run.duration_ms} ms` : null,
|
|
26368
|
+
run.error_code
|
|
26369
|
+
].filter(Boolean).join(", ");
|
|
26370
|
+
if (run.status === "ok") {
|
|
26371
|
+
success(`Ran ${code(key)} on ${t.label}: ok${detail ? ` (${detail})` : ""}.`);
|
|
26372
|
+
return 0;
|
|
26373
|
+
}
|
|
26374
|
+
fail(`Ran ${code(key)} on ${t.label}: ${run.status}${detail ? ` (${detail})` : ""}.`);
|
|
26375
|
+
const excerpt = safeExcerpt(run.error_excerpt);
|
|
26376
|
+
if (excerpt) out(dim(excerpt));
|
|
26377
|
+
return 2;
|
|
26378
|
+
}
|
|
26379
|
+
function formatTs2(iso) {
|
|
26380
|
+
const d = new Date(iso);
|
|
26381
|
+
return Number.isNaN(d.getTime()) ? iso : d.toISOString().replace("T", " ").replace(/\.\d{3}Z$/, "Z");
|
|
26382
|
+
}
|
|
26383
|
+
function colorStatus(status) {
|
|
26384
|
+
switch (status) {
|
|
26385
|
+
case "ok":
|
|
26386
|
+
return pc.green(status);
|
|
26387
|
+
case "skipped":
|
|
26388
|
+
return pc.dim(status);
|
|
26389
|
+
default:
|
|
26390
|
+
return pc.red(status);
|
|
26391
|
+
}
|
|
26392
|
+
}
|
|
26393
|
+
function table(head, rows) {
|
|
26394
|
+
const visible = (s) => s.replace(/\u001b\[[0-9;]*m/g, "").length;
|
|
26395
|
+
const widths = head.map((h, i) => Math.max(visible(h), ...rows.map((r) => visible(r[i] ?? ""))));
|
|
26396
|
+
const line = (cells) => cells.map((c2, i) => c2 + " ".repeat(Math.max(0, widths[i] - visible(c2)))).join(" ").trimEnd();
|
|
26397
|
+
out(dim(line(head)));
|
|
26398
|
+
for (const r of rows) out(line(r));
|
|
26399
|
+
}
|
|
26400
|
+
function mapJobsError(err, json, what) {
|
|
26401
|
+
const emit = (c2, message2) => {
|
|
26402
|
+
if (json) out(JSON.stringify({ ok: false, error: { code: c2, message: message2 } }));
|
|
26403
|
+
else fail(message2);
|
|
26404
|
+
};
|
|
26405
|
+
if (err instanceof ApiError) {
|
|
26406
|
+
if (err.status === 401 || err.code === "UNAUTHENTICATED") {
|
|
26407
|
+
emit("NOT_LOGGED_IN", `Not logged in. Run ${code("dashwise login")} first.`);
|
|
26408
|
+
return 1;
|
|
26409
|
+
}
|
|
26410
|
+
if (err.code === "SCHEDULE_NOT_FOUND") {
|
|
26411
|
+
emit(err.code, `${err.message} See ${code("dashwise jobs list")}.`);
|
|
26412
|
+
return 1;
|
|
26413
|
+
}
|
|
26414
|
+
if (err.status === 404 || err.code === "INSTALLATION_NOT_FOUND") {
|
|
26415
|
+
emit(
|
|
26416
|
+
"INSTALLATION_NOT_FOUND",
|
|
26417
|
+
`Installation not found, or you may not open it. Check the id (${code("dashwise module status <slug>")}).`
|
|
26418
|
+
);
|
|
26419
|
+
return 1;
|
|
26420
|
+
}
|
|
26421
|
+
if (err.status === 403) {
|
|
26422
|
+
emit(
|
|
26423
|
+
err.code || "FORBIDDEN",
|
|
26424
|
+
`You may not ${what} on this installation: reading needs its logs permission, running needs its settings permission (an installation manager or a company admin).`
|
|
26425
|
+
);
|
|
26426
|
+
return 1;
|
|
26427
|
+
}
|
|
26428
|
+
if (err.code === "FEATURE_NOT_ENABLED" || err.code === "COPY_NOT_RUNNING_JOBS") {
|
|
26429
|
+
emit(err.code, err.message);
|
|
26430
|
+
return 1;
|
|
26431
|
+
}
|
|
26432
|
+
emit(err.code, `Could not ${what}: ${err.code} ${err.status} \u2014 ${err.message}`);
|
|
26433
|
+
return 1;
|
|
26434
|
+
}
|
|
26435
|
+
const message = `Could not ${what}: ${err.message}`;
|
|
26436
|
+
emit("FETCH_FAILED", message);
|
|
26437
|
+
return 1;
|
|
26438
|
+
}
|
|
26439
|
+
|
|
26440
|
+
// src/commands/webhooks.ts
|
|
26441
|
+
init_api_client();
|
|
26442
|
+
init_output();
|
|
26443
|
+
var STATUSES = [
|
|
26444
|
+
"pending",
|
|
26445
|
+
"forwarding",
|
|
26446
|
+
"delivered",
|
|
26447
|
+
"failed",
|
|
26448
|
+
"skipped",
|
|
26449
|
+
"ignored"
|
|
26450
|
+
];
|
|
26451
|
+
var KEY2 = /^[a-z0-9][a-z0-9-]{0,39}$/;
|
|
26452
|
+
async function webhooksListCommand(slugArg, options) {
|
|
26453
|
+
const t = await resolveJobsTarget(slugArg, options, "webhooks list", "read the webhooks of");
|
|
26454
|
+
if (typeof t === "number") return t;
|
|
26455
|
+
let body;
|
|
26456
|
+
try {
|
|
26457
|
+
body = await getModuleWebhooks(t.installationId, t.auth);
|
|
26458
|
+
} catch (err) {
|
|
26459
|
+
return mapWebhooksError(err, options.json, "read the webhooks");
|
|
26460
|
+
}
|
|
26461
|
+
if (options.json) {
|
|
26462
|
+
out(JSON.stringify(body));
|
|
26463
|
+
return 0;
|
|
26464
|
+
}
|
|
26465
|
+
if (body.webhooks.length === 0) {
|
|
26466
|
+
info(`The installed version of ${t.label} declares no webhooks.`);
|
|
26467
|
+
return 0;
|
|
26468
|
+
}
|
|
26469
|
+
if (!body.enabled) {
|
|
26470
|
+
warn("Webhooks are not received on this platform yet (not enabled): there is no URL to give a provider.");
|
|
26471
|
+
} else if (!body.active) {
|
|
26472
|
+
warn("This installation does not receive webhooks now (offline, held, without a runtime, or on a Workbench).");
|
|
26473
|
+
}
|
|
26474
|
+
table(
|
|
26475
|
+
["KEY", "PROVIDER", "EVENTS", "ROUTE", "LAST DELIVERY"],
|
|
26476
|
+
body.webhooks.map((w) => [
|
|
26477
|
+
w.key,
|
|
26478
|
+
w.provider,
|
|
26479
|
+
w.events ? w.events.join(",") : dim("all"),
|
|
26480
|
+
w.route,
|
|
26481
|
+
w.last_delivery ? `${colorStatus2(w.last_delivery.status)} ${dim(formatTs2(w.last_delivery.received_at))}` : dim("never")
|
|
26482
|
+
])
|
|
26483
|
+
);
|
|
26484
|
+
out("");
|
|
26485
|
+
for (const w of body.webhooks) {
|
|
26486
|
+
if (w.url) out(`${w.key}: ${w.url} ${dim(`(signed with ${w.secret})`)}`);
|
|
26487
|
+
}
|
|
26488
|
+
info(`${body.webhooks.length} ${body.webhooks.length === 1 ? "webhook" : "webhooks"} (${t.label}).`);
|
|
26489
|
+
return 0;
|
|
26490
|
+
}
|
|
26491
|
+
async function webhooksDeliveriesCommand(slugArg, options) {
|
|
26492
|
+
if (options.status !== void 0 && !STATUSES.includes(options.status)) {
|
|
26493
|
+
fail(`--status must be one of ${STATUSES.join(", ")} (got "${options.status}").`);
|
|
26494
|
+
return 1;
|
|
26495
|
+
}
|
|
26496
|
+
if (options.key !== void 0 && !KEY2.test(options.key)) {
|
|
26497
|
+
fail(`--key must be a webhook key like ${code("github")} (got "${options.key}").`);
|
|
26498
|
+
return 1;
|
|
26499
|
+
}
|
|
26500
|
+
if (options.limit !== void 0 && (!Number.isInteger(options.limit) || options.limit < 1 || options.limit > 200)) {
|
|
26501
|
+
fail(`--limit must be between 1 and 200 (got "${options.limit}").`);
|
|
26502
|
+
return 1;
|
|
26503
|
+
}
|
|
26504
|
+
const t = await resolveJobsTarget(slugArg, options, "webhooks deliveries", "read the webhooks of");
|
|
26505
|
+
if (typeof t === "number") return t;
|
|
26506
|
+
let body;
|
|
26507
|
+
try {
|
|
26508
|
+
body = await listWebhookDeliveries(
|
|
26509
|
+
t.installationId,
|
|
26510
|
+
{
|
|
26511
|
+
...options.key ? { key: options.key } : {},
|
|
26512
|
+
...options.status ? { status: options.status } : {},
|
|
26513
|
+
...options.before !== void 0 ? { cursor: options.before } : {},
|
|
26514
|
+
...options.limit !== void 0 ? { limit: options.limit } : {}
|
|
26515
|
+
},
|
|
26516
|
+
t.auth
|
|
26517
|
+
);
|
|
26518
|
+
} catch (err) {
|
|
26519
|
+
return mapWebhooksError(err, options.json, "read the deliveries");
|
|
26520
|
+
}
|
|
26521
|
+
if (options.json) {
|
|
26522
|
+
out(JSON.stringify(body));
|
|
26523
|
+
return 0;
|
|
26524
|
+
}
|
|
26525
|
+
if (body.deliveries.length === 0) {
|
|
26526
|
+
info(`No deliveries for ${t.label}${options.key ? ` (${options.key})` : ""}.`);
|
|
26527
|
+
return 0;
|
|
26528
|
+
}
|
|
26529
|
+
table(
|
|
26530
|
+
["RECEIVED", "ID", "KEY", "EVENT", "STATUS", "KIND", "TRIES", "HTTP", "ERROR"],
|
|
26531
|
+
body.deliveries.map((d) => [
|
|
26532
|
+
formatTs2(d.received_at),
|
|
26533
|
+
String(d.id),
|
|
26534
|
+
d.webhook_key,
|
|
26535
|
+
d.event ?? "",
|
|
26536
|
+
colorStatus2(d.status),
|
|
26537
|
+
d.kind,
|
|
26538
|
+
String(d.attempts),
|
|
26539
|
+
d.last_http_status === null ? "" : String(d.last_http_status),
|
|
26540
|
+
d.last_error_code ?? ""
|
|
26541
|
+
])
|
|
26542
|
+
);
|
|
26543
|
+
out("");
|
|
26544
|
+
info(
|
|
26545
|
+
`${body.deliveries.length} ${body.deliveries.length === 1 ? "delivery" : "deliveries"} (${t.label}).` + (body.next_cursor !== null ? ` Older: ${code(`--before ${body.next_cursor}`)}.` : "")
|
|
26546
|
+
);
|
|
26547
|
+
return 0;
|
|
26548
|
+
}
|
|
26549
|
+
async function webhooksRedeliverCommand(deliveryId, slugArg, options) {
|
|
26550
|
+
if (!/^[1-9][0-9]{0,15}$/.test(deliveryId)) {
|
|
26551
|
+
fail(`"${deliveryId}" is not a delivery id (the ID column of ${code("dashwise webhooks deliveries")}).`);
|
|
26552
|
+
return 1;
|
|
26553
|
+
}
|
|
26554
|
+
const t = await resolveJobsTarget(slugArg, options, `webhooks redeliver ${deliveryId}`, "redeliver webhooks on");
|
|
26555
|
+
if (typeof t === "number") return t;
|
|
26556
|
+
let body;
|
|
26557
|
+
try {
|
|
26558
|
+
body = await redeliverWebhookDelivery(t.installationId, Number(deliveryId), t.auth);
|
|
26559
|
+
} catch (err) {
|
|
26560
|
+
return mapWebhooksError(err, options.json, `redeliver ${deliveryId}`);
|
|
26561
|
+
}
|
|
26562
|
+
if (options.json) {
|
|
26563
|
+
out(JSON.stringify(body));
|
|
26564
|
+
return 0;
|
|
26565
|
+
}
|
|
26566
|
+
success(
|
|
26567
|
+
`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}).`
|
|
26568
|
+
);
|
|
26569
|
+
info(`Follow it with ${code(`dashwise webhooks deliveries --key ${body.delivery.webhook_key}`)}.`);
|
|
26570
|
+
return 0;
|
|
26571
|
+
}
|
|
26572
|
+
function colorStatus2(status) {
|
|
26573
|
+
switch (status) {
|
|
26574
|
+
case "delivered":
|
|
26575
|
+
return pc.green(status);
|
|
26576
|
+
case "pending":
|
|
26577
|
+
case "forwarding":
|
|
26578
|
+
return pc.yellow(status);
|
|
26579
|
+
case "ignored":
|
|
26580
|
+
case "skipped":
|
|
26581
|
+
return pc.dim(status);
|
|
26582
|
+
default:
|
|
26583
|
+
return pc.red(status);
|
|
26584
|
+
}
|
|
26585
|
+
}
|
|
26586
|
+
var PASS_THROUGH = /* @__PURE__ */ new Set([
|
|
26587
|
+
"FEATURE_NOT_ENABLED",
|
|
26588
|
+
"COPY_NOT_RUNNING_JOBS",
|
|
26589
|
+
"WEBHOOK_DELIVERY_NOT_STORED",
|
|
26590
|
+
"WEBHOOK_DELIVERY_IN_PROGRESS",
|
|
26591
|
+
"WEBHOOK_DELIVERY_NOT_FOUND",
|
|
26592
|
+
"WEBHOOK_REDELIVERY_IMPERSONATION_REFUSED"
|
|
26593
|
+
]);
|
|
26594
|
+
function mapWebhooksError(err, json, what) {
|
|
26595
|
+
const emit = (c2, message) => {
|
|
26596
|
+
if (json) out(JSON.stringify({ ok: false, error: { code: c2, message } }));
|
|
26597
|
+
else fail(message);
|
|
26598
|
+
};
|
|
26599
|
+
if (err instanceof ApiError) {
|
|
26600
|
+
if (err.status === 401 || err.code === "UNAUTHENTICATED") {
|
|
26601
|
+
emit("NOT_LOGGED_IN", `Not logged in. Run ${code("dashwise login")} first.`);
|
|
26602
|
+
return 1;
|
|
26603
|
+
}
|
|
26604
|
+
if (PASS_THROUGH.has(err.code)) {
|
|
26605
|
+
emit(err.code, err.message);
|
|
26606
|
+
return 1;
|
|
26607
|
+
}
|
|
26608
|
+
if (err.status === 404 || err.code === "INSTALLATION_NOT_FOUND") {
|
|
26609
|
+
emit(
|
|
26610
|
+
"INSTALLATION_NOT_FOUND",
|
|
26611
|
+
`Installation not found, or you may not open it. Check the id (${code("dashwise module status <slug>")}).`
|
|
26612
|
+
);
|
|
26613
|
+
return 1;
|
|
26614
|
+
}
|
|
26615
|
+
if (err.status === 403) {
|
|
26616
|
+
emit(
|
|
26617
|
+
err.code || "FORBIDDEN",
|
|
26618
|
+
`You may not ${what} on this installation: reading needs its logs permission, a redelivery its settings permission (an installation manager or a company admin).`
|
|
26619
|
+
);
|
|
26620
|
+
return 1;
|
|
26621
|
+
}
|
|
26622
|
+
emit(err.code, `Could not ${what}: ${err.code} ${err.status} \u2014 ${err.message}`);
|
|
26623
|
+
return 1;
|
|
26624
|
+
}
|
|
26625
|
+
emit("FETCH_FAILED", `Could not ${what}: ${err.message}`);
|
|
26626
|
+
return 1;
|
|
26627
|
+
}
|
|
26628
|
+
|
|
26128
26629
|
// src/commands/module/rebuild.ts
|
|
26129
26630
|
init_api_client();
|
|
26130
26631
|
init_config();
|
|
@@ -26994,6 +27495,69 @@ moduleCmd.command("clone <slug>").description("Clone a published module locally.
|
|
|
26994
27495
|
});
|
|
26995
27496
|
}
|
|
26996
27497
|
);
|
|
27498
|
+
var jobsCmd = program.command("jobs").description(
|
|
27499
|
+
"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>`."
|
|
27500
|
+
);
|
|
27501
|
+
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.");
|
|
27502
|
+
var jobsTarget = (o) => ({
|
|
27503
|
+
...o.dir !== void 0 ? { dir: o.dir } : {},
|
|
27504
|
+
...o.workspace !== void 0 ? { workspace: o.workspace } : {},
|
|
27505
|
+
...o.install !== void 0 ? { install: o.install } : {},
|
|
27506
|
+
...o.json !== void 0 ? { json: o.json } : {}
|
|
27507
|
+
});
|
|
27508
|
+
jobsTargetFlags(
|
|
27509
|
+
jobsCmd.command("list [slug]").description("List the schedules the installed version declares: cron (UTC), route, next run, last run.")
|
|
27510
|
+
).action(async (slug, cmdOpts) => {
|
|
27511
|
+
process.exitCode = await jobsListCommand(slug, jobsTarget(cmdOpts));
|
|
27512
|
+
});
|
|
27513
|
+
jobsTargetFlags(
|
|
27514
|
+
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)
|
|
27515
|
+
).action(
|
|
27516
|
+
async (slug, cmdOpts) => {
|
|
27517
|
+
process.exitCode = await jobsRunsCommand(slug, {
|
|
27518
|
+
...jobsTarget(cmdOpts),
|
|
27519
|
+
...cmdOpts.key !== void 0 ? { key: cmdOpts.key } : {},
|
|
27520
|
+
...cmdOpts.kind !== void 0 ? { kind: cmdOpts.kind } : {},
|
|
27521
|
+
...cmdOpts.limit !== void 0 ? { limit: cmdOpts.limit } : {},
|
|
27522
|
+
...cmdOpts.before !== void 0 ? { before: cmdOpts.before } : {}
|
|
27523
|
+
});
|
|
27524
|
+
}
|
|
27525
|
+
);
|
|
27526
|
+
jobsTargetFlags(
|
|
27527
|
+
jobsCmd.command("run <key> [slug]").description(
|
|
27528
|
+
"Run schedule <key> now, outside its cron (audited). Needs the installation settings permission. Exit 2 when the run did not succeed."
|
|
27529
|
+
)
|
|
27530
|
+
).action(async (key, slug, cmdOpts) => {
|
|
27531
|
+
process.exitCode = await jobsRunCommand(key, slug, jobsTarget(cmdOpts));
|
|
27532
|
+
});
|
|
27533
|
+
var webhooksCmd = program.command("webhooks").description(
|
|
27534
|
+
"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."
|
|
27535
|
+
);
|
|
27536
|
+
jobsTargetFlags(
|
|
27537
|
+
webhooksCmd.command("list [slug]").description("List the webhooks the installed version declares: provider, events, route, last delivery, URL.")
|
|
27538
|
+
).action(async (slug, cmdOpts) => {
|
|
27539
|
+
process.exitCode = await webhooksListCommand(slug, jobsTarget(cmdOpts));
|
|
27540
|
+
});
|
|
27541
|
+
jobsTargetFlags(
|
|
27542
|
+
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)
|
|
27543
|
+
).action(
|
|
27544
|
+
async (slug, cmdOpts) => {
|
|
27545
|
+
process.exitCode = await webhooksDeliveriesCommand(slug, {
|
|
27546
|
+
...jobsTarget(cmdOpts),
|
|
27547
|
+
...cmdOpts.key !== void 0 ? { key: cmdOpts.key } : {},
|
|
27548
|
+
...cmdOpts.status !== void 0 ? { status: cmdOpts.status } : {},
|
|
27549
|
+
...cmdOpts.limit !== void 0 ? { limit: cmdOpts.limit } : {},
|
|
27550
|
+
...cmdOpts.before !== void 0 ? { before: cmdOpts.before } : {}
|
|
27551
|
+
});
|
|
27552
|
+
}
|
|
27553
|
+
);
|
|
27554
|
+
jobsTargetFlags(
|
|
27555
|
+
webhooksCmd.command("redeliver <delivery-id> [slug]").description(
|
|
27556
|
+
"Forward a stored delivery again (a replay with a new invocation id; audited). Needs the installation settings permission."
|
|
27557
|
+
)
|
|
27558
|
+
).action(async (deliveryId, slug, cmdOpts) => {
|
|
27559
|
+
process.exitCode = await webhooksRedeliverCommand(deliveryId, slug, jobsTarget(cmdOpts));
|
|
27560
|
+
});
|
|
26997
27561
|
var depsCmd = program.command("deps").description(
|
|
26998
27562
|
"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)."
|
|
26999
27563
|
).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) => {
|
|
@@ -27817,7 +28381,7 @@ fieldCmd.command("set-type <table-slug> <field-slug> <new-type>").description(
|
|
|
27817
28381
|
}
|
|
27818
28382
|
);
|
|
27819
28383
|
function getVersion() {
|
|
27820
|
-
return "0.
|
|
28384
|
+
return "0.32.0";
|
|
27821
28385
|
}
|
|
27822
28386
|
function parsePositiveIdOption(value) {
|
|
27823
28387
|
if (!/^[1-9][0-9]*$/.test(value.trim())) {
|