@tenderprompt/cli 0.1.17 → 0.1.18
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 +29 -0
- package/dist/index.js +559 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -39,6 +39,8 @@ session:
|
|
|
39
39
|
- inspect artifact-scoped analytics without SQL or platform credentials
|
|
40
40
|
- inspect artifact-scoped app database tables, schema, and bounded read-only
|
|
41
41
|
query output
|
|
42
|
+
- share app setup between Tender accounts without copying customer data,
|
|
43
|
+
analytics history, app database rows, secrets, or release history
|
|
42
44
|
- run server-validated chart specs and export aggregate rows
|
|
43
45
|
- generate starter analytics suggestions
|
|
44
46
|
- create, list, update, and delete saved analytics dashboards and charts, with
|
|
@@ -129,6 +131,9 @@ tender capabilities --json
|
|
|
129
131
|
tender app list --json
|
|
130
132
|
tender app create --name "Exit Intent Widget" --json
|
|
131
133
|
tender app create --name "Exit Intent Widget" --init --dir ./widget --preview --json
|
|
134
|
+
tender app share create artifact_123 --expires 7d --claims 1 --json
|
|
135
|
+
tender app share inspect tok_share_123 --json
|
|
136
|
+
tender app claim tok_share_123 --dir ./widget --confirm source-and-setup --json
|
|
132
137
|
tender app update artifact_123 --name "Sale Widget" --json
|
|
133
138
|
tender app init artifact_123 --dir ./widget --preview --json
|
|
134
139
|
tender app bindings outbound --id shopify --host admin.shopify.com --dry-run --json
|
|
@@ -145,6 +150,30 @@ tender app db query artifact_123 --env published --sql "select * from votes limi
|
|
|
145
150
|
tender app db query artifact_123 --env published --sql "select * from votes order by created_at" --limit 50 --offset 50 --json
|
|
146
151
|
```
|
|
147
152
|
|
|
153
|
+
## Shared app setup
|
|
154
|
+
|
|
155
|
+
Use this for agency-to-customer or collaborator handoffs where the recipient
|
|
156
|
+
should get the source and reusable setup, not the source account's data.
|
|
157
|
+
|
|
158
|
+
Agency side:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
tender app share create artifact_123 --expires 7d --claims 1 --json
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Recipient side:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
tender app share inspect tok_share_123 --json
|
|
168
|
+
tender app claim tok_share_123 --dir ./exit-intent-widget
|
|
169
|
+
tender app claim tok_share_123 --dir ./exit-intent-widget --confirm source-and-setup --json
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Claim copies source code, app setup, and analytics dashboard/chart definitions.
|
|
173
|
+
It does not copy analytics events, aggregates, exports, app database rows,
|
|
174
|
+
secrets, environment values, release history, or ongoing access to the source
|
|
175
|
+
artifact.
|
|
176
|
+
|
|
148
177
|
## Analytics
|
|
149
178
|
|
|
150
179
|
Analytics commands are artifact-scoped and go through Tender's server-side
|
package/dist/index.js
CHANGED
|
@@ -91,6 +91,10 @@ Commands:
|
|
|
91
91
|
Create a new Tender App in the current account
|
|
92
92
|
tender app update <app-id> --name <name>
|
|
93
93
|
Update app metadata
|
|
94
|
+
tender app share create <app-id>
|
|
95
|
+
Create a claimable app setup share token
|
|
96
|
+
tender app claim <token>
|
|
97
|
+
Claim shared app setup into the current account
|
|
94
98
|
tender app init <app-id>
|
|
95
99
|
Bootstrap a checkout for local agent work
|
|
96
100
|
tender app validate <app-id>
|
|
@@ -145,6 +149,8 @@ Examples:
|
|
|
145
149
|
tender app create --name "Exit Intent Widget" --json
|
|
146
150
|
tender app create --name "Exit Intent Widget" --init --dir ./widget --preview --json
|
|
147
151
|
tender app update artifact_123 --name "Sale Widget" --json
|
|
152
|
+
tender app share create artifact_123 --expires 7d --claims 1 --json
|
|
153
|
+
tender app claim tok_share_123 --dir ./widget --confirm source-and-setup --json
|
|
148
154
|
tender app init artifact_123 --dir ./widget --preview --json
|
|
149
155
|
tender app validate artifact_123 --json
|
|
150
156
|
tender app build artifact_123 --commit $(git rev-parse HEAD) --json
|
|
@@ -262,6 +268,14 @@ Commands:
|
|
|
262
268
|
Create a new Tender App, optionally initialize source, and preview
|
|
263
269
|
tender app update <app-id> --name <name>
|
|
264
270
|
Update app metadata
|
|
271
|
+
tender app share create <app-id>
|
|
272
|
+
Create a claimable app setup share token
|
|
273
|
+
tender app share inspect <token>
|
|
274
|
+
Inspect a shared app setup token without claiming it
|
|
275
|
+
tender app share revoke <share-id>
|
|
276
|
+
Revoke a shared app setup token
|
|
277
|
+
tender app claim <token>
|
|
278
|
+
Claim shared app setup into the current account
|
|
265
279
|
tender app init <app-id>
|
|
266
280
|
Bootstrap context files and the tender Git remote
|
|
267
281
|
tender app validate <app-id>
|
|
@@ -331,6 +345,9 @@ Examples:
|
|
|
331
345
|
tender app create --name "Exit Intent Widget" --json
|
|
332
346
|
tender app create --name "Exit Intent Widget" --init --dir ./widget --preview --json
|
|
333
347
|
tender app update artifact_123 --name "Sale Widget" --json
|
|
348
|
+
tender app share create artifact_123 --expires 7d --claims 1 --json
|
|
349
|
+
tender app share inspect tok_share_123 --json
|
|
350
|
+
tender app claim tok_share_123 --dir ./widget --confirm source-and-setup --json
|
|
334
351
|
tender app init artifact_123 --dir ./widget --preview --json
|
|
335
352
|
tender app validate artifact_123 --json
|
|
336
353
|
tender app build artifact_123 --commit $(git rev-parse HEAD) --json
|
|
@@ -366,6 +383,53 @@ Examples:
|
|
|
366
383
|
tender app doctor --json
|
|
367
384
|
tender app git remote artifact_123 --ttl 7d --json`;
|
|
368
385
|
}
|
|
386
|
+
function appShareHelp() {
|
|
387
|
+
return `Usage: tender app share <command> [options]
|
|
388
|
+
|
|
389
|
+
Commands:
|
|
390
|
+
tender app share create <app-id> Create a claimable app setup token
|
|
391
|
+
tender app share inspect <token> Inspect token metadata before claiming
|
|
392
|
+
tender app share revoke <share-id> Revoke a share token
|
|
393
|
+
|
|
394
|
+
Examples:
|
|
395
|
+
tender app share create artifact_123 --expires 7d --claims 1 --json
|
|
396
|
+
tender app share inspect tok_share_123 --json
|
|
397
|
+
tender app share revoke app_setup_share_123 --json`;
|
|
398
|
+
}
|
|
399
|
+
function appShareCreateHelp() {
|
|
400
|
+
return `Usage: tender app share create <app-id> [--expires <7d|24h>] [--claims <n>] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
401
|
+
|
|
402
|
+
Creates a claimable app setup token. The share includes source code, app setup,
|
|
403
|
+
and analytics dashboard/chart definitions. It does not include customer data,
|
|
404
|
+
analytics history, app database rows, secrets, or release history.
|
|
405
|
+
|
|
406
|
+
Examples:
|
|
407
|
+
tender app share create artifact_123 --expires 7d --claims 1 --json`;
|
|
408
|
+
}
|
|
409
|
+
function appShareInspectHelp() {
|
|
410
|
+
return `Usage: tender app share inspect <token> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
411
|
+
|
|
412
|
+
Examples:
|
|
413
|
+
tender app share inspect tok_share_123 --json`;
|
|
414
|
+
}
|
|
415
|
+
function appShareRevokeHelp() {
|
|
416
|
+
return `Usage: tender app share revoke <share-id> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
417
|
+
|
|
418
|
+
Examples:
|
|
419
|
+
tender app share revoke app_setup_share_123 --json`;
|
|
420
|
+
}
|
|
421
|
+
function appClaimHelp() {
|
|
422
|
+
return `Usage: tender app claim <token> --dir <path> [--name <name>] [--confirm source-and-setup] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
423
|
+
|
|
424
|
+
Claims shared app setup into the authenticated Tender account, creates a new app,
|
|
425
|
+
materializes source files into the local directory, and configures the Tender Git
|
|
426
|
+
remote. Without --confirm source-and-setup, the command only previews what will
|
|
427
|
+
happen.
|
|
428
|
+
|
|
429
|
+
Examples:
|
|
430
|
+
tender app claim tok_share_123 --dir ./exit-intent-widget
|
|
431
|
+
tender app claim tok_share_123 --dir ./exit-intent-widget --confirm source-and-setup --json`;
|
|
432
|
+
}
|
|
369
433
|
function capabilitiesHelp() {
|
|
370
434
|
return `Usage: tender capabilities [--json]
|
|
371
435
|
|
|
@@ -772,7 +836,7 @@ Options:
|
|
|
772
836
|
--token <token> Tender API token. Precedence: --token, explicit --profile, TENDER_API_TOKEN, saved default profile.
|
|
773
837
|
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
774
838
|
--ttl <value> Deprecated for Tender-hosted Git remotes; accepted for compatibility.
|
|
775
|
-
--json Emit stable JSON for coding agents.
|
|
839
|
+
--json Emit stable JSON for coding agents without printing Git passwords.
|
|
776
840
|
|
|
777
841
|
Examples:
|
|
778
842
|
tender app git remote artifact_123 --ttl 7d --json
|
|
@@ -798,11 +862,12 @@ Examples:
|
|
|
798
862
|
tender app git setup artifact_123 --dir ./widget --dry-run --json`;
|
|
799
863
|
}
|
|
800
864
|
function gitCredentialHelp() {
|
|
801
|
-
return `Usage: tender app git credential <app-id> [get|store|erase] [--base-url <url>] [--token <token>] [--profile <name>] [--ttl <7d|14d|30d>] [--remote-url <url>]
|
|
865
|
+
return `Usage: tender app git credential <app-id> [get|store|erase] [--base-url <url>] [--token <token>] [--profile <name>] [--ttl <7d|14d|30d>] [--remote-url <url>] [--json]
|
|
802
866
|
|
|
803
867
|
This command is intended for Git's credential.helper protocol. It prints a
|
|
804
|
-
|
|
805
|
-
|
|
868
|
+
username/password pair only for matching get requests. Do not run it manually
|
|
869
|
+
unless you are debugging Git credential-helper behavior.
|
|
870
|
+
JSON output reports whether a password is available without printing it.
|
|
806
871
|
|
|
807
872
|
Examples:
|
|
808
873
|
tender app git credential artifact_123 get
|
|
@@ -984,6 +1049,8 @@ function tenderCliCapabilities() {
|
|
|
984
1049
|
"tender playbooks --help",
|
|
985
1050
|
"tender app --help",
|
|
986
1051
|
"tender app agent heartbeat --help",
|
|
1052
|
+
"tender app share --help",
|
|
1053
|
+
"tender app claim --help",
|
|
987
1054
|
"tender app analytics --help",
|
|
988
1055
|
"tender app db --help",
|
|
989
1056
|
"tender app bindings outbound --help",
|
|
@@ -1037,6 +1104,20 @@ function tenderCliCapabilities() {
|
|
|
1037
1104
|
"Use context fetch only when you already have a checkout and only need AGENTS.md, skills, or Tender context refreshed.",
|
|
1038
1105
|
],
|
|
1039
1106
|
},
|
|
1107
|
+
{
|
|
1108
|
+
name: "share_app_setup_handoff",
|
|
1109
|
+
when: "When handing off source code and reusable app setup between Tender accounts without copying data.",
|
|
1110
|
+
commands: [
|
|
1111
|
+
"tender app share create <artifact-id> --expires 7d --claims 1 --json",
|
|
1112
|
+
"tender app share inspect <token> --json",
|
|
1113
|
+
"tender app claim <token> --dir <dir> --confirm source-and-setup --json",
|
|
1114
|
+
],
|
|
1115
|
+
notes: [
|
|
1116
|
+
"Share app setup copies source files, app setup, and analytics dashboard/chart definitions.",
|
|
1117
|
+
"It never copies customer data, analytics history, app database rows, secrets, release history, or source account access.",
|
|
1118
|
+
"Run claim without --confirm first when the user wants a preview of what will happen.",
|
|
1119
|
+
],
|
|
1120
|
+
},
|
|
1040
1121
|
{
|
|
1041
1122
|
name: "local_edit_preview_publish",
|
|
1042
1123
|
when: "When editing app source from a local checkout.",
|
|
@@ -1162,6 +1243,24 @@ function tenderCliCapabilities() {
|
|
|
1162
1243
|
writes: true,
|
|
1163
1244
|
dryRun: true,
|
|
1164
1245
|
},
|
|
1246
|
+
{
|
|
1247
|
+
command: "tender app share create <artifact-id> --expires 7d --claims 1 --json",
|
|
1248
|
+
purpose: "Create a claimable app setup token that includes source/setup definitions but no app data.",
|
|
1249
|
+
requiresAuth: true,
|
|
1250
|
+
writes: true,
|
|
1251
|
+
},
|
|
1252
|
+
{
|
|
1253
|
+
command: "tender app share inspect <token> --json",
|
|
1254
|
+
purpose: "Inspect a shared app setup token before claiming it.",
|
|
1255
|
+
requiresAuth: true,
|
|
1256
|
+
writes: false,
|
|
1257
|
+
},
|
|
1258
|
+
{
|
|
1259
|
+
command: "tender app claim <token> --dir <dir> --confirm source-and-setup --json",
|
|
1260
|
+
purpose: "Create a new account-owned app from shared source/setup and materialize its source locally.",
|
|
1261
|
+
requiresAuth: true,
|
|
1262
|
+
writes: true,
|
|
1263
|
+
},
|
|
1165
1264
|
{
|
|
1166
1265
|
command: "tender app agent heartbeat <artifact-id> --source codex --status working --summary <text> --json",
|
|
1167
1266
|
purpose: "Send a concise workspace feedback update from a coding agent.",
|
|
@@ -2417,6 +2516,128 @@ function parseArtifactsUpdateArgs(args) {
|
|
|
2417
2516
|
json,
|
|
2418
2517
|
};
|
|
2419
2518
|
}
|
|
2519
|
+
function parseShareCommonArgs(args, startIndex) {
|
|
2520
|
+
let baseUrl = null;
|
|
2521
|
+
let flagToken = null;
|
|
2522
|
+
let profileName = null;
|
|
2523
|
+
let json = false;
|
|
2524
|
+
const remaining = [];
|
|
2525
|
+
for (let index = startIndex; index < args.length; index += 1) {
|
|
2526
|
+
const arg = args[index];
|
|
2527
|
+
if (arg === "--json") {
|
|
2528
|
+
json = true;
|
|
2529
|
+
continue;
|
|
2530
|
+
}
|
|
2531
|
+
if (arg === "--base-url") {
|
|
2532
|
+
baseUrl = parseBaseUrlFlag(args, index);
|
|
2533
|
+
index += 1;
|
|
2534
|
+
continue;
|
|
2535
|
+
}
|
|
2536
|
+
if (arg === "--token") {
|
|
2537
|
+
flagToken = parseTokenFlag(args, index);
|
|
2538
|
+
index += 1;
|
|
2539
|
+
continue;
|
|
2540
|
+
}
|
|
2541
|
+
if (arg === "--profile") {
|
|
2542
|
+
profileName = parseProfileName(args[index + 1], "--profile");
|
|
2543
|
+
index += 1;
|
|
2544
|
+
continue;
|
|
2545
|
+
}
|
|
2546
|
+
remaining.push(arg);
|
|
2547
|
+
}
|
|
2548
|
+
return { baseUrl, flagToken, profileName, json, remaining };
|
|
2549
|
+
}
|
|
2550
|
+
function parseShareCreateArgs(args) {
|
|
2551
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
2552
|
+
return { command: "help", topic: "app share create" };
|
|
2553
|
+
}
|
|
2554
|
+
const artifactId = args[0];
|
|
2555
|
+
if (!artifactId || artifactId.startsWith("-")) {
|
|
2556
|
+
throw new TenderCliUsageError("app id is required. Example: tender app share create <app-id> --json");
|
|
2557
|
+
}
|
|
2558
|
+
const parsed = parseShareCommonArgs(args, 1);
|
|
2559
|
+
let expires = "7d";
|
|
2560
|
+
let claims = 1;
|
|
2561
|
+
for (let index = 0; index < parsed.remaining.length; index += 1) {
|
|
2562
|
+
const arg = parsed.remaining[index];
|
|
2563
|
+
if (arg === "--expires") {
|
|
2564
|
+
expires = parseRequiredFlagValue(parsed.remaining[index + 1], "--expires");
|
|
2565
|
+
index += 1;
|
|
2566
|
+
continue;
|
|
2567
|
+
}
|
|
2568
|
+
if (arg === "--claims") {
|
|
2569
|
+
claims = parsePositiveIntegerFlag(parsed.remaining[index + 1], "--claims");
|
|
2570
|
+
index += 1;
|
|
2571
|
+
continue;
|
|
2572
|
+
}
|
|
2573
|
+
throw new TenderCliUsageError(`Unknown app share create option: ${arg}`);
|
|
2574
|
+
}
|
|
2575
|
+
return { command: "app share create", artifactId, expires, claims, ...parsed };
|
|
2576
|
+
}
|
|
2577
|
+
function parseShareInspectArgs(args) {
|
|
2578
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
2579
|
+
return { command: "help", topic: "app share inspect" };
|
|
2580
|
+
}
|
|
2581
|
+
const token = args[0];
|
|
2582
|
+
if (!token || token.startsWith("-")) {
|
|
2583
|
+
throw new TenderCliUsageError("share token is required. Example: tender app share inspect <token> --json");
|
|
2584
|
+
}
|
|
2585
|
+
const parsed = parseShareCommonArgs(args, 1);
|
|
2586
|
+
if (parsed.remaining.length > 0) {
|
|
2587
|
+
throw new TenderCliUsageError(`Unknown app share inspect option: ${parsed.remaining[0]}`);
|
|
2588
|
+
}
|
|
2589
|
+
return { command: "app share inspect", token, ...parsed };
|
|
2590
|
+
}
|
|
2591
|
+
function parseShareRevokeArgs(args) {
|
|
2592
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
2593
|
+
return { command: "help", topic: "app share revoke" };
|
|
2594
|
+
}
|
|
2595
|
+
const shareId = args[0];
|
|
2596
|
+
if (!shareId || shareId.startsWith("-")) {
|
|
2597
|
+
throw new TenderCliUsageError("share id is required. Example: tender app share revoke <share-id> --json");
|
|
2598
|
+
}
|
|
2599
|
+
const parsed = parseShareCommonArgs(args, 1);
|
|
2600
|
+
if (parsed.remaining.length > 0) {
|
|
2601
|
+
throw new TenderCliUsageError(`Unknown app share revoke option: ${parsed.remaining[0]}`);
|
|
2602
|
+
}
|
|
2603
|
+
return { command: "app share revoke", shareId, ...parsed };
|
|
2604
|
+
}
|
|
2605
|
+
function parseClaimArgs(args) {
|
|
2606
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
2607
|
+
return { command: "help", topic: "app claim" };
|
|
2608
|
+
}
|
|
2609
|
+
const token = args[0];
|
|
2610
|
+
if (!token || token.startsWith("-")) {
|
|
2611
|
+
throw new TenderCliUsageError("share token is required. Example: tender app claim <token> --dir ./widget");
|
|
2612
|
+
}
|
|
2613
|
+
const parsed = parseShareCommonArgs(args, 1);
|
|
2614
|
+
let dir = null;
|
|
2615
|
+
let name = null;
|
|
2616
|
+
let confirm = null;
|
|
2617
|
+
for (let index = 0; index < parsed.remaining.length; index += 1) {
|
|
2618
|
+
const arg = parsed.remaining[index];
|
|
2619
|
+
if (arg === "--dir") {
|
|
2620
|
+
dir = parseRequiredFlagValue(parsed.remaining[index + 1], "--dir");
|
|
2621
|
+
index += 1;
|
|
2622
|
+
continue;
|
|
2623
|
+
}
|
|
2624
|
+
if (arg === "--name") {
|
|
2625
|
+
name = parseRequiredFlagValue(parsed.remaining[index + 1], "--name");
|
|
2626
|
+
index += 1;
|
|
2627
|
+
continue;
|
|
2628
|
+
}
|
|
2629
|
+
if (arg === "--confirm") {
|
|
2630
|
+
confirm = parseRequiredFlagValue(parsed.remaining[index + 1], "--confirm");
|
|
2631
|
+
index += 1;
|
|
2632
|
+
continue;
|
|
2633
|
+
}
|
|
2634
|
+
throw new TenderCliUsageError(`Unknown app claim option: ${arg}`);
|
|
2635
|
+
}
|
|
2636
|
+
if (!dir) {
|
|
2637
|
+
throw new TenderCliUsageError("--dir is required. Example: tender app claim tok_share_123 --dir ./widget");
|
|
2638
|
+
}
|
|
2639
|
+
return { command: "app claim", token, dir, name, confirm, ...parsed };
|
|
2640
|
+
}
|
|
2420
2641
|
function parseArtifactsInitArgs(args) {
|
|
2421
2642
|
if (args.includes("--help") || args.includes("-h")) {
|
|
2422
2643
|
return { command: "help", topic: "app init" };
|
|
@@ -4429,6 +4650,27 @@ function parseTenderArgs(args) {
|
|
|
4429
4650
|
if (resourceArgs[1] === "update") {
|
|
4430
4651
|
return parseArtifactsUpdateArgs(resourceArgs.slice(2));
|
|
4431
4652
|
}
|
|
4653
|
+
if (resourceArgs[1] === "share" &&
|
|
4654
|
+
(resourceArgs[2] === undefined ||
|
|
4655
|
+
resourceArgs[2] === "--help" ||
|
|
4656
|
+
resourceArgs[2] === "-h")) {
|
|
4657
|
+
return { command: "help", topic: "app share" };
|
|
4658
|
+
}
|
|
4659
|
+
if (resourceArgs[1] === "share" && resourceArgs[2] === "create") {
|
|
4660
|
+
return parseShareCreateArgs(resourceArgs.slice(3));
|
|
4661
|
+
}
|
|
4662
|
+
if (resourceArgs[1] === "share" && resourceArgs[2] === "inspect") {
|
|
4663
|
+
return parseShareInspectArgs(resourceArgs.slice(3));
|
|
4664
|
+
}
|
|
4665
|
+
if (resourceArgs[1] === "share" && resourceArgs[2] === "revoke") {
|
|
4666
|
+
return parseShareRevokeArgs(resourceArgs.slice(3));
|
|
4667
|
+
}
|
|
4668
|
+
if (resourceArgs[1] === "share") {
|
|
4669
|
+
throw new TenderCliUsageError(`Unknown app share command: ${resourceArgs[2]}`);
|
|
4670
|
+
}
|
|
4671
|
+
if (resourceArgs[1] === "claim") {
|
|
4672
|
+
return parseClaimArgs(resourceArgs.slice(2));
|
|
4673
|
+
}
|
|
4432
4674
|
if (resourceArgs[1] === "init") {
|
|
4433
4675
|
return parseArtifactsInitArgs(resourceArgs.slice(2));
|
|
4434
4676
|
}
|
|
@@ -6209,6 +6451,262 @@ async function runArtifactsUpdate(command, io, runtime) {
|
|
|
6209
6451
|
}
|
|
6210
6452
|
return 0;
|
|
6211
6453
|
}
|
|
6454
|
+
async function runShareCreate(command, io, runtime) {
|
|
6455
|
+
const credentials = await resolveApiCredentials({
|
|
6456
|
+
baseUrl: command.baseUrl,
|
|
6457
|
+
flagToken: command.flagToken,
|
|
6458
|
+
profileName: command.profileName,
|
|
6459
|
+
env: runtime.env ?? process.env,
|
|
6460
|
+
});
|
|
6461
|
+
const response = await (runtime.fetcher ?? fetch)(`${credentials.baseUrl}/api/v1/artifacts/${encodeURIComponent(command.artifactId)}/share`, {
|
|
6462
|
+
method: "POST",
|
|
6463
|
+
headers: {
|
|
6464
|
+
authorization: `Bearer ${credentials.token}`,
|
|
6465
|
+
"content-type": "application/json",
|
|
6466
|
+
},
|
|
6467
|
+
body: JSON.stringify({
|
|
6468
|
+
expiresIn: command.expires,
|
|
6469
|
+
claims: command.claims,
|
|
6470
|
+
}),
|
|
6471
|
+
});
|
|
6472
|
+
const payload = await parseJsonResponse(response);
|
|
6473
|
+
if (!response.ok || !payload?.ok || !payload.share || !payload.token) {
|
|
6474
|
+
throw new Error(payload?.message ??
|
|
6475
|
+
payload?.reasonCode ??
|
|
6476
|
+
`App setup share request failed with HTTP ${response.status}.`);
|
|
6477
|
+
}
|
|
6478
|
+
const result = {
|
|
6479
|
+
ok: true,
|
|
6480
|
+
command: command.command,
|
|
6481
|
+
baseUrl: credentials.baseUrl,
|
|
6482
|
+
authSource: credentials.source,
|
|
6483
|
+
profile: credentials.profileName,
|
|
6484
|
+
share: payload.share,
|
|
6485
|
+
token: payload.token,
|
|
6486
|
+
claimCommand: payload.claimCommand,
|
|
6487
|
+
};
|
|
6488
|
+
if (command.json) {
|
|
6489
|
+
io.stdout(JSON.stringify(result, null, 2));
|
|
6490
|
+
}
|
|
6491
|
+
else {
|
|
6492
|
+
io.stdout([
|
|
6493
|
+
`Created share token for "${payload.share.sourceAppName ?? command.artifactId}".`,
|
|
6494
|
+
"",
|
|
6495
|
+
"Includes:",
|
|
6496
|
+
"- Source code",
|
|
6497
|
+
"- App setup",
|
|
6498
|
+
"- Analytics dashboard and chart definitions",
|
|
6499
|
+
"",
|
|
6500
|
+
"Excludes:",
|
|
6501
|
+
"- Customer data",
|
|
6502
|
+
"- Analytics history",
|
|
6503
|
+
"- App database data",
|
|
6504
|
+
"- Secrets",
|
|
6505
|
+
"- Release history",
|
|
6506
|
+
"",
|
|
6507
|
+
"Share this command:",
|
|
6508
|
+
"",
|
|
6509
|
+
` ${payload.claimCommand ?? `tender app claim ${payload.token} --dir .`}`,
|
|
6510
|
+
"",
|
|
6511
|
+
`Expires: ${payload.share.expiresAt ?? ""}`,
|
|
6512
|
+
`Claims remaining: ${payload.share.claimsRemaining ?? command.claims}`,
|
|
6513
|
+
].join("\n"));
|
|
6514
|
+
}
|
|
6515
|
+
return 0;
|
|
6516
|
+
}
|
|
6517
|
+
async function runShareInspect(command, io, runtime) {
|
|
6518
|
+
const credentials = await resolveApiCredentials({
|
|
6519
|
+
baseUrl: command.baseUrl,
|
|
6520
|
+
flagToken: command.flagToken,
|
|
6521
|
+
profileName: command.profileName,
|
|
6522
|
+
env: runtime.env ?? process.env,
|
|
6523
|
+
});
|
|
6524
|
+
const response = await (runtime.fetcher ?? fetch)(`${credentials.baseUrl}/api/v1/app-shares/${encodeURIComponent(command.token)}`, {
|
|
6525
|
+
headers: {
|
|
6526
|
+
authorization: `Bearer ${credentials.token}`,
|
|
6527
|
+
},
|
|
6528
|
+
});
|
|
6529
|
+
const payload = await parseJsonResponse(response);
|
|
6530
|
+
if (!response.ok || !payload?.ok || !payload.share) {
|
|
6531
|
+
throw new Error(payload?.message ??
|
|
6532
|
+
payload?.reasonCode ??
|
|
6533
|
+
`App setup share inspect failed with HTTP ${response.status}.`);
|
|
6534
|
+
}
|
|
6535
|
+
if (command.json) {
|
|
6536
|
+
io.stdout(JSON.stringify({ command: command.command, ...payload }, null, 2));
|
|
6537
|
+
}
|
|
6538
|
+
else {
|
|
6539
|
+
io.stdout([
|
|
6540
|
+
`App: ${payload.share.sourceAppName ?? payload.share.sourceArtifactId ?? "Shared app"}`,
|
|
6541
|
+
`Expires: ${payload.share.expiresAt ?? ""}`,
|
|
6542
|
+
`Claims remaining: ${payload.share.claimsRemaining ?? 0}`,
|
|
6543
|
+
`Claimable: ${payload.claimable ? "yes" : "no"}`,
|
|
6544
|
+
].join("\n"));
|
|
6545
|
+
}
|
|
6546
|
+
return 0;
|
|
6547
|
+
}
|
|
6548
|
+
async function runShareRevoke(command, io, runtime) {
|
|
6549
|
+
const credentials = await resolveApiCredentials({
|
|
6550
|
+
baseUrl: command.baseUrl,
|
|
6551
|
+
flagToken: command.flagToken,
|
|
6552
|
+
profileName: command.profileName,
|
|
6553
|
+
env: runtime.env ?? process.env,
|
|
6554
|
+
});
|
|
6555
|
+
const response = await (runtime.fetcher ?? fetch)(`${credentials.baseUrl}/api/v1/app-shares/${encodeURIComponent(command.shareId)}/revoke`, {
|
|
6556
|
+
method: "POST",
|
|
6557
|
+
headers: {
|
|
6558
|
+
authorization: `Bearer ${credentials.token}`,
|
|
6559
|
+
},
|
|
6560
|
+
});
|
|
6561
|
+
const payload = await parseJsonResponse(response);
|
|
6562
|
+
if (!response.ok || !payload?.ok || !payload.share) {
|
|
6563
|
+
throw new Error(payload?.message ??
|
|
6564
|
+
payload?.reasonCode ??
|
|
6565
|
+
`App setup share revoke failed with HTTP ${response.status}.`);
|
|
6566
|
+
}
|
|
6567
|
+
if (command.json) {
|
|
6568
|
+
io.stdout(JSON.stringify({ ok: true, command: command.command, share: payload.share }, null, 2));
|
|
6569
|
+
}
|
|
6570
|
+
else {
|
|
6571
|
+
io.stdout(`revoked: ${payload.share.id}`);
|
|
6572
|
+
}
|
|
6573
|
+
return 0;
|
|
6574
|
+
}
|
|
6575
|
+
async function runClaim(command, io, runtime) {
|
|
6576
|
+
const credentials = await resolveApiCredentials({
|
|
6577
|
+
baseUrl: command.baseUrl,
|
|
6578
|
+
flagToken: command.flagToken,
|
|
6579
|
+
profileName: command.profileName,
|
|
6580
|
+
env: runtime.env ?? process.env,
|
|
6581
|
+
});
|
|
6582
|
+
const response = await (runtime.fetcher ?? fetch)(`${credentials.baseUrl}/api/v1/app-shares/${encodeURIComponent(command.token)}/claim`, {
|
|
6583
|
+
method: "POST",
|
|
6584
|
+
headers: {
|
|
6585
|
+
authorization: `Bearer ${credentials.token}`,
|
|
6586
|
+
"content-type": "application/json",
|
|
6587
|
+
},
|
|
6588
|
+
body: JSON.stringify({
|
|
6589
|
+
name: command.name,
|
|
6590
|
+
confirm: command.confirm,
|
|
6591
|
+
}),
|
|
6592
|
+
});
|
|
6593
|
+
const payload = await parseJsonResponse(response);
|
|
6594
|
+
if (!response.ok || !payload?.ok) {
|
|
6595
|
+
throw new Error(payload?.message ??
|
|
6596
|
+
payload?.reasonCode ??
|
|
6597
|
+
`App setup claim failed with HTTP ${response.status}.`);
|
|
6598
|
+
}
|
|
6599
|
+
if (payload.requiresConfirmation) {
|
|
6600
|
+
if (command.json) {
|
|
6601
|
+
io.stdout(JSON.stringify({ command: command.command, ...payload }, null, 2));
|
|
6602
|
+
}
|
|
6603
|
+
else {
|
|
6604
|
+
io.stdout([
|
|
6605
|
+
`Claim "${payload.share?.sourceAppName ?? "shared app setup"}"?`,
|
|
6606
|
+
"",
|
|
6607
|
+
"This creates a new app owned by the current Tender account.",
|
|
6608
|
+
"",
|
|
6609
|
+
"Will copy:",
|
|
6610
|
+
"- Source code",
|
|
6611
|
+
"- App setup",
|
|
6612
|
+
"- Analytics dashboard and chart definitions",
|
|
6613
|
+
"",
|
|
6614
|
+
"Will not copy:",
|
|
6615
|
+
"- Customer data",
|
|
6616
|
+
"- Analytics history",
|
|
6617
|
+
"- App database data",
|
|
6618
|
+
"- Secrets",
|
|
6619
|
+
"- Release history",
|
|
6620
|
+
"",
|
|
6621
|
+
`Continue with: tender app claim ${command.token} --dir ${command.dir} --confirm source-and-setup`,
|
|
6622
|
+
].join("\n"));
|
|
6623
|
+
}
|
|
6624
|
+
return 0;
|
|
6625
|
+
}
|
|
6626
|
+
if (!payload.artifact) {
|
|
6627
|
+
throw new Error("App setup claim response did not include a destination app.");
|
|
6628
|
+
}
|
|
6629
|
+
const root = path.resolve(command.dir);
|
|
6630
|
+
await mkdir(root, { recursive: true });
|
|
6631
|
+
const plannedFiles = await planSourceFiles({
|
|
6632
|
+
root,
|
|
6633
|
+
files: payload.sourceFiles ?? [],
|
|
6634
|
+
force: false,
|
|
6635
|
+
});
|
|
6636
|
+
const conflicts = plannedFiles.filter((file) => file.action === "conflict");
|
|
6637
|
+
if (conflicts.length > 0) {
|
|
6638
|
+
throw new TenderCliUsageError(`Claim target has ${conflicts.length} conflicting files. Choose an empty --dir or resolve: ${conflicts.map((file) => file.path).join(", ")}`);
|
|
6639
|
+
}
|
|
6640
|
+
for (const file of plannedFiles) {
|
|
6641
|
+
if (file.action !== "create" && file.action !== "overwrite") {
|
|
6642
|
+
continue;
|
|
6643
|
+
}
|
|
6644
|
+
await mkdir(path.dirname(file.absolutePath), { recursive: true });
|
|
6645
|
+
await writeFile(file.absolutePath, file.content);
|
|
6646
|
+
}
|
|
6647
|
+
const gitStdout = [];
|
|
6648
|
+
await runGitSetup({
|
|
6649
|
+
command: "app git setup",
|
|
6650
|
+
artifactId: payload.artifact.artifactId,
|
|
6651
|
+
dir: root,
|
|
6652
|
+
remoteName: "tender",
|
|
6653
|
+
baseUrl: credentials.baseUrl,
|
|
6654
|
+
flagToken: command.flagToken,
|
|
6655
|
+
profileName: credentials.profileName,
|
|
6656
|
+
ttl: "7d",
|
|
6657
|
+
dryRun: false,
|
|
6658
|
+
json: true,
|
|
6659
|
+
}, {
|
|
6660
|
+
stdout: (message) => gitStdout.push(message),
|
|
6661
|
+
stderr: io.stderr,
|
|
6662
|
+
}, runtime);
|
|
6663
|
+
const result = {
|
|
6664
|
+
ok: true,
|
|
6665
|
+
command: command.command,
|
|
6666
|
+
baseUrl: credentials.baseUrl,
|
|
6667
|
+
authSource: credentials.source,
|
|
6668
|
+
profile: credentials.profileName,
|
|
6669
|
+
appId: payload.artifact.artifactId,
|
|
6670
|
+
artifact: payload.artifact,
|
|
6671
|
+
dir: root,
|
|
6672
|
+
copied: payload.copied,
|
|
6673
|
+
notCopied: payload.notCopied,
|
|
6674
|
+
files: plannedFiles.map((file) => ({ path: file.path, action: file.action })),
|
|
6675
|
+
git: gitStdout.join("\n").trim() ? JSON.parse(gitStdout.join("\n")) : null,
|
|
6676
|
+
nextCommands: payload.nextCommands,
|
|
6677
|
+
};
|
|
6678
|
+
if (command.json) {
|
|
6679
|
+
io.stdout(JSON.stringify(result, null, 2));
|
|
6680
|
+
}
|
|
6681
|
+
else {
|
|
6682
|
+
io.stdout([
|
|
6683
|
+
"Claimed shared app setup.",
|
|
6684
|
+
"",
|
|
6685
|
+
"Created:",
|
|
6686
|
+
` App: ${payload.artifact.name}`,
|
|
6687
|
+
` ID: ${payload.artifact.artifactId}`,
|
|
6688
|
+
` Local checkout: ${root}`,
|
|
6689
|
+
"",
|
|
6690
|
+
"Copied:",
|
|
6691
|
+
` Source files: ${payload.copied?.sourceFiles ?? plannedFiles.length}`,
|
|
6692
|
+
` Analytics dashboards: ${payload.copied?.analyticsDashboards ?? 0}`,
|
|
6693
|
+
` Analytics charts: ${payload.copied?.analyticsCharts ?? 0}`,
|
|
6694
|
+
"",
|
|
6695
|
+
"Not copied:",
|
|
6696
|
+
" Usage data",
|
|
6697
|
+
" App database rows",
|
|
6698
|
+
" Secrets",
|
|
6699
|
+
" Release history",
|
|
6700
|
+
"",
|
|
6701
|
+
"Next:",
|
|
6702
|
+
` cd ${command.dir}`,
|
|
6703
|
+
` tender app doctor --dir .`,
|
|
6704
|
+
` git push tender main`,
|
|
6705
|
+
` tender app preview ${payload.artifact.artifactId} --stream`,
|
|
6706
|
+
].join("\n"));
|
|
6707
|
+
}
|
|
6708
|
+
return 0;
|
|
6709
|
+
}
|
|
6212
6710
|
function defaultCommandRunner(command, args, options) {
|
|
6213
6711
|
return new Promise((resolve, reject) => {
|
|
6214
6712
|
const child = spawn(command, args, {
|
|
@@ -6489,7 +6987,11 @@ async function maybeSeedInitialArtifactGitCommit(input) {
|
|
|
6489
6987
|
};
|
|
6490
6988
|
}
|
|
6491
6989
|
async function requestGitRemoteLease(input) {
|
|
6492
|
-
const
|
|
6990
|
+
const params = new URLSearchParams({ ttl: input.ttl });
|
|
6991
|
+
if (input.includeCredential) {
|
|
6992
|
+
params.set("credential", "1");
|
|
6993
|
+
}
|
|
6994
|
+
const url = `${input.baseUrl}/api/v1/artifacts/${encodeURIComponent(input.artifactId)}/git/remote?${params.toString()}`;
|
|
6493
6995
|
const response = await input.fetcher(url, {
|
|
6494
6996
|
headers: {
|
|
6495
6997
|
authorization: `Bearer ${input.token}`,
|
|
@@ -6652,28 +7154,41 @@ async function runGitCredential(command, io, runtime) {
|
|
|
6652
7154
|
baseUrl: credentials.baseUrl,
|
|
6653
7155
|
token: credentials.token,
|
|
6654
7156
|
fetcher: runtime.fetcher ?? fetch,
|
|
7157
|
+
includeCredential: true,
|
|
6655
7158
|
});
|
|
7159
|
+
const credentialPassword = git.token?.password ?? null;
|
|
7160
|
+
const credentialUsername = git.token?.username ?? git.authentication?.username ?? "tender";
|
|
7161
|
+
if (!credentialPassword) {
|
|
7162
|
+
if (command.json) {
|
|
7163
|
+
io.stdout(JSON.stringify({
|
|
7164
|
+
ok: false,
|
|
7165
|
+
command: command.command,
|
|
7166
|
+
artifactId: command.artifactId,
|
|
7167
|
+
remoteUrl: git.remoteUrl,
|
|
7168
|
+
reasonCode: "git_credential_unavailable",
|
|
7169
|
+
passwordAvailable: false,
|
|
7170
|
+
}, null, 2));
|
|
7171
|
+
}
|
|
7172
|
+
else {
|
|
7173
|
+
io.stderr("Tender Git credential is unavailable. Refresh the Git setup so the helper can request a scoped Git credential.");
|
|
7174
|
+
}
|
|
7175
|
+
return 1;
|
|
7176
|
+
}
|
|
6656
7177
|
if (command.json) {
|
|
6657
7178
|
io.stdout(JSON.stringify({
|
|
6658
7179
|
ok: true,
|
|
6659
7180
|
command: command.command,
|
|
6660
7181
|
artifactId: command.artifactId,
|
|
6661
7182
|
remoteUrl: git.remoteUrl,
|
|
6662
|
-
username:
|
|
6663
|
-
|
|
6664
|
-
(git.authentication?.mode === "tender-api-token"
|
|
6665
|
-
? credentials.token
|
|
6666
|
-
: ""),
|
|
7183
|
+
username: credentialUsername,
|
|
7184
|
+
passwordAvailable: true,
|
|
6667
7185
|
expiresAt: git.token?.expiresAt ?? null,
|
|
6668
7186
|
}, null, 2));
|
|
6669
7187
|
}
|
|
6670
7188
|
else {
|
|
6671
7189
|
io.stdout([
|
|
6672
|
-
`username=${
|
|
6673
|
-
`password=${
|
|
6674
|
-
(git.authentication?.mode === "tender-api-token"
|
|
6675
|
-
? credentials.token
|
|
6676
|
-
: "")}`,
|
|
7190
|
+
`username=${credentialUsername}`,
|
|
7191
|
+
`password=${credentialPassword}`,
|
|
6677
7192
|
].join("\n"));
|
|
6678
7193
|
}
|
|
6679
7194
|
return 0;
|
|
@@ -7606,7 +8121,7 @@ async function runGitRemote(command, io, runtime) {
|
|
|
7606
8121
|
scope: git.scope,
|
|
7607
8122
|
authentication: git.authentication?.mode ?? "artifacts-token",
|
|
7608
8123
|
username: git.token?.username ?? git.authentication?.username ?? "tender",
|
|
7609
|
-
|
|
8124
|
+
passwordAvailable: Boolean(git.token?.password),
|
|
7610
8125
|
expiresAt: git.token?.expiresAt ?? null,
|
|
7611
8126
|
};
|
|
7612
8127
|
if (command.json) {
|
|
@@ -7618,10 +8133,8 @@ async function runGitRemote(command, io, runtime) {
|
|
|
7618
8133
|
`default_branch: ${result.defaultBranch}`,
|
|
7619
8134
|
`authentication: ${result.authentication}`,
|
|
7620
8135
|
`username: ${result.username}`,
|
|
8136
|
+
`password_available: ${result.passwordAvailable ? "true" : "false"}`,
|
|
7621
8137
|
];
|
|
7622
|
-
if (result.password) {
|
|
7623
|
-
lines.push(`password: ${result.password}`);
|
|
7624
|
-
}
|
|
7625
8138
|
if (result.expiresAt) {
|
|
7626
8139
|
lines.push(`expires_at: ${result.expiresAt}`);
|
|
7627
8140
|
}
|
|
@@ -8382,6 +8895,21 @@ export async function runTenderCli(args, io = {
|
|
|
8382
8895
|
else if (command.topic === "app update") {
|
|
8383
8896
|
io.stdout(artifactsUpdateHelp());
|
|
8384
8897
|
}
|
|
8898
|
+
else if (command.topic === "app share") {
|
|
8899
|
+
io.stdout(appShareHelp());
|
|
8900
|
+
}
|
|
8901
|
+
else if (command.topic === "app share create") {
|
|
8902
|
+
io.stdout(appShareCreateHelp());
|
|
8903
|
+
}
|
|
8904
|
+
else if (command.topic === "app share inspect") {
|
|
8905
|
+
io.stdout(appShareInspectHelp());
|
|
8906
|
+
}
|
|
8907
|
+
else if (command.topic === "app share revoke") {
|
|
8908
|
+
io.stdout(appShareRevokeHelp());
|
|
8909
|
+
}
|
|
8910
|
+
else if (command.topic === "app claim") {
|
|
8911
|
+
io.stdout(appClaimHelp());
|
|
8912
|
+
}
|
|
8385
8913
|
else if (command.topic === "app init") {
|
|
8386
8914
|
io.stdout(artifactsInitHelp());
|
|
8387
8915
|
}
|
|
@@ -8600,6 +9128,18 @@ export async function runTenderCli(args, io = {
|
|
|
8600
9128
|
if (command.command === "app analytics query") {
|
|
8601
9129
|
return await runAnalyticsQuery(command, io, runtime);
|
|
8602
9130
|
}
|
|
9131
|
+
if (command.command === "app share create") {
|
|
9132
|
+
return await runShareCreate(command, io, runtime);
|
|
9133
|
+
}
|
|
9134
|
+
if (command.command === "app share inspect") {
|
|
9135
|
+
return await runShareInspect(command, io, runtime);
|
|
9136
|
+
}
|
|
9137
|
+
if (command.command === "app share revoke") {
|
|
9138
|
+
return await runShareRevoke(command, io, runtime);
|
|
9139
|
+
}
|
|
9140
|
+
if (command.command === "app claim") {
|
|
9141
|
+
return await runClaim(command, io, runtime);
|
|
9142
|
+
}
|
|
8603
9143
|
if (command.command === "app analytics capabilities") {
|
|
8604
9144
|
return await runAnalyticsCapabilities(command, io, runtime);
|
|
8605
9145
|
}
|