@tenderprompt/cli 0.1.13 → 0.1.14
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 +16 -10
- package/dist/index.js +282 -58
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -133,9 +133,10 @@ tender app build artifact_123 --commit "$(git rev-parse HEAD)" --json
|
|
|
133
133
|
tender app preview artifact_123 --commit "$(git rev-parse HEAD)" --stream --json
|
|
134
134
|
tender app publish artifact_123 --dry-run --json
|
|
135
135
|
tender app db capabilities artifact_123 --json
|
|
136
|
-
tender app db tables artifact_123 --json
|
|
137
|
-
tender app db schema artifact_123 --table votes --json
|
|
138
|
-
tender app db query artifact_123 --sql "select * from votes limit 20" --json
|
|
136
|
+
tender app db tables artifact_123 --env published --json
|
|
137
|
+
tender app db schema artifact_123 --env published --table votes --json
|
|
138
|
+
tender app db query artifact_123 --env published --sql "select * from votes limit 20" --json
|
|
139
|
+
tender app db query artifact_123 --env published --sql "select * from votes order by created_at" --limit 50 --offset 50 --json
|
|
139
140
|
```
|
|
140
141
|
|
|
141
142
|
## Analytics
|
|
@@ -249,7 +250,8 @@ ids, URLs, or high-cardinality values unless the chart has a clear purpose.
|
|
|
249
250
|
App database commands are artifact-scoped and go through Tender's server-side
|
|
250
251
|
authorization and read-only query checks. The CLI only accepts an app id; it
|
|
251
252
|
does not accept tenant ids, runner names, provider account ids, or runtime
|
|
252
|
-
storage identifiers.
|
|
253
|
+
storage identifiers. Commands that read data require an explicit `--env preview`
|
|
254
|
+
or `--env published`; there is no default environment.
|
|
253
255
|
|
|
254
256
|
Start with capabilities:
|
|
255
257
|
|
|
@@ -262,24 +264,28 @@ tender app db capabilities artifact_123 --profile db --json
|
|
|
262
264
|
Common read-only commands:
|
|
263
265
|
|
|
264
266
|
```bash
|
|
265
|
-
tender app db tables artifact_123 --profile db --json
|
|
267
|
+
tender app db tables artifact_123 --env published --profile db --json
|
|
266
268
|
|
|
267
|
-
tender app db schema artifact_123 --table votes --profile db --json
|
|
269
|
+
tender app db schema artifact_123 --env published --table votes --profile db --json
|
|
268
270
|
|
|
269
|
-
tender app db query artifact_123 --sql "select * from votes limit 20" --profile db --json
|
|
271
|
+
tender app db query artifact_123 --env published --sql "select * from votes limit 20" --profile db --json
|
|
270
272
|
|
|
271
273
|
tender app db query artifact_123 \
|
|
274
|
+
--env published \
|
|
272
275
|
--sql "select * from votes where choice = ?" \
|
|
273
276
|
--param coffee \
|
|
274
277
|
--profile db \
|
|
275
278
|
--json
|
|
276
279
|
|
|
277
|
-
tender app db query artifact_123 --file ./debug.sql --limit 50 --profile db --json
|
|
280
|
+
tender app db query artifact_123 --env published --file ./debug.sql --limit 50 --offset 100 --profile db --json
|
|
278
281
|
|
|
279
|
-
tender app db query artifact_123 --file - --profile db --json < debug.sql
|
|
282
|
+
tender app db query artifact_123 --env published --file - --offset 100 --profile db --json < debug.sql
|
|
280
283
|
```
|
|
281
284
|
|
|
282
|
-
The query command sends one read-only statement with positional bindings.
|
|
285
|
+
The query command sends one read-only statement with positional bindings. Query
|
|
286
|
+
responses include `requestedLimit`, `appliedLimit`, `maxInteractiveLimit`,
|
|
287
|
+
`offset`, and `nextOffset`; agents must request each page explicitly and should
|
|
288
|
+
not auto-drain large tables. The interactive row cap is 50 rows per request.
|
|
283
289
|
Client and server limits include a 100 KB statement cap and at most 100 bound
|
|
284
290
|
parameters. The server enforces the final policy, row limits, response limits,
|
|
285
291
|
artifact scope, and audit logging.
|
package/dist/index.js
CHANGED
|
@@ -41,18 +41,26 @@ class TenderCliUsageError extends Error {
|
|
|
41
41
|
}
|
|
42
42
|
class TenderCliAnalyticsApiError extends Error {
|
|
43
43
|
reasonCode;
|
|
44
|
-
|
|
44
|
+
scope;
|
|
45
|
+
example;
|
|
46
|
+
constructor(message, reasonCode, scope = null, example = null) {
|
|
45
47
|
super(message);
|
|
46
48
|
this.name = "TenderCliAnalyticsApiError";
|
|
47
49
|
this.reasonCode = reasonCode;
|
|
50
|
+
this.scope = scope;
|
|
51
|
+
this.example = example;
|
|
48
52
|
}
|
|
49
53
|
}
|
|
50
54
|
class TenderCliDbApiError extends Error {
|
|
51
55
|
reasonCode;
|
|
52
|
-
|
|
56
|
+
scope;
|
|
57
|
+
example;
|
|
58
|
+
constructor(message, reasonCode, scope, example) {
|
|
53
59
|
super(message);
|
|
54
60
|
this.name = "TenderCliDbApiError";
|
|
55
61
|
this.reasonCode = reasonCode;
|
|
62
|
+
this.scope = scope;
|
|
63
|
+
this.example = example;
|
|
56
64
|
}
|
|
57
65
|
}
|
|
58
66
|
const DEFAULT_PROFILE_NAME = "default";
|
|
@@ -101,7 +109,7 @@ Commands:
|
|
|
101
109
|
Generate useful starter chart suggestions
|
|
102
110
|
tender app db capabilities <app-id>
|
|
103
111
|
Describe app database query limits and policy
|
|
104
|
-
tender app db query <app-id> --sql <sql>
|
|
112
|
+
tender app db query <app-id> --env <preview|published> --sql <sql>
|
|
105
113
|
Run one read-only app database query
|
|
106
114
|
tender app context fetch <app-id>
|
|
107
115
|
Fetch managed agent context into a checkout
|
|
@@ -135,9 +143,9 @@ Examples:
|
|
|
135
143
|
tender app analytics query artifact_123 --spec chart.json --json
|
|
136
144
|
tender app analytics suggestions artifact_123 --range 30d --json
|
|
137
145
|
tender app db capabilities artifact_123 --json
|
|
138
|
-
tender app db tables artifact_123 --json
|
|
139
|
-
tender app db schema artifact_123 --table votes --json
|
|
140
|
-
tender app db query artifact_123 --sql "select * from votes limit 20" --json
|
|
146
|
+
tender app db tables artifact_123 --env published --json
|
|
147
|
+
tender app db schema artifact_123 --env published --table votes --json
|
|
148
|
+
tender app db query artifact_123 --env published --sql "select * from votes limit 20" --json
|
|
141
149
|
tender app context fetch artifact_123 --dir ./widget --dry-run --json
|
|
142
150
|
tender app context status artifact_123 --dir ./widget --json
|
|
143
151
|
tender app context refresh artifact_123 --dir ./widget --dry-run --json
|
|
@@ -238,11 +246,11 @@ Commands:
|
|
|
238
246
|
Make a custom event property queryable
|
|
239
247
|
tender app db capabilities <app-id>
|
|
240
248
|
Describe app database query limits and policy
|
|
241
|
-
tender app db tables <app-id>
|
|
249
|
+
tender app db tables <app-id> --env <preview|published>
|
|
242
250
|
List app database tables and views
|
|
243
|
-
tender app db schema <app-id> --table <name>
|
|
251
|
+
tender app db schema <app-id> --env <preview|published> --table <name>
|
|
244
252
|
Show table and index schema for one table
|
|
245
|
-
tender app db query <app-id> --sql <sql>
|
|
253
|
+
tender app db query <app-id> --env <preview|published> --sql <sql>
|
|
246
254
|
Run one read-only app database query
|
|
247
255
|
tender app context fetch <app-id>
|
|
248
256
|
Fetch AGENTS.md, skills, and Tender App scaffold files
|
|
@@ -285,9 +293,9 @@ Examples:
|
|
|
285
293
|
tender app analytics properties list artifact_123 --json
|
|
286
294
|
tender app analytics properties activate artifact_123 --event signup_started --property plan --type dimension --dry-run --json
|
|
287
295
|
tender app db capabilities artifact_123 --json
|
|
288
|
-
tender app db tables artifact_123 --json
|
|
289
|
-
tender app db schema artifact_123 --table votes --json
|
|
290
|
-
tender app db query artifact_123 --sql "select * from votes limit 20" --json
|
|
296
|
+
tender app db tables artifact_123 --env published --json
|
|
297
|
+
tender app db schema artifact_123 --env published --table votes --json
|
|
298
|
+
tender app db query artifact_123 --env published --sql "select * from votes limit 20" --json
|
|
291
299
|
tender app context fetch artifact_123 --dir ./widget --dry-run --json
|
|
292
300
|
tender app context status artifact_123 --dir ./widget --json
|
|
293
301
|
tender app context refresh artifact_123 --dir ./widget --dry-run --json
|
|
@@ -471,16 +479,19 @@ function dbHelp() {
|
|
|
471
479
|
|
|
472
480
|
Commands:
|
|
473
481
|
tender app db capabilities <app-id> Describe app database policy and limits
|
|
474
|
-
tender app db tables <app-id>
|
|
475
|
-
|
|
476
|
-
tender app db
|
|
482
|
+
tender app db tables <app-id> --env <preview|published>
|
|
483
|
+
List app database tables and views
|
|
484
|
+
tender app db schema <app-id> --env <preview|published>
|
|
485
|
+
Show schema for one table
|
|
486
|
+
tender app db query <app-id> --env <preview|published>
|
|
487
|
+
Run one read-only SQL query
|
|
477
488
|
|
|
478
489
|
Examples:
|
|
479
490
|
tender app db capabilities artifact_123 --json
|
|
480
|
-
tender app db tables artifact_123 --json
|
|
481
|
-
tender app db schema artifact_123 --table votes --json
|
|
482
|
-
tender app db query artifact_123 --sql "select * from votes limit 20" --json
|
|
483
|
-
tender app db query artifact_123 --file ./debug.sql --param active --limit 50 --json`;
|
|
491
|
+
tender app db tables artifact_123 --env published --json
|
|
492
|
+
tender app db schema artifact_123 --env published --table votes --json
|
|
493
|
+
tender app db query artifact_123 --env published --sql "select * from votes limit 20" --json
|
|
494
|
+
tender app db query artifact_123 --env published --file ./debug.sql --param active --limit 50 --json`;
|
|
484
495
|
}
|
|
485
496
|
function dbCapabilitiesHelp() {
|
|
486
497
|
return `Usage: tender app db capabilities <app-id> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
@@ -489,33 +500,35 @@ Examples:
|
|
|
489
500
|
tender app db capabilities artifact_123 --json`;
|
|
490
501
|
}
|
|
491
502
|
function dbTablesHelp() {
|
|
492
|
-
return `Usage: tender app db tables <app-id>
|
|
503
|
+
return `Usage: tender app db tables <app-id> --env <preview|published> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
493
504
|
|
|
494
505
|
Examples:
|
|
495
|
-
tender app db tables artifact_123 --json
|
|
506
|
+
tender app db tables artifact_123 --env published --json
|
|
496
507
|
tender app db tables artifact_123 --env preview --json`;
|
|
497
508
|
}
|
|
498
509
|
function dbSchemaHelp() {
|
|
499
|
-
return `Usage: tender app db schema <app-id> --
|
|
510
|
+
return `Usage: tender app db schema <app-id> --env <preview|published> --table <name> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
500
511
|
|
|
501
512
|
Examples:
|
|
502
|
-
tender app db schema artifact_123 --table votes --json
|
|
513
|
+
tender app db schema artifact_123 --env published --table votes --json
|
|
503
514
|
tender app db schema artifact_123 --table preference_votes --env preview --json`;
|
|
504
515
|
}
|
|
505
516
|
function dbQueryHelp() {
|
|
506
|
-
return `Usage: tender app db query <app-id> (--sql <sql> | --file <path|->) [--param <value>] [--limit <n>] [--
|
|
517
|
+
return `Usage: tender app db query <app-id> --env <preview|published> (--sql <sql> | --file <path|->) [--param <value>] [--limit <n>] [--offset <n>] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
507
518
|
|
|
508
519
|
Options:
|
|
520
|
+
--env <value> Target app environment: preview or published. Required.
|
|
509
521
|
--sql <sql> SQL statement. Must be one read-only statement.
|
|
510
522
|
--file <path|-> Read SQL from a file or stdin with -.
|
|
511
|
-
--param <value> Bind one positional parameter. Repeat for multiple params. Values parse as
|
|
512
|
-
--limit <n> Interactive row limit. Server caps the maximum.
|
|
523
|
+
--param <value> Bind one positional parameter. Repeat for multiple params. Values parse as numbers or null; other values are passed as strings.
|
|
524
|
+
--limit <n> Interactive row limit. Server caps the maximum at 50.
|
|
525
|
+
--offset <n> Skip rows before returning results. Server caps the maximum offset.
|
|
513
526
|
|
|
514
527
|
Examples:
|
|
515
|
-
tender app db query artifact_123 --sql "select * from votes limit 20" --json
|
|
516
|
-
tender app db query artifact_123 --sql "select * from votes where choice = ?" --param coffee --json
|
|
517
|
-
tender app db query artifact_123 --file ./debug.sql --limit 50 --json
|
|
518
|
-
tender app db query artifact_123 --file - --json < debug.sql`;
|
|
528
|
+
tender app db query artifact_123 --env published --sql "select * from votes limit 20" --json
|
|
529
|
+
tender app db query artifact_123 --env published --sql "select * from votes where choice = ?" --param coffee --json
|
|
530
|
+
tender app db query artifact_123 --env published --file ./debug.sql --limit 50 --offset 100 --json
|
|
531
|
+
tender app db query artifact_123 --env published --file - --json < debug.sql`;
|
|
519
532
|
}
|
|
520
533
|
function artifactsListHelp() {
|
|
521
534
|
return `Usage: tender app list [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
@@ -921,10 +934,10 @@ function tenderCliCapabilities() {
|
|
|
921
934
|
commands: [
|
|
922
935
|
"tender auth login --device --profile db-read --artifact <artifact-id> --ttl 7d --save-profile db --json",
|
|
923
936
|
"tender app db capabilities <artifact-id> --profile db --json",
|
|
924
|
-
"tender app db tables <artifact-id> --profile db --json",
|
|
925
|
-
"tender app db schema <artifact-id> --table <table-name> --profile db --json",
|
|
926
|
-
"tender app db query <artifact-id> --sql \"select * from <table-name> limit 20\" --profile db --json",
|
|
927
|
-
"tender app db query <artifact-id> --file ./debug.sql --profile db --json",
|
|
937
|
+
"tender app db tables <artifact-id> --env published --profile db --json",
|
|
938
|
+
"tender app db schema <artifact-id> --env published --table <table-name> --profile db --json",
|
|
939
|
+
"tender app db query <artifact-id> --env published --sql \"select * from <table-name> limit 20\" --profile db --json",
|
|
940
|
+
"tender app db query <artifact-id> --env published --file ./debug.sql --profile db --json",
|
|
928
941
|
],
|
|
929
942
|
notes: [
|
|
930
943
|
"The CLI only accepts artifact ids. It never accepts tenant ids, runner names, provider account ids, or runtime storage identifiers.",
|
|
@@ -1032,19 +1045,19 @@ function tenderCliCapabilities() {
|
|
|
1032
1045
|
writes: false,
|
|
1033
1046
|
},
|
|
1034
1047
|
{
|
|
1035
|
-
command: "tender app db tables <artifact-id> --json",
|
|
1048
|
+
command: "tender app db tables <artifact-id> --env published --json",
|
|
1036
1049
|
purpose: "List app database tables and views for one artifact.",
|
|
1037
1050
|
requiresAuth: true,
|
|
1038
1051
|
writes: false,
|
|
1039
1052
|
},
|
|
1040
1053
|
{
|
|
1041
|
-
command: "tender app db schema <artifact-id> --table <table-name> --json",
|
|
1054
|
+
command: "tender app db schema <artifact-id> --env published --table <table-name> --json",
|
|
1042
1055
|
purpose: "Inspect table, index, and trigger schema for one app database table.",
|
|
1043
1056
|
requiresAuth: true,
|
|
1044
1057
|
writes: false,
|
|
1045
1058
|
},
|
|
1046
1059
|
{
|
|
1047
|
-
command: "tender app db query <artifact-id> --sql \"select * from <table-name> limit 20\" --json",
|
|
1060
|
+
command: "tender app db query <artifact-id> --env published --sql \"select * from <table-name> limit 20\" --json",
|
|
1048
1061
|
purpose: "Run one bounded read-only app database query.",
|
|
1049
1062
|
requiresAuth: true,
|
|
1050
1063
|
writes: false,
|
|
@@ -2402,6 +2415,13 @@ function parsePositiveIntegerFlag(value, flag) {
|
|
|
2402
2415
|
}
|
|
2403
2416
|
return parsed;
|
|
2404
2417
|
}
|
|
2418
|
+
function parseNonNegativeIntegerFlag(value, flag) {
|
|
2419
|
+
const parsed = Number(value);
|
|
2420
|
+
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
2421
|
+
throw new TenderCliUsageError(`${flag} requires a non-negative integer.`);
|
|
2422
|
+
}
|
|
2423
|
+
return parsed;
|
|
2424
|
+
}
|
|
2405
2425
|
function parseAuthLoginArgs(args) {
|
|
2406
2426
|
if (args.includes("--help") || args.includes("-h")) {
|
|
2407
2427
|
return { command: "help", topic: "auth login" };
|
|
@@ -3457,10 +3477,23 @@ function parseDbCommonArgs(args, startIndex) {
|
|
|
3457
3477
|
return { baseUrl, flagToken, profileName, json, remaining };
|
|
3458
3478
|
}
|
|
3459
3479
|
function parseDbEnvironment(value) {
|
|
3460
|
-
if (value
|
|
3480
|
+
if (!value || value.startsWith("--")) {
|
|
3481
|
+
throw new TenderCliUsageError("--env requires a value. Use preview or published.");
|
|
3482
|
+
}
|
|
3483
|
+
if (value === "preview") {
|
|
3461
3484
|
return "preview";
|
|
3462
3485
|
}
|
|
3463
|
-
|
|
3486
|
+
if (value === "published" ||
|
|
3487
|
+
value === "live" ||
|
|
3488
|
+
value === "production" ||
|
|
3489
|
+
value === "prod") {
|
|
3490
|
+
return "published";
|
|
3491
|
+
}
|
|
3492
|
+
throw new TenderCliUsageError("--env must be preview or published.");
|
|
3493
|
+
}
|
|
3494
|
+
function parseFlagAssignment(arg, flag) {
|
|
3495
|
+
const prefix = `${flag}=`;
|
|
3496
|
+
return arg.startsWith(prefix) ? arg.slice(prefix.length) : null;
|
|
3464
3497
|
}
|
|
3465
3498
|
function parseDbFlagValue(value, flag) {
|
|
3466
3499
|
if (!value || value.startsWith("--")) {
|
|
@@ -3468,6 +3501,12 @@ function parseDbFlagValue(value, flag) {
|
|
|
3468
3501
|
}
|
|
3469
3502
|
return value;
|
|
3470
3503
|
}
|
|
3504
|
+
function parseDbSqlFlagValue(value) {
|
|
3505
|
+
if (value === undefined || value.length === 0) {
|
|
3506
|
+
throw new TenderCliUsageError("--sql requires a value.");
|
|
3507
|
+
}
|
|
3508
|
+
return value;
|
|
3509
|
+
}
|
|
3471
3510
|
function parseDbFilePath(value) {
|
|
3472
3511
|
if (!value || (value !== "-" && value.startsWith("-"))) {
|
|
3473
3512
|
throw new TenderCliUsageError("--file requires a SQL file path or - for stdin.");
|
|
@@ -3509,12 +3548,17 @@ function parseDbTablesArgs(args) {
|
|
|
3509
3548
|
}
|
|
3510
3549
|
const artifactId = args[0];
|
|
3511
3550
|
if (!artifactId || artifactId.startsWith("-")) {
|
|
3512
|
-
throw new TenderCliUsageError("app id is required. Example: tender app db tables <app-id> --json");
|
|
3551
|
+
throw new TenderCliUsageError("app id is required. Example: tender app db tables <app-id> --env published --json");
|
|
3513
3552
|
}
|
|
3514
3553
|
const parsed = parseDbCommonArgs(args, 1);
|
|
3515
|
-
let environment =
|
|
3554
|
+
let environment = null;
|
|
3516
3555
|
for (let index = 0; index < parsed.remaining.length; index += 1) {
|
|
3517
3556
|
const arg = parsed.remaining[index];
|
|
3557
|
+
const envAssignment = parseFlagAssignment(arg, "--env");
|
|
3558
|
+
if (envAssignment !== null) {
|
|
3559
|
+
environment = parseDbEnvironment(envAssignment);
|
|
3560
|
+
continue;
|
|
3561
|
+
}
|
|
3518
3562
|
if (arg === "--env") {
|
|
3519
3563
|
environment = parseDbEnvironment(parsed.remaining[index + 1]);
|
|
3520
3564
|
index += 1;
|
|
@@ -3522,6 +3566,9 @@ function parseDbTablesArgs(args) {
|
|
|
3522
3566
|
}
|
|
3523
3567
|
throw new TenderCliUsageError(`Unknown app db tables option: ${arg}`);
|
|
3524
3568
|
}
|
|
3569
|
+
if (!environment) {
|
|
3570
|
+
throw new TenderCliUsageError("--env is required. Example: tender app db tables artifact_123 --env published --json");
|
|
3571
|
+
}
|
|
3525
3572
|
return { command: "app db tables", artifactId, environment, ...parsed };
|
|
3526
3573
|
}
|
|
3527
3574
|
function parseDbSchemaArgs(args) {
|
|
@@ -3530,13 +3577,23 @@ function parseDbSchemaArgs(args) {
|
|
|
3530
3577
|
}
|
|
3531
3578
|
const artifactId = args[0];
|
|
3532
3579
|
if (!artifactId || artifactId.startsWith("-")) {
|
|
3533
|
-
throw new TenderCliUsageError("app id is required. Example: tender app db schema <app-id> --table votes --json");
|
|
3580
|
+
throw new TenderCliUsageError("app id is required. Example: tender app db schema <app-id> --env published --table votes --json");
|
|
3534
3581
|
}
|
|
3535
3582
|
const parsed = parseDbCommonArgs(args, 1);
|
|
3536
|
-
let environment =
|
|
3583
|
+
let environment = null;
|
|
3537
3584
|
let tableName = null;
|
|
3538
3585
|
for (let index = 0; index < parsed.remaining.length; index += 1) {
|
|
3539
3586
|
const arg = parsed.remaining[index];
|
|
3587
|
+
const envAssignment = parseFlagAssignment(arg, "--env");
|
|
3588
|
+
if (envAssignment !== null) {
|
|
3589
|
+
environment = parseDbEnvironment(envAssignment);
|
|
3590
|
+
continue;
|
|
3591
|
+
}
|
|
3592
|
+
const tableAssignment = parseFlagAssignment(arg, "--table");
|
|
3593
|
+
if (tableAssignment !== null) {
|
|
3594
|
+
tableName = parseRequiredFlagValue(tableAssignment, "--table");
|
|
3595
|
+
continue;
|
|
3596
|
+
}
|
|
3540
3597
|
if (arg === "--env") {
|
|
3541
3598
|
environment = parseDbEnvironment(parsed.remaining[index + 1]);
|
|
3542
3599
|
index += 1;
|
|
@@ -3549,8 +3606,11 @@ function parseDbSchemaArgs(args) {
|
|
|
3549
3606
|
}
|
|
3550
3607
|
throw new TenderCliUsageError(`Unknown app db schema option: ${arg}`);
|
|
3551
3608
|
}
|
|
3609
|
+
if (!environment) {
|
|
3610
|
+
throw new TenderCliUsageError("--env is required. Example: tender app db schema artifact_123 --env published --table votes --json");
|
|
3611
|
+
}
|
|
3552
3612
|
if (!tableName) {
|
|
3553
|
-
throw new TenderCliUsageError("--table is required. Example: tender app db schema artifact_123 --table votes --json");
|
|
3613
|
+
throw new TenderCliUsageError("--table is required. Example: tender app db schema artifact_123 --env published --table votes --json");
|
|
3554
3614
|
}
|
|
3555
3615
|
return {
|
|
3556
3616
|
command: "app db schema",
|
|
@@ -3566,15 +3626,58 @@ function parseDbQueryArgs(args) {
|
|
|
3566
3626
|
}
|
|
3567
3627
|
const artifactId = args[0];
|
|
3568
3628
|
if (!artifactId || artifactId.startsWith("-")) {
|
|
3569
|
-
throw new TenderCliUsageError("app id is required. Example: tender app db query <app-id> --sql \"select * from votes limit 20\" --json");
|
|
3629
|
+
throw new TenderCliUsageError("app id is required. Example: tender app db query <app-id> --env published --sql \"select * from votes limit 20\" --json");
|
|
3570
3630
|
}
|
|
3571
3631
|
const parsed = parseDbCommonArgs(args, 1);
|
|
3572
|
-
let environment =
|
|
3632
|
+
let environment = null;
|
|
3573
3633
|
let sqlSource = null;
|
|
3574
3634
|
const params = [];
|
|
3575
3635
|
let limit = null;
|
|
3636
|
+
let offset = 0;
|
|
3576
3637
|
for (let index = 0; index < parsed.remaining.length; index += 1) {
|
|
3577
3638
|
const arg = parsed.remaining[index];
|
|
3639
|
+
const envAssignment = parseFlagAssignment(arg, "--env");
|
|
3640
|
+
if (envAssignment !== null) {
|
|
3641
|
+
environment = parseDbEnvironment(envAssignment);
|
|
3642
|
+
continue;
|
|
3643
|
+
}
|
|
3644
|
+
const sqlAssignment = parseFlagAssignment(arg, "--sql");
|
|
3645
|
+
if (sqlAssignment !== null) {
|
|
3646
|
+
if (sqlSource) {
|
|
3647
|
+
throw new TenderCliUsageError("--sql and --file cannot be repeated or combined.");
|
|
3648
|
+
}
|
|
3649
|
+
sqlSource = {
|
|
3650
|
+
kind: "inline",
|
|
3651
|
+
value: parseDbSqlFlagValue(sqlAssignment),
|
|
3652
|
+
};
|
|
3653
|
+
continue;
|
|
3654
|
+
}
|
|
3655
|
+
const fileAssignment = parseFlagAssignment(arg, "--file");
|
|
3656
|
+
if (fileAssignment !== null) {
|
|
3657
|
+
if (sqlSource) {
|
|
3658
|
+
throw new TenderCliUsageError("--sql and --file cannot be repeated or combined.");
|
|
3659
|
+
}
|
|
3660
|
+
sqlSource = {
|
|
3661
|
+
kind: "file",
|
|
3662
|
+
value: parseDbFilePath(fileAssignment),
|
|
3663
|
+
};
|
|
3664
|
+
continue;
|
|
3665
|
+
}
|
|
3666
|
+
const paramAssignment = parseFlagAssignment(arg, "--param");
|
|
3667
|
+
if (paramAssignment !== null) {
|
|
3668
|
+
params.push(parseDbParameter(paramAssignment));
|
|
3669
|
+
continue;
|
|
3670
|
+
}
|
|
3671
|
+
const limitAssignment = parseFlagAssignment(arg, "--limit");
|
|
3672
|
+
if (limitAssignment !== null) {
|
|
3673
|
+
limit = parsePositiveIntegerFlag(limitAssignment, "--limit");
|
|
3674
|
+
continue;
|
|
3675
|
+
}
|
|
3676
|
+
const offsetAssignment = parseFlagAssignment(arg, "--offset");
|
|
3677
|
+
if (offsetAssignment !== null) {
|
|
3678
|
+
offset = parseNonNegativeIntegerFlag(offsetAssignment, "--offset");
|
|
3679
|
+
continue;
|
|
3680
|
+
}
|
|
3578
3681
|
if (arg === "--env") {
|
|
3579
3682
|
environment = parseDbEnvironment(parsed.remaining[index + 1]);
|
|
3580
3683
|
index += 1;
|
|
@@ -3586,7 +3689,7 @@ function parseDbQueryArgs(args) {
|
|
|
3586
3689
|
}
|
|
3587
3690
|
sqlSource = {
|
|
3588
3691
|
kind: "inline",
|
|
3589
|
-
value:
|
|
3692
|
+
value: parseDbSqlFlagValue(parsed.remaining[index + 1]),
|
|
3590
3693
|
};
|
|
3591
3694
|
index += 1;
|
|
3592
3695
|
continue;
|
|
@@ -3612,10 +3715,18 @@ function parseDbQueryArgs(args) {
|
|
|
3612
3715
|
index += 1;
|
|
3613
3716
|
continue;
|
|
3614
3717
|
}
|
|
3718
|
+
if (arg === "--offset") {
|
|
3719
|
+
offset = parseNonNegativeIntegerFlag(parsed.remaining[index + 1], "--offset");
|
|
3720
|
+
index += 1;
|
|
3721
|
+
continue;
|
|
3722
|
+
}
|
|
3615
3723
|
throw new TenderCliUsageError(`Unknown app db query option: ${arg}`);
|
|
3616
3724
|
}
|
|
3725
|
+
if (!environment) {
|
|
3726
|
+
throw new TenderCliUsageError("--env is required. Example: tender app db query artifact_123 --env published --sql \"select * from votes limit 20\" --json");
|
|
3727
|
+
}
|
|
3617
3728
|
if (!sqlSource) {
|
|
3618
|
-
throw new TenderCliUsageError("--sql or --file is required. Example: tender app db query artifact_123 --sql \"select * from votes limit 20\" --json");
|
|
3729
|
+
throw new TenderCliUsageError("--sql or --file is required. Example: tender app db query artifact_123 --env published --sql \"select * from votes limit 20\" --json");
|
|
3619
3730
|
}
|
|
3620
3731
|
if (params.length > DB_MAX_BOUND_PARAMETERS) {
|
|
3621
3732
|
throw new TenderCliUsageError(`App database queries are limited to ${DB_MAX_BOUND_PARAMETERS} bound parameters.`);
|
|
@@ -3627,6 +3738,7 @@ function parseDbQueryArgs(args) {
|
|
|
3627
3738
|
sqlSource,
|
|
3628
3739
|
params,
|
|
3629
3740
|
limit,
|
|
3741
|
+
offset,
|
|
3630
3742
|
...parsed,
|
|
3631
3743
|
};
|
|
3632
3744
|
}
|
|
@@ -4192,12 +4304,16 @@ async function requestAnalyticsApi(input) {
|
|
|
4192
4304
|
const message = (typeof payload?.message === "string" && payload.message) ||
|
|
4193
4305
|
(typeof payload?.reasonCode === "string" && payload.reasonCode) ||
|
|
4194
4306
|
`Analytics request failed with HTTP ${response.status}.`;
|
|
4307
|
+
const example = analyticsCorrectedExample(input.artifactId, input.analyticsPath);
|
|
4195
4308
|
throw new TenderCliAnalyticsApiError([
|
|
4196
4309
|
message,
|
|
4197
4310
|
`Scope: artifact ${input.artifactId}, analytics/${input.analyticsPath || "summary"}.`,
|
|
4198
4311
|
`Reason: ${reasonCode}.`,
|
|
4199
|
-
`Example: ${
|
|
4200
|
-
].join("\n"), reasonCode
|
|
4312
|
+
`Example: ${example}.`,
|
|
4313
|
+
].join("\n"), reasonCode, {
|
|
4314
|
+
artifactId: input.artifactId,
|
|
4315
|
+
surface: `analytics/${input.analyticsPath || "summary"}`,
|
|
4316
|
+
}, example);
|
|
4201
4317
|
}
|
|
4202
4318
|
return payload;
|
|
4203
4319
|
}
|
|
@@ -4229,12 +4345,16 @@ async function requestDbApi(input) {
|
|
|
4229
4345
|
const message = (typeof payload?.message === "string" && payload.message) ||
|
|
4230
4346
|
(typeof payload?.reasonCode === "string" && payload.reasonCode) ||
|
|
4231
4347
|
`App database request failed with HTTP ${response.status}.`;
|
|
4348
|
+
const example = dbCorrectedExample(input.artifactId, input.dbPath);
|
|
4232
4349
|
throw new TenderCliDbApiError([
|
|
4233
4350
|
message,
|
|
4234
4351
|
`Scope: artifact ${input.artifactId}, db/${input.dbPath}.`,
|
|
4235
4352
|
`Reason: ${reasonCode}.`,
|
|
4236
|
-
`Example: ${
|
|
4237
|
-
].join("\n"), reasonCode
|
|
4353
|
+
`Example: ${example}.`,
|
|
4354
|
+
].join("\n"), reasonCode, {
|
|
4355
|
+
artifactId: input.artifactId,
|
|
4356
|
+
surface: `db/${input.dbPath}`,
|
|
4357
|
+
}, example);
|
|
4238
4358
|
}
|
|
4239
4359
|
return payload;
|
|
4240
4360
|
}
|
|
@@ -4243,12 +4363,12 @@ function dbCorrectedExample(artifactId, dbPath) {
|
|
|
4243
4363
|
return `tender app db capabilities ${artifactId} --json`;
|
|
4244
4364
|
}
|
|
4245
4365
|
if (dbPath === "tables") {
|
|
4246
|
-
return `tender app db tables ${artifactId} --json`;
|
|
4366
|
+
return `tender app db tables ${artifactId} --env published --json`;
|
|
4247
4367
|
}
|
|
4248
4368
|
if (dbPath === "schema") {
|
|
4249
|
-
return `tender app db schema ${artifactId} --table <table-name> --json`;
|
|
4369
|
+
return `tender app db schema ${artifactId} --env published --table <table-name> --json`;
|
|
4250
4370
|
}
|
|
4251
|
-
return `tender app db query ${artifactId} --sql "select * from <table-name> limit 20" --json`;
|
|
4371
|
+
return `tender app db query ${artifactId} --env published --sql "select * from <table-name> limit 20" --json`;
|
|
4252
4372
|
}
|
|
4253
4373
|
function analyticsCorrectedExample(artifactId, analyticsPath) {
|
|
4254
4374
|
if (analyticsPath === "capabilities") {
|
|
@@ -4497,7 +4617,7 @@ async function requestAnalyticsQueryWithPropertyGuidance(input) {
|
|
|
4497
4617
|
spec: input.spec,
|
|
4498
4618
|
});
|
|
4499
4619
|
if (guidance) {
|
|
4500
|
-
throw new TenderCliAnalyticsApiError(`${error.message}\n\n${guidance}`, error.reasonCode);
|
|
4620
|
+
throw new TenderCliAnalyticsApiError(`${error.message}\n\n${guidance}`, error.reasonCode, error.scope, error.example);
|
|
4501
4621
|
}
|
|
4502
4622
|
}
|
|
4503
4623
|
throw error;
|
|
@@ -6641,6 +6761,7 @@ async function runDbQuery(command, io, runtime) {
|
|
|
6641
6761
|
sql,
|
|
6642
6762
|
params: command.params,
|
|
6643
6763
|
...(command.limit === null ? {} : { limit: command.limit }),
|
|
6764
|
+
offset: command.offset,
|
|
6644
6765
|
};
|
|
6645
6766
|
const payload = await requestDbApi({
|
|
6646
6767
|
artifactId: command.artifactId,
|
|
@@ -6656,7 +6777,12 @@ async function runDbQuery(command, io, runtime) {
|
|
|
6656
6777
|
`app_id: ${command.artifactId}`,
|
|
6657
6778
|
`environment: ${command.environment}`,
|
|
6658
6779
|
`rows: ${rows.length}`,
|
|
6780
|
+
`offset: ${String(payload.offset ?? command.offset)}`,
|
|
6781
|
+
`applied_limit: ${String(payload.appliedLimit ?? command.limit ?? "")}`,
|
|
6659
6782
|
`truncated: ${String(payload.truncated ?? false)}`,
|
|
6783
|
+
...(typeof payload.nextOffset === "number"
|
|
6784
|
+
? [`next_offset: ${payload.nextOffset}`]
|
|
6785
|
+
: []),
|
|
6660
6786
|
]);
|
|
6661
6787
|
return 0;
|
|
6662
6788
|
}
|
|
@@ -7063,12 +7189,106 @@ async function runAnalyticsChartsDelete(command, io, runtime) {
|
|
|
7063
7189
|
]);
|
|
7064
7190
|
return 0;
|
|
7065
7191
|
}
|
|
7192
|
+
function cliErrorMessage(error) {
|
|
7193
|
+
if (error instanceof Error) {
|
|
7194
|
+
return error.message.split("\n")[0] ?? error.message;
|
|
7195
|
+
}
|
|
7196
|
+
return String(error);
|
|
7197
|
+
}
|
|
7198
|
+
function cliErrorCode(error) {
|
|
7199
|
+
if (error instanceof TenderCliDbApiError || error instanceof TenderCliAnalyticsApiError) {
|
|
7200
|
+
return error.reasonCode;
|
|
7201
|
+
}
|
|
7202
|
+
if (error && typeof error === "object" && "code" in error) {
|
|
7203
|
+
const code = error.code;
|
|
7204
|
+
if (code === "ENOENT") {
|
|
7205
|
+
return "file_not_found";
|
|
7206
|
+
}
|
|
7207
|
+
if (typeof code === "string" && code.length > 0) {
|
|
7208
|
+
return code.toLowerCase();
|
|
7209
|
+
}
|
|
7210
|
+
}
|
|
7211
|
+
return error instanceof TenderCliUsageError ? "cli_usage_error" : "cli_error";
|
|
7212
|
+
}
|
|
7213
|
+
function commandNameForError(args, command) {
|
|
7214
|
+
if (command) {
|
|
7215
|
+
return command.command === "help" ? `help ${command.topic}` : command.command;
|
|
7216
|
+
}
|
|
7217
|
+
if (args[0] === "app" && args[1] === "db" && args[2]) {
|
|
7218
|
+
return `app db ${args[2]}`;
|
|
7219
|
+
}
|
|
7220
|
+
if (args[0] === "app" && args[1]) {
|
|
7221
|
+
return `app ${args[1]}`;
|
|
7222
|
+
}
|
|
7223
|
+
return args[0] ?? "unknown";
|
|
7224
|
+
}
|
|
7225
|
+
function dbPathFromCommandName(commandName) {
|
|
7226
|
+
if (commandName === "app db capabilities") {
|
|
7227
|
+
return "capabilities";
|
|
7228
|
+
}
|
|
7229
|
+
if (commandName === "app db tables") {
|
|
7230
|
+
return "tables";
|
|
7231
|
+
}
|
|
7232
|
+
if (commandName === "app db schema") {
|
|
7233
|
+
return "schema";
|
|
7234
|
+
}
|
|
7235
|
+
if (commandName === "app db query") {
|
|
7236
|
+
return "query";
|
|
7237
|
+
}
|
|
7238
|
+
return null;
|
|
7239
|
+
}
|
|
7240
|
+
function artifactIdForError(args, command) {
|
|
7241
|
+
if (command && "artifactId" in command) {
|
|
7242
|
+
return command.artifactId;
|
|
7243
|
+
}
|
|
7244
|
+
if (args[0] === "app" && args[1] === "db" && args[3] && !args[3].startsWith("-")) {
|
|
7245
|
+
return args[3];
|
|
7246
|
+
}
|
|
7247
|
+
return null;
|
|
7248
|
+
}
|
|
7249
|
+
function scopeForError(error, args, command) {
|
|
7250
|
+
if (error instanceof TenderCliDbApiError || error instanceof TenderCliAnalyticsApiError) {
|
|
7251
|
+
return error.scope;
|
|
7252
|
+
}
|
|
7253
|
+
const commandName = commandNameForError(args, command);
|
|
7254
|
+
const dbPath = dbPathFromCommandName(commandName);
|
|
7255
|
+
const artifactId = artifactIdForError(args, command);
|
|
7256
|
+
if (dbPath && artifactId) {
|
|
7257
|
+
return {
|
|
7258
|
+
artifactId,
|
|
7259
|
+
surface: `db/${dbPath}`,
|
|
7260
|
+
};
|
|
7261
|
+
}
|
|
7262
|
+
return null;
|
|
7263
|
+
}
|
|
7264
|
+
function exampleForError(error, args, command) {
|
|
7265
|
+
if (error instanceof TenderCliDbApiError || error instanceof TenderCliAnalyticsApiError) {
|
|
7266
|
+
return error.example;
|
|
7267
|
+
}
|
|
7268
|
+
const commandName = commandNameForError(args, command);
|
|
7269
|
+
const dbPath = dbPathFromCommandName(commandName);
|
|
7270
|
+
const artifactId = artifactIdForError(args, command) ?? "artifact_123";
|
|
7271
|
+
return dbPath ? dbCorrectedExample(artifactId, dbPath) : null;
|
|
7272
|
+
}
|
|
7273
|
+
function cliErrorPayload(error, args, command) {
|
|
7274
|
+
const scope = scopeForError(error, args, command);
|
|
7275
|
+
const example = exampleForError(error, args, command);
|
|
7276
|
+
return {
|
|
7277
|
+
ok: false,
|
|
7278
|
+
command: commandNameForError(args, command),
|
|
7279
|
+
reasonCode: cliErrorCode(error),
|
|
7280
|
+
message: cliErrorMessage(error),
|
|
7281
|
+
...(scope ? { scope } : {}),
|
|
7282
|
+
...(example ? { example } : {}),
|
|
7283
|
+
};
|
|
7284
|
+
}
|
|
7066
7285
|
export async function runTenderCli(args, io = {
|
|
7067
7286
|
stdout: (message) => console.log(message),
|
|
7068
7287
|
stderr: (message) => console.error(message),
|
|
7069
7288
|
}, runtime = {}) {
|
|
7289
|
+
let command = null;
|
|
7070
7290
|
try {
|
|
7071
|
-
|
|
7291
|
+
command = parseTenderArgs(args);
|
|
7072
7292
|
if (command.command === "help") {
|
|
7073
7293
|
if (command.topic === "capabilities") {
|
|
7074
7294
|
io.stdout(capabilitiesHelp());
|
|
@@ -7342,6 +7562,10 @@ export async function runTenderCli(args, io = {
|
|
|
7342
7562
|
return await runDoctor(command, io);
|
|
7343
7563
|
}
|
|
7344
7564
|
catch (error) {
|
|
7565
|
+
if (args.includes("--json") && args[0] === "app" && args[1] === "db") {
|
|
7566
|
+
io.stdout(JSON.stringify(cliErrorPayload(error, args, command), null, 2));
|
|
7567
|
+
return error instanceof TenderCliUsageError ? 2 : 1;
|
|
7568
|
+
}
|
|
7345
7569
|
io.stderr(error instanceof Error ? error.message : String(error));
|
|
7346
7570
|
io.stderr("Run `tender --help` for examples.");
|
|
7347
7571
|
return error instanceof TenderCliUsageError ? 2 : 1;
|
|
@@ -7349,7 +7573,7 @@ export async function runTenderCli(args, io = {
|
|
|
7349
7573
|
}
|
|
7350
7574
|
const currentFile = fileURLToPath(import.meta.url);
|
|
7351
7575
|
function hasFlagValue(args, flag, value) {
|
|
7352
|
-
return args.some((arg, index) => arg === flag && args[index + 1] === value);
|
|
7576
|
+
return args.some((arg, index) => (arg === flag && args[index + 1] === value) || arg === `${flag}=${value}`);
|
|
7353
7577
|
}
|
|
7354
7578
|
function shouldReadEntrypointStdin(args) {
|
|
7355
7579
|
return (args[0] === "app" &&
|