@tapi-dev/sdk 0.1.36 → 0.1.39
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 +60 -48
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +459 -255
- package/dist/client.d.ts +1 -1
- package/dist/client.js +10 -3
- package/dist/types.d.ts +1 -1
- package/package.json +7 -1
package/dist/cli.js
CHANGED
|
@@ -12,6 +12,9 @@ import { emitKeypressEvents } from "node:readline";
|
|
|
12
12
|
import { Readable, Transform } from "node:stream";
|
|
13
13
|
import { pipeline } from "node:stream/promises";
|
|
14
14
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
15
|
+
import { select } from "@inquirer/prompts";
|
|
16
|
+
import { Presets, SingleBar } from "cli-progress";
|
|
17
|
+
import pc from "yoctocolors";
|
|
15
18
|
import { TapiClient } from "./index.js";
|
|
16
19
|
import { loadWorkspace, normalizeProjectValue, writeWorkspaceConfig, } from "./workspace.js";
|
|
17
20
|
const DEFAULT_DOWNLOADS_BASE_URL = "https://d4xaf52nfwiok.cloudfront.net";
|
|
@@ -117,6 +120,50 @@ async function withCliSpinner(message, work) {
|
|
|
117
120
|
throw error;
|
|
118
121
|
}
|
|
119
122
|
}
|
|
123
|
+
async function withSdkAuth(options) {
|
|
124
|
+
if (options.authToken?.trim()) {
|
|
125
|
+
return { ...options, authToken: options.authToken.trim() };
|
|
126
|
+
}
|
|
127
|
+
const envAuthToken = envString("TAPI_FIREBASE_ID_TOKEN");
|
|
128
|
+
if (envAuthToken) {
|
|
129
|
+
return { ...options, authToken: envAuthToken };
|
|
130
|
+
}
|
|
131
|
+
const credentials = await requireSdkFirebaseCredentials();
|
|
132
|
+
return { ...options, authToken: credentials.idToken };
|
|
133
|
+
}
|
|
134
|
+
async function requireSdkFirebaseCredentials() {
|
|
135
|
+
const credentials = await refreshCachedFirebaseCredentials();
|
|
136
|
+
if (!credentials) {
|
|
137
|
+
throw new Error(`Tapi sign-in is required. Run \`tapi login\`, then retry.`);
|
|
138
|
+
}
|
|
139
|
+
await writeStudioAuthCache(credentials);
|
|
140
|
+
return credentials;
|
|
141
|
+
}
|
|
142
|
+
async function runLogin() {
|
|
143
|
+
try {
|
|
144
|
+
const credentials = await withCliSpinner("Signing in to Tapi", () => authenticateSdkUser());
|
|
145
|
+
console.log(`Signed in to Tapi as ${credentials.uid}.`);
|
|
146
|
+
return 0;
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
console.error(formatError(error));
|
|
150
|
+
return 1;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async function authenticateSdkUser() {
|
|
154
|
+
const cached = await refreshCachedFirebaseCredentials();
|
|
155
|
+
if (cached) {
|
|
156
|
+
await writeStudioAuthCache(cached);
|
|
157
|
+
return cached;
|
|
158
|
+
}
|
|
159
|
+
const browserAuth = await browserGoogleSignIn();
|
|
160
|
+
if (!browserAuth.idToken) {
|
|
161
|
+
throw new Error(browserAuth.error || browserAuth.detail || "Browser sign-in did not return a Google ID token.");
|
|
162
|
+
}
|
|
163
|
+
const credentials = await exchangeGoogleToFirebase(browserAuth.idToken, browserAuth.accessToken);
|
|
164
|
+
await writeStudioAuthCache(credentials);
|
|
165
|
+
return credentials;
|
|
166
|
+
}
|
|
120
167
|
export async function runCli(argv = process.argv.slice(2)) {
|
|
121
168
|
const [command, subcommand, ...rest] = argv;
|
|
122
169
|
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
@@ -127,6 +174,18 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
127
174
|
console.log(sdkVersion);
|
|
128
175
|
return 0;
|
|
129
176
|
}
|
|
177
|
+
if (command === "login") {
|
|
178
|
+
if (hasHelpFlag([subcommand, ...rest])) {
|
|
179
|
+
printLoginHelp();
|
|
180
|
+
return 0;
|
|
181
|
+
}
|
|
182
|
+
if (subcommand) {
|
|
183
|
+
console.error(`Unexpected login argument: ${subcommand}`);
|
|
184
|
+
printLoginHelp();
|
|
185
|
+
return 1;
|
|
186
|
+
}
|
|
187
|
+
return runLogin();
|
|
188
|
+
}
|
|
130
189
|
if (command === "doctor") {
|
|
131
190
|
if (hasHelpFlag([subcommand, ...rest])) {
|
|
132
191
|
printStudioHelp();
|
|
@@ -258,6 +317,9 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
258
317
|
if (!subcommand) {
|
|
259
318
|
return openStudio(parseStudioOptions([]));
|
|
260
319
|
}
|
|
320
|
+
if (subcommand.startsWith("-")) {
|
|
321
|
+
return openStudio(parseStudioOptions([subcommand, ...rest]));
|
|
322
|
+
}
|
|
261
323
|
if (subcommand === "help" || subcommand === "--help" || subcommand === "-h") {
|
|
262
324
|
printStudioHelp();
|
|
263
325
|
return 0;
|
|
@@ -284,6 +346,8 @@ export function parseStudioOptions(args) {
|
|
|
284
346
|
const raw = {};
|
|
285
347
|
let workspaceSearchRoot;
|
|
286
348
|
let skipWorkspace = false;
|
|
349
|
+
let projectIdSource;
|
|
350
|
+
let tappSelectionMode = "auto";
|
|
287
351
|
for (let index = 0; index < args.length; index += 1) {
|
|
288
352
|
const arg = args[index];
|
|
289
353
|
if (!arg) {
|
|
@@ -329,12 +393,40 @@ export function parseStudioOptions(args) {
|
|
|
329
393
|
skipWorkspace = true;
|
|
330
394
|
continue;
|
|
331
395
|
}
|
|
396
|
+
if (arg === "--tapp") {
|
|
397
|
+
raw.projectId = normalizeProjectValue(requireOptionValue(args, ++index, "--tapp"), "tapp");
|
|
398
|
+
projectIdSource = "option";
|
|
399
|
+
tappSelectionMode = "none";
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
if (arg.startsWith("--tapp=")) {
|
|
403
|
+
raw.projectId = normalizeProjectValue(arg.slice("--tapp=".length), "tapp");
|
|
404
|
+
projectIdSource = "option";
|
|
405
|
+
tappSelectionMode = "none";
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
if (arg === "--select") {
|
|
409
|
+
tappSelectionMode = "select";
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
if (arg === "--last") {
|
|
413
|
+
tappSelectionMode = "last";
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
if (arg === "--no-select") {
|
|
417
|
+
tappSelectionMode = "none";
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
332
420
|
if (arg === "--project") {
|
|
333
421
|
raw.projectId = normalizeProjectValue(requireOptionValue(args, ++index, "--project"), "project");
|
|
422
|
+
projectIdSource = "option";
|
|
423
|
+
tappSelectionMode = "none";
|
|
334
424
|
continue;
|
|
335
425
|
}
|
|
336
426
|
if (arg.startsWith("--project=")) {
|
|
337
427
|
raw.projectId = normalizeProjectValue(arg.slice("--project=".length), "project");
|
|
428
|
+
projectIdSource = "option";
|
|
429
|
+
tappSelectionMode = "none";
|
|
338
430
|
continue;
|
|
339
431
|
}
|
|
340
432
|
if (arg === "--project-slug") {
|
|
@@ -403,7 +495,11 @@ export function parseStudioOptions(args) {
|
|
|
403
495
|
const manifestUrl = explicitManifestUrl
|
|
404
496
|
? normalizeHttpUrl(explicitManifestUrl, "Studio manifest URL")
|
|
405
497
|
: `${downloadsBaseUrl.replace(/\/+$/, "")}/studio/channels/${channel}/latest.json`;
|
|
406
|
-
const
|
|
498
|
+
const envProjectId = envString("TAPI_PROJECT_ID");
|
|
499
|
+
const projectId = raw.projectId ?? envProjectId ?? workspace?.projectId;
|
|
500
|
+
if (!projectIdSource && projectId) {
|
|
501
|
+
projectIdSource = raw.projectId ? "option" : envProjectId ? "env" : "workspace";
|
|
502
|
+
}
|
|
407
503
|
const projectSlug = raw.projectSlug ?? envString("TAPI_PROJECT_SLUG") ?? workspace?.projectSlug;
|
|
408
504
|
const envInstallToken = envString("TAPI_STUDIO_INSTALL_TOKEN");
|
|
409
505
|
const installToken = raw.installToken ?? envInstallToken;
|
|
@@ -423,14 +519,15 @@ export function parseStudioOptions(args) {
|
|
|
423
519
|
workspaceRoot: workspace?.root,
|
|
424
520
|
projectId,
|
|
425
521
|
projectSlug,
|
|
522
|
+
projectIdSource,
|
|
426
523
|
workspaceMode: Boolean(projectId || workspace),
|
|
524
|
+
tappSelectionMode,
|
|
427
525
|
};
|
|
428
526
|
}
|
|
429
527
|
function parseServiceOptions(args) {
|
|
430
528
|
let operation = "";
|
|
431
529
|
const workspace = loadWorkspace();
|
|
432
530
|
let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
|
|
433
|
-
let apiKey = envString("TAPI_API_KEY") || "";
|
|
434
531
|
let projectId = envString("TAPI_PROJECT_ID") || workspace?.projectId || "";
|
|
435
532
|
for (let index = 0; index < args.length; index += 1) {
|
|
436
533
|
const arg = args[index];
|
|
@@ -448,14 +545,6 @@ function parseServiceOptions(args) {
|
|
|
448
545
|
apiBaseUrl = arg.slice("--server=".length);
|
|
449
546
|
continue;
|
|
450
547
|
}
|
|
451
|
-
if (arg === "--api-key") {
|
|
452
|
-
apiKey = requireOptionValue(args, ++index, "--api-key");
|
|
453
|
-
continue;
|
|
454
|
-
}
|
|
455
|
-
if (arg.startsWith("--api-key=")) {
|
|
456
|
-
apiKey = arg.slice("--api-key=".length);
|
|
457
|
-
continue;
|
|
458
|
-
}
|
|
459
548
|
if (arg === "--project") {
|
|
460
549
|
projectId = normalizeProjectValue(requireOptionValue(args, ++index, "--project"), "project");
|
|
461
550
|
continue;
|
|
@@ -476,9 +565,6 @@ function parseServiceOptions(args) {
|
|
|
476
565
|
if (!operation) {
|
|
477
566
|
throw new Error("services describe requires a service run like schwab.place_order.");
|
|
478
567
|
}
|
|
479
|
-
if (!apiKey) {
|
|
480
|
-
throw new Error("services describe requires --api-key or TAPI_API_KEY.");
|
|
481
|
-
}
|
|
482
568
|
if (!projectId) {
|
|
483
569
|
throw new Error("services describe requires --project, TAPI_PROJECT_ID, or .tapi/project.json.");
|
|
484
570
|
}
|
|
@@ -486,7 +572,6 @@ function parseServiceOptions(args) {
|
|
|
486
572
|
operation,
|
|
487
573
|
options: {
|
|
488
574
|
apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
|
|
489
|
-
apiKey,
|
|
490
575
|
projectId,
|
|
491
576
|
},
|
|
492
577
|
};
|
|
@@ -494,11 +579,12 @@ function parseServiceOptions(args) {
|
|
|
494
579
|
async function describeServiceOperation(args) {
|
|
495
580
|
try {
|
|
496
581
|
const { operation, options } = parseServiceOptions(args);
|
|
582
|
+
const authenticated = await withSdkAuth(options);
|
|
497
583
|
const client = new TapiClient({
|
|
498
|
-
baseUrl:
|
|
499
|
-
|
|
500
|
-
tappId:
|
|
501
|
-
projectId:
|
|
584
|
+
baseUrl: authenticated.apiBaseUrl,
|
|
585
|
+
authToken: authenticated.authToken,
|
|
586
|
+
tappId: authenticated.projectId,
|
|
587
|
+
projectId: authenticated.projectId,
|
|
502
588
|
});
|
|
503
589
|
const description = await withCliSpinner(`Fetching Tapi service-run contract for ${operation}`, () => client.services.describe(operation));
|
|
504
590
|
console.log(JSON.stringify(description, null, 2));
|
|
@@ -512,7 +598,6 @@ async function describeServiceOperation(args) {
|
|
|
512
598
|
function parseApiWorkspaceOptions(args) {
|
|
513
599
|
const workspace = loadWorkspace();
|
|
514
600
|
let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
|
|
515
|
-
let apiKey = envString("TAPI_API_KEY") || "";
|
|
516
601
|
let projectId = envString("TAPI_PROJECT_ID") || workspace?.projectId || "";
|
|
517
602
|
let catalogPath = workspace?.config.services?.catalog || ".tapi/services/catalog.json";
|
|
518
603
|
for (let index = 0; index < args.length; index += 1) {
|
|
@@ -531,14 +616,6 @@ function parseApiWorkspaceOptions(args) {
|
|
|
531
616
|
apiBaseUrl = arg.slice("--server=".length);
|
|
532
617
|
continue;
|
|
533
618
|
}
|
|
534
|
-
if (arg === "--api-key") {
|
|
535
|
-
apiKey = requireOptionValue(args, ++index, "--api-key");
|
|
536
|
-
continue;
|
|
537
|
-
}
|
|
538
|
-
if (arg.startsWith("--api-key=")) {
|
|
539
|
-
apiKey = arg.slice("--api-key=".length);
|
|
540
|
-
continue;
|
|
541
|
-
}
|
|
542
619
|
if (arg === "--project") {
|
|
543
620
|
projectId = normalizeProjectValue(requireOptionValue(args, ++index, "--project"), "project");
|
|
544
621
|
continue;
|
|
@@ -557,23 +634,19 @@ function parseApiWorkspaceOptions(args) {
|
|
|
557
634
|
}
|
|
558
635
|
throw new Error(`Unknown services option: ${arg}`);
|
|
559
636
|
}
|
|
560
|
-
if (!apiKey) {
|
|
561
|
-
throw new Error("services command requires --api-key or TAPI_API_KEY.");
|
|
562
|
-
}
|
|
563
637
|
if (!projectId) {
|
|
564
638
|
throw new Error("services command requires --project, TAPI_PROJECT_ID, or .tapi/project.json.");
|
|
565
639
|
}
|
|
566
640
|
const root = workspace?.root || process.cwd();
|
|
567
641
|
return {
|
|
568
642
|
apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
|
|
569
|
-
apiKey,
|
|
570
643
|
projectId,
|
|
571
644
|
catalogPath: resolve(root, catalogPath),
|
|
572
645
|
};
|
|
573
646
|
}
|
|
574
647
|
async function syncServiceCatalog(args) {
|
|
575
648
|
try {
|
|
576
|
-
const options = parseApiWorkspaceOptions(args);
|
|
649
|
+
const options = await withSdkAuth(parseApiWorkspaceOptions(args));
|
|
577
650
|
const catalog = await withCliSpinner("Fetching Tapi service-run catalog", () => fetchApiCatalog(options));
|
|
578
651
|
await writeJsonFile(options.catalogPath, catalog);
|
|
579
652
|
console.log(`Synced Tapi service-run catalog: ${options.catalogPath}`);
|
|
@@ -587,7 +660,6 @@ async function syncServiceCatalog(args) {
|
|
|
587
660
|
function parseTappServiceAddOptions(args) {
|
|
588
661
|
const workspace = loadWorkspace();
|
|
589
662
|
let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
|
|
590
|
-
let apiKey = envString("TAPI_API_KEY") || "";
|
|
591
663
|
let tappId = "";
|
|
592
664
|
let serviceCall = "";
|
|
593
665
|
let serviceMapId = "";
|
|
@@ -609,14 +681,6 @@ function parseTappServiceAddOptions(args) {
|
|
|
609
681
|
apiBaseUrl = arg.slice("--server=".length);
|
|
610
682
|
continue;
|
|
611
683
|
}
|
|
612
|
-
if (arg === "--api-key") {
|
|
613
|
-
apiKey = requireOptionValue(args, ++index, "--api-key");
|
|
614
|
-
continue;
|
|
615
|
-
}
|
|
616
|
-
if (arg.startsWith("--api-key=")) {
|
|
617
|
-
apiKey = arg.slice("--api-key=".length);
|
|
618
|
-
continue;
|
|
619
|
-
}
|
|
620
684
|
if (arg === "--service-map") {
|
|
621
685
|
serviceMapId = requireOptionValue(args, ++index, "--service-map");
|
|
622
686
|
continue;
|
|
@@ -654,9 +718,6 @@ function parseTappServiceAddOptions(args) {
|
|
|
654
718
|
}
|
|
655
719
|
throw new Error(`Unexpected tapp service add argument: ${arg}`);
|
|
656
720
|
}
|
|
657
|
-
if (!apiKey) {
|
|
658
|
-
throw new Error("tapp service add requires --api-key or TAPI_API_KEY.");
|
|
659
|
-
}
|
|
660
721
|
if (!tappId) {
|
|
661
722
|
throw new Error("tapp service add requires a Tapp id.");
|
|
662
723
|
}
|
|
@@ -671,7 +732,6 @@ function parseTappServiceAddOptions(args) {
|
|
|
671
732
|
}
|
|
672
733
|
return {
|
|
673
734
|
apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
|
|
674
|
-
apiKey,
|
|
675
735
|
tappId,
|
|
676
736
|
serviceCall,
|
|
677
737
|
serviceMapId,
|
|
@@ -682,7 +742,6 @@ function parseTappServiceAddOptions(args) {
|
|
|
682
742
|
function parseTappCreateOptions(args) {
|
|
683
743
|
const workspace = loadWorkspace();
|
|
684
744
|
let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
|
|
685
|
-
let apiKey = envString("TAPI_API_KEY") || "";
|
|
686
745
|
let tappId = envString("TAPI_PROJECT_ID") || workspace?.projectId || "";
|
|
687
746
|
let name = "";
|
|
688
747
|
for (let index = 0; index < args.length; index += 1) {
|
|
@@ -701,14 +760,6 @@ function parseTappCreateOptions(args) {
|
|
|
701
760
|
apiBaseUrl = arg.slice("--server=".length);
|
|
702
761
|
continue;
|
|
703
762
|
}
|
|
704
|
-
if (arg === "--api-key") {
|
|
705
|
-
apiKey = requireOptionValue(args, ++index, "--api-key");
|
|
706
|
-
continue;
|
|
707
|
-
}
|
|
708
|
-
if (arg.startsWith("--api-key=")) {
|
|
709
|
-
apiKey = arg.slice("--api-key=".length);
|
|
710
|
-
continue;
|
|
711
|
-
}
|
|
712
763
|
if (arg === "--tapp-id" || arg === "--project") {
|
|
713
764
|
tappId = normalizeProjectValue(requireOptionValue(args, ++index, arg), "tapp");
|
|
714
765
|
continue;
|
|
@@ -741,9 +792,6 @@ function parseTappCreateOptions(args) {
|
|
|
741
792
|
}
|
|
742
793
|
throw new Error(`Unexpected tapp create argument: ${arg}`);
|
|
743
794
|
}
|
|
744
|
-
if (!apiKey) {
|
|
745
|
-
throw new Error("tapp create requires --api-key or TAPI_API_KEY.");
|
|
746
|
-
}
|
|
747
795
|
if (!tappId) {
|
|
748
796
|
throw new Error("tapp create requires a Tapp id or name.");
|
|
749
797
|
}
|
|
@@ -752,14 +800,13 @@ function parseTappCreateOptions(args) {
|
|
|
752
800
|
}
|
|
753
801
|
return {
|
|
754
802
|
apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
|
|
755
|
-
apiKey,
|
|
756
803
|
tappId,
|
|
757
804
|
name,
|
|
758
805
|
};
|
|
759
806
|
}
|
|
760
807
|
async function createTapp(args) {
|
|
761
808
|
try {
|
|
762
|
-
const options = parseTappCreateOptions(args);
|
|
809
|
+
const options = await withSdkAuth(parseTappCreateOptions(args));
|
|
763
810
|
const tapp = await withCliSpinner(`Creating Tapi Tapp ${options.tappId}`, () => postTappCreate(options));
|
|
764
811
|
console.log(JSON.stringify(tapp, null, 2));
|
|
765
812
|
return 0;
|
|
@@ -772,11 +819,7 @@ async function createTapp(args) {
|
|
|
772
819
|
async function postTappCreate(options, fetchImpl = fetch) {
|
|
773
820
|
const response = await fetchImpl(`${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/tapps`, {
|
|
774
821
|
method: "POST",
|
|
775
|
-
headers:
|
|
776
|
-
"Content-Type": "application/json",
|
|
777
|
-
"X-API-Key": options.apiKey,
|
|
778
|
-
"X-Tapi-Project": options.tappId,
|
|
779
|
-
},
|
|
822
|
+
headers: sdkJsonHeaders(options.authToken, options.tappId),
|
|
780
823
|
body: JSON.stringify({
|
|
781
824
|
tappId: options.tappId,
|
|
782
825
|
name: options.name,
|
|
@@ -791,7 +834,7 @@ async function postTappCreate(options, fetchImpl = fetch) {
|
|
|
791
834
|
}
|
|
792
835
|
async function addTappService(args) {
|
|
793
836
|
try {
|
|
794
|
-
const options = parseTappServiceAddOptions(args);
|
|
837
|
+
const options = await withSdkAuth(parseTappServiceAddOptions(args));
|
|
795
838
|
const operation = await withCliSpinner(`Adding Tapi service ${options.serviceCall} to ${options.tappId}`, () => postTappServiceAdd(options));
|
|
796
839
|
console.log(JSON.stringify(operation, null, 2));
|
|
797
840
|
return 0;
|
|
@@ -804,11 +847,7 @@ async function addTappService(args) {
|
|
|
804
847
|
async function postTappServiceAdd(options) {
|
|
805
848
|
const response = await fetch(`${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/tapps/${encodeURIComponent(options.tappId)}/services/${encodeURIComponent(options.serviceCall)}`, {
|
|
806
849
|
method: "POST",
|
|
807
|
-
headers:
|
|
808
|
-
"Content-Type": "application/json",
|
|
809
|
-
"X-API-Key": options.apiKey,
|
|
810
|
-
"X-Tapi-Project": options.tappId,
|
|
811
|
-
},
|
|
850
|
+
headers: sdkJsonHeaders(options.authToken, options.tappId),
|
|
812
851
|
body: JSON.stringify({
|
|
813
852
|
serviceMapId: options.serviceMapId,
|
|
814
853
|
entry: options.entry,
|
|
@@ -825,7 +864,6 @@ async function postTappServiceAdd(options) {
|
|
|
825
864
|
function parseQueueCreateOptions(args) {
|
|
826
865
|
const workspace = loadWorkspace();
|
|
827
866
|
let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
|
|
828
|
-
let apiKey = envString("TAPI_API_KEY") || "";
|
|
829
867
|
let projectId = envString("TAPI_PROJECT_ID") || workspace?.projectId || "";
|
|
830
868
|
let displayName = "";
|
|
831
869
|
let maxSlots = 8;
|
|
@@ -845,14 +883,6 @@ function parseQueueCreateOptions(args) {
|
|
|
845
883
|
apiBaseUrl = arg.slice("--server=".length);
|
|
846
884
|
continue;
|
|
847
885
|
}
|
|
848
|
-
if (arg === "--api-key") {
|
|
849
|
-
apiKey = requireOptionValue(args, ++index, "--api-key");
|
|
850
|
-
continue;
|
|
851
|
-
}
|
|
852
|
-
if (arg.startsWith("--api-key=")) {
|
|
853
|
-
apiKey = arg.slice("--api-key=".length);
|
|
854
|
-
continue;
|
|
855
|
-
}
|
|
856
886
|
if (arg === "--project") {
|
|
857
887
|
projectId = normalizeProjectValue(requireOptionValue(args, ++index, "--project"), "project");
|
|
858
888
|
continue;
|
|
@@ -878,12 +908,8 @@ function parseQueueCreateOptions(args) {
|
|
|
878
908
|
}
|
|
879
909
|
throw new Error(`Unexpected queue create argument: ${arg}`);
|
|
880
910
|
}
|
|
881
|
-
if (!apiKey) {
|
|
882
|
-
throw new Error("queue create requires --api-key or TAPI_API_KEY.");
|
|
883
|
-
}
|
|
884
911
|
return {
|
|
885
912
|
apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
|
|
886
|
-
apiKey,
|
|
887
913
|
projectId,
|
|
888
914
|
displayName,
|
|
889
915
|
maxSlots,
|
|
@@ -892,7 +918,6 @@ function parseQueueCreateOptions(args) {
|
|
|
892
918
|
function parseTappQueueAddOptions(args) {
|
|
893
919
|
const workspace = loadWorkspace();
|
|
894
920
|
let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
|
|
895
|
-
let apiKey = envString("TAPI_API_KEY") || "";
|
|
896
921
|
let tappId = "";
|
|
897
922
|
let queueId = "";
|
|
898
923
|
for (let index = 0; index < args.length; index += 1) {
|
|
@@ -911,14 +936,6 @@ function parseTappQueueAddOptions(args) {
|
|
|
911
936
|
apiBaseUrl = arg.slice("--server=".length);
|
|
912
937
|
continue;
|
|
913
938
|
}
|
|
914
|
-
if (arg === "--api-key") {
|
|
915
|
-
apiKey = requireOptionValue(args, ++index, "--api-key");
|
|
916
|
-
continue;
|
|
917
|
-
}
|
|
918
|
-
if (arg.startsWith("--api-key=")) {
|
|
919
|
-
apiKey = arg.slice("--api-key=".length);
|
|
920
|
-
continue;
|
|
921
|
-
}
|
|
922
939
|
if (arg.startsWith("--")) {
|
|
923
940
|
throw new Error(`Unknown tapp queue add option: ${arg}`);
|
|
924
941
|
}
|
|
@@ -932,9 +949,6 @@ function parseTappQueueAddOptions(args) {
|
|
|
932
949
|
}
|
|
933
950
|
throw new Error(`Unexpected tapp queue add argument: ${arg}`);
|
|
934
951
|
}
|
|
935
|
-
if (!apiKey) {
|
|
936
|
-
throw new Error("tapp queue add requires --api-key or TAPI_API_KEY.");
|
|
937
|
-
}
|
|
938
952
|
if (!tappId) {
|
|
939
953
|
throw new Error("tapp queue add requires a Tapp id.");
|
|
940
954
|
}
|
|
@@ -943,7 +957,6 @@ function parseTappQueueAddOptions(args) {
|
|
|
943
957
|
}
|
|
944
958
|
return {
|
|
945
959
|
apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
|
|
946
|
-
apiKey,
|
|
947
960
|
tappId,
|
|
948
961
|
queueId,
|
|
949
962
|
};
|
|
@@ -951,7 +964,6 @@ function parseTappQueueAddOptions(args) {
|
|
|
951
964
|
function parseRunnerSlotSetOptions(args) {
|
|
952
965
|
const workspace = loadWorkspace();
|
|
953
966
|
let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
|
|
954
|
-
let apiKey = envString("TAPI_API_KEY") || "";
|
|
955
967
|
let projectId = envString("TAPI_PROJECT_ID") || workspace?.projectId || "";
|
|
956
968
|
let runnerId = "";
|
|
957
969
|
let slotIndex = 0;
|
|
@@ -972,14 +984,6 @@ function parseRunnerSlotSetOptions(args) {
|
|
|
972
984
|
apiBaseUrl = arg.slice("--server=".length);
|
|
973
985
|
continue;
|
|
974
986
|
}
|
|
975
|
-
if (arg === "--api-key") {
|
|
976
|
-
apiKey = requireOptionValue(args, ++index, "--api-key");
|
|
977
|
-
continue;
|
|
978
|
-
}
|
|
979
|
-
if (arg.startsWith("--api-key=")) {
|
|
980
|
-
apiKey = arg.slice("--api-key=".length);
|
|
981
|
-
continue;
|
|
982
|
-
}
|
|
983
987
|
if (arg === "--project") {
|
|
984
988
|
projectId = normalizeProjectValue(requireOptionValue(args, ++index, "--project"), "project");
|
|
985
989
|
continue;
|
|
@@ -1005,9 +1009,6 @@ function parseRunnerSlotSetOptions(args) {
|
|
|
1005
1009
|
}
|
|
1006
1010
|
throw new Error(`Unexpected runner slot set argument: ${arg}`);
|
|
1007
1011
|
}
|
|
1008
|
-
if (!apiKey) {
|
|
1009
|
-
throw new Error("runner slot set requires --api-key or TAPI_API_KEY.");
|
|
1010
|
-
}
|
|
1011
1012
|
if (!projectId) {
|
|
1012
1013
|
throw new Error("runner slot set requires --project or TAPI_PROJECT_ID.");
|
|
1013
1014
|
}
|
|
@@ -1022,7 +1023,6 @@ function parseRunnerSlotSetOptions(args) {
|
|
|
1022
1023
|
}
|
|
1023
1024
|
return {
|
|
1024
1025
|
apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
|
|
1025
|
-
apiKey,
|
|
1026
1026
|
projectId,
|
|
1027
1027
|
runnerId,
|
|
1028
1028
|
slotIndex,
|
|
@@ -1031,7 +1031,7 @@ function parseRunnerSlotSetOptions(args) {
|
|
|
1031
1031
|
}
|
|
1032
1032
|
async function createQueue(args) {
|
|
1033
1033
|
try {
|
|
1034
|
-
const options = parseQueueCreateOptions(args);
|
|
1034
|
+
const options = await withSdkAuth(parseQueueCreateOptions(args));
|
|
1035
1035
|
const queue = await withCliSpinner(`Creating Tapi queue`, () => postQueueCreate(options));
|
|
1036
1036
|
console.log(JSON.stringify(queue, null, 2));
|
|
1037
1037
|
return 0;
|
|
@@ -1043,7 +1043,7 @@ async function createQueue(args) {
|
|
|
1043
1043
|
}
|
|
1044
1044
|
async function addTappQueue(args) {
|
|
1045
1045
|
try {
|
|
1046
|
-
const options = parseTappQueueAddOptions(args);
|
|
1046
|
+
const options = await withSdkAuth(parseTappQueueAddOptions(args));
|
|
1047
1047
|
const result = await withCliSpinner(`Attaching Tapi queue ${options.queueId} to ${options.tappId}`, () => postTappQueueAdd(options));
|
|
1048
1048
|
console.log(JSON.stringify(result, null, 2));
|
|
1049
1049
|
return 0;
|
|
@@ -1055,7 +1055,7 @@ async function addTappQueue(args) {
|
|
|
1055
1055
|
}
|
|
1056
1056
|
async function setRunnerSlot(args) {
|
|
1057
1057
|
try {
|
|
1058
|
-
const options = parseRunnerSlotSetOptions(args);
|
|
1058
|
+
const options = await withSdkAuth(parseRunnerSlotSetOptions(args));
|
|
1059
1059
|
const result = await withCliSpinner(`Assigning runner slot ${options.runnerId}/${options.slotIndex} to queue ${options.queueId}`, () => putRunnerSlot(options));
|
|
1060
1060
|
console.log(JSON.stringify(result, null, 2));
|
|
1061
1061
|
return 0;
|
|
@@ -1066,13 +1066,7 @@ async function setRunnerSlot(args) {
|
|
|
1066
1066
|
}
|
|
1067
1067
|
}
|
|
1068
1068
|
async function postQueueCreate(options) {
|
|
1069
|
-
const headers =
|
|
1070
|
-
"Content-Type": "application/json",
|
|
1071
|
-
"X-API-Key": options.apiKey,
|
|
1072
|
-
};
|
|
1073
|
-
if (options.projectId) {
|
|
1074
|
-
headers["X-Tapi-Project"] = options.projectId;
|
|
1075
|
-
}
|
|
1069
|
+
const headers = sdkJsonHeaders(options.authToken, options.projectId);
|
|
1076
1070
|
const response = await fetch(`${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/queues`, {
|
|
1077
1071
|
method: "POST",
|
|
1078
1072
|
headers,
|
|
@@ -1091,11 +1085,7 @@ async function postQueueCreate(options) {
|
|
|
1091
1085
|
async function postTappQueueAdd(options) {
|
|
1092
1086
|
const response = await fetch(`${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/tapps/${encodeURIComponent(options.tappId)}/queues`, {
|
|
1093
1087
|
method: "POST",
|
|
1094
|
-
headers:
|
|
1095
|
-
"Content-Type": "application/json",
|
|
1096
|
-
"X-API-Key": options.apiKey,
|
|
1097
|
-
"X-Tapi-Project": options.tappId,
|
|
1098
|
-
},
|
|
1088
|
+
headers: sdkJsonHeaders(options.authToken, options.tappId),
|
|
1099
1089
|
body: JSON.stringify({ queueId: options.queueId }),
|
|
1100
1090
|
});
|
|
1101
1091
|
const body = await readJsonBody(response);
|
|
@@ -1108,11 +1098,7 @@ async function postTappQueueAdd(options) {
|
|
|
1108
1098
|
async function putRunnerSlot(options) {
|
|
1109
1099
|
const response = await fetch(`${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/runners/${encodeURIComponent(options.runnerId)}/slots/${encodeURIComponent(String(options.slotIndex))}`, {
|
|
1110
1100
|
method: "PUT",
|
|
1111
|
-
headers:
|
|
1112
|
-
"Content-Type": "application/json",
|
|
1113
|
-
"X-API-Key": options.apiKey,
|
|
1114
|
-
"X-Tapi-Project": options.projectId,
|
|
1115
|
-
},
|
|
1101
|
+
headers: sdkJsonHeaders(options.authToken, options.projectId),
|
|
1116
1102
|
body: JSON.stringify({ queueId: options.queueId }),
|
|
1117
1103
|
});
|
|
1118
1104
|
const body = await readJsonBody(response);
|
|
@@ -1125,7 +1111,6 @@ async function putRunnerSlot(options) {
|
|
|
1125
1111
|
export function parseRunnerSetupOptions(args) {
|
|
1126
1112
|
const workspace = loadWorkspace();
|
|
1127
1113
|
let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
|
|
1128
|
-
let apiKey = envString("TAPI_API_KEY") || "";
|
|
1129
1114
|
let projectId = envString("TAPI_PROJECT_ID") || workspace?.projectId || "";
|
|
1130
1115
|
let runnerId = envString("TAPI_RUNNER_ID") || "";
|
|
1131
1116
|
let port = Number(envString("TAPI_RUNNER_SETUP_PORT") || "17687");
|
|
@@ -1146,14 +1131,6 @@ export function parseRunnerSetupOptions(args) {
|
|
|
1146
1131
|
apiBaseUrl = arg.slice("--server=".length);
|
|
1147
1132
|
continue;
|
|
1148
1133
|
}
|
|
1149
|
-
if (arg === "--api-key") {
|
|
1150
|
-
apiKey = requireOptionValue(args, ++index, "--api-key");
|
|
1151
|
-
continue;
|
|
1152
|
-
}
|
|
1153
|
-
if (arg.startsWith("--api-key=")) {
|
|
1154
|
-
apiKey = arg.slice("--api-key=".length);
|
|
1155
|
-
continue;
|
|
1156
|
-
}
|
|
1157
1134
|
if (arg === "--project") {
|
|
1158
1135
|
projectId = normalizeProjectValue(requireOptionValue(args, ++index, "--project"), "project");
|
|
1159
1136
|
continue;
|
|
@@ -1191,9 +1168,6 @@ export function parseRunnerSetupOptions(args) {
|
|
|
1191
1168
|
}
|
|
1192
1169
|
throw new Error(`Unexpected runner setup argument: ${arg}`);
|
|
1193
1170
|
}
|
|
1194
|
-
if (!apiKey) {
|
|
1195
|
-
throw new Error("runner setup requires --api-key or TAPI_API_KEY.");
|
|
1196
|
-
}
|
|
1197
1171
|
if (!projectId) {
|
|
1198
1172
|
throw new Error("runner setup requires --project or TAPI_PROJECT_ID.");
|
|
1199
1173
|
}
|
|
@@ -1205,7 +1179,6 @@ export function parseRunnerSetupOptions(args) {
|
|
|
1205
1179
|
}
|
|
1206
1180
|
return {
|
|
1207
1181
|
apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
|
|
1208
|
-
apiKey,
|
|
1209
1182
|
projectId,
|
|
1210
1183
|
runnerId,
|
|
1211
1184
|
port,
|
|
@@ -1214,7 +1187,7 @@ export function parseRunnerSetupOptions(args) {
|
|
|
1214
1187
|
}
|
|
1215
1188
|
async function runRunnerSetup(args) {
|
|
1216
1189
|
try {
|
|
1217
|
-
const options = parseRunnerSetupOptions(args);
|
|
1190
|
+
const options = await withSdkAuth(parseRunnerSetupOptions(args));
|
|
1218
1191
|
const handle = await startRunnerSetupServer(options);
|
|
1219
1192
|
if (options.openBrowser) {
|
|
1220
1193
|
openUrlInBrowser(handle.url);
|
|
@@ -1252,7 +1225,7 @@ export async function executeRunnerSetupAction(options, action, fetchImpl = fetc
|
|
|
1252
1225
|
if (!queueId) {
|
|
1253
1226
|
queue = await sdkJsonRequest(fetchImpl, `${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/queues`, {
|
|
1254
1227
|
method: "POST",
|
|
1255
|
-
headers: sdkJsonHeaders(options.
|
|
1228
|
+
headers: sdkJsonHeaders(options.authToken, options.projectId),
|
|
1256
1229
|
body: JSON.stringify({
|
|
1257
1230
|
displayName: String(action.displayName || `${options.projectId} queue`).trim(),
|
|
1258
1231
|
maxSlots,
|
|
@@ -1265,7 +1238,7 @@ export async function executeRunnerSetupAction(options, action, fetchImpl = fetc
|
|
|
1265
1238
|
}
|
|
1266
1239
|
const attachment = await sdkJsonRequest(fetchImpl, `${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/tapps/${encodeURIComponent(options.projectId)}/queues`, {
|
|
1267
1240
|
method: "POST",
|
|
1268
|
-
headers: sdkJsonHeaders(options.
|
|
1241
|
+
headers: sdkJsonHeaders(options.authToken, options.projectId),
|
|
1269
1242
|
body: JSON.stringify({ queueId }),
|
|
1270
1243
|
});
|
|
1271
1244
|
const slots = [];
|
|
@@ -1273,7 +1246,7 @@ export async function executeRunnerSetupAction(options, action, fetchImpl = fetc
|
|
|
1273
1246
|
const slotIndex = slotStart + offset;
|
|
1274
1247
|
slots.push(await sdkJsonRequest(fetchImpl, `${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/runners/${encodeURIComponent(options.runnerId)}/slots/${encodeURIComponent(String(slotIndex))}`, {
|
|
1275
1248
|
method: "PUT",
|
|
1276
|
-
headers: sdkJsonHeaders(options.
|
|
1249
|
+
headers: sdkJsonHeaders(options.authToken, options.projectId),
|
|
1277
1250
|
body: JSON.stringify({ queueId }),
|
|
1278
1251
|
}));
|
|
1279
1252
|
}
|
|
@@ -1416,12 +1389,22 @@ async function sdkJsonRequest(fetchImpl, url, init) {
|
|
|
1416
1389
|
}
|
|
1417
1390
|
return body ?? {};
|
|
1418
1391
|
}
|
|
1419
|
-
function sdkJsonHeaders(
|
|
1420
|
-
|
|
1392
|
+
function sdkJsonHeaders(authToken, projectId) {
|
|
1393
|
+
const headers = {
|
|
1421
1394
|
"Content-Type": "application/json",
|
|
1422
|
-
|
|
1423
|
-
"X-Tapi-Project": projectId,
|
|
1395
|
+
Authorization: `Bearer ${requireSdkAuthToken(authToken)}`,
|
|
1424
1396
|
};
|
|
1397
|
+
if (projectId) {
|
|
1398
|
+
headers["X-Tapi-Project"] = projectId;
|
|
1399
|
+
}
|
|
1400
|
+
return headers;
|
|
1401
|
+
}
|
|
1402
|
+
function requireSdkAuthToken(authToken) {
|
|
1403
|
+
const normalized = String(authToken || "").trim();
|
|
1404
|
+
if (!normalized) {
|
|
1405
|
+
throw new Error("Tapi sign-in is required. Run `tapi login`, then retry.");
|
|
1406
|
+
}
|
|
1407
|
+
return normalized;
|
|
1425
1408
|
}
|
|
1426
1409
|
function coercePositiveInteger(value, fallback, label) {
|
|
1427
1410
|
const text = value === undefined || value === null || value === "" ? String(fallback) : String(value);
|
|
@@ -1444,7 +1427,6 @@ function parseNonNegativeInteger(value, label) {
|
|
|
1444
1427
|
function parseTriggerSyncOptions(args) {
|
|
1445
1428
|
const workspace = loadWorkspace();
|
|
1446
1429
|
let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
|
|
1447
|
-
let apiKey = envString("TAPI_API_KEY") || "";
|
|
1448
1430
|
let projectId = envString("TAPI_PROJECT_ID") || workspace?.projectId || "";
|
|
1449
1431
|
let configPath = "";
|
|
1450
1432
|
const root = workspace?.root || process.cwd();
|
|
@@ -1464,14 +1446,6 @@ function parseTriggerSyncOptions(args) {
|
|
|
1464
1446
|
apiBaseUrl = arg.slice("--server=".length);
|
|
1465
1447
|
continue;
|
|
1466
1448
|
}
|
|
1467
|
-
if (arg === "--api-key") {
|
|
1468
|
-
apiKey = requireOptionValue(args, ++index, "--api-key");
|
|
1469
|
-
continue;
|
|
1470
|
-
}
|
|
1471
|
-
if (arg.startsWith("--api-key=")) {
|
|
1472
|
-
apiKey = arg.slice("--api-key=".length);
|
|
1473
|
-
continue;
|
|
1474
|
-
}
|
|
1475
1449
|
if (arg === "--project") {
|
|
1476
1450
|
projectId = normalizeProjectValue(requireOptionValue(args, ++index, "--project"), "project");
|
|
1477
1451
|
continue;
|
|
@@ -1490,29 +1464,25 @@ function parseTriggerSyncOptions(args) {
|
|
|
1490
1464
|
}
|
|
1491
1465
|
throw new Error(`Unknown triggers option: ${arg}`);
|
|
1492
1466
|
}
|
|
1493
|
-
if (!apiKey) {
|
|
1494
|
-
throw new Error("triggers sync requires --api-key or TAPI_API_KEY.");
|
|
1495
|
-
}
|
|
1496
1467
|
if (!projectId) {
|
|
1497
1468
|
throw new Error("triggers sync requires --project, TAPI_PROJECT_ID, or .tapi/project.json.");
|
|
1498
1469
|
}
|
|
1499
1470
|
return {
|
|
1500
1471
|
apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
|
|
1501
|
-
apiKey,
|
|
1502
1472
|
projectId,
|
|
1503
1473
|
configPath: configPath ? resolve(root, configPath) : defaultTriggerConfigPath(root),
|
|
1504
1474
|
};
|
|
1505
1475
|
}
|
|
1506
1476
|
async function syncServiceTriggers(args) {
|
|
1507
1477
|
try {
|
|
1508
|
-
const options = parseTriggerSyncOptions(args);
|
|
1478
|
+
const options = await withSdkAuth(parseTriggerSyncOptions(args));
|
|
1509
1479
|
const triggers = await readServiceTriggerConfig(options.configPath);
|
|
1510
1480
|
if (triggers.length === 0) {
|
|
1511
1481
|
throw new Error(`No Tapi service triggers found in ${options.configPath}.`);
|
|
1512
1482
|
}
|
|
1513
1483
|
const client = new TapiClient({
|
|
1514
1484
|
baseUrl: options.apiBaseUrl,
|
|
1515
|
-
|
|
1485
|
+
authToken: options.authToken,
|
|
1516
1486
|
tappId: options.projectId,
|
|
1517
1487
|
projectId: options.projectId,
|
|
1518
1488
|
});
|
|
@@ -1532,7 +1502,6 @@ async function syncServiceTriggers(args) {
|
|
|
1532
1502
|
function parseSessionsOptions(args) {
|
|
1533
1503
|
const workspace = loadWorkspace();
|
|
1534
1504
|
let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
|
|
1535
|
-
let apiKey = envString("TAPI_API_KEY") || "";
|
|
1536
1505
|
let projectId = envString("TAPI_PROJECT_ID") || workspace?.projectId || "";
|
|
1537
1506
|
let site = "";
|
|
1538
1507
|
let sessionId = "";
|
|
@@ -1554,14 +1523,6 @@ function parseSessionsOptions(args) {
|
|
|
1554
1523
|
apiBaseUrl = arg.slice("--server=".length);
|
|
1555
1524
|
continue;
|
|
1556
1525
|
}
|
|
1557
|
-
if (arg === "--api-key") {
|
|
1558
|
-
apiKey = requireOptionValue(args, ++index, "--api-key");
|
|
1559
|
-
continue;
|
|
1560
|
-
}
|
|
1561
|
-
if (arg.startsWith("--api-key=")) {
|
|
1562
|
-
apiKey = arg.slice("--api-key=".length);
|
|
1563
|
-
continue;
|
|
1564
|
-
}
|
|
1565
1526
|
if (arg === "--project") {
|
|
1566
1527
|
projectId = normalizeProjectValue(requireOptionValue(args, ++index, "--project"), "project");
|
|
1567
1528
|
continue;
|
|
@@ -1596,9 +1557,6 @@ function parseSessionsOptions(args) {
|
|
|
1596
1557
|
}
|
|
1597
1558
|
throw new Error(`Unexpected sessions argument: ${arg}`);
|
|
1598
1559
|
}
|
|
1599
|
-
if (!apiKey) {
|
|
1600
|
-
throw new Error("sessions command requires --api-key or TAPI_API_KEY.");
|
|
1601
|
-
}
|
|
1602
1560
|
if (!projectId) {
|
|
1603
1561
|
throw new Error("sessions command requires --project, TAPI_PROJECT_ID, or .tapi/project.json.");
|
|
1604
1562
|
}
|
|
@@ -1606,7 +1564,6 @@ function parseSessionsOptions(args) {
|
|
|
1606
1564
|
sessionId,
|
|
1607
1565
|
options: {
|
|
1608
1566
|
apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
|
|
1609
|
-
apiKey,
|
|
1610
1567
|
projectId,
|
|
1611
1568
|
site: site || undefined,
|
|
1612
1569
|
json,
|
|
@@ -1617,18 +1574,19 @@ function parseSessionsOptions(args) {
|
|
|
1617
1574
|
async function listDevSessions(args) {
|
|
1618
1575
|
try {
|
|
1619
1576
|
const { options } = parseSessionsOptions(args);
|
|
1620
|
-
const
|
|
1621
|
-
|
|
1577
|
+
const authenticated = await withSdkAuth(options);
|
|
1578
|
+
const sessions = await fetchDevSessions(authenticated);
|
|
1579
|
+
if (authenticated.json) {
|
|
1622
1580
|
console.log(JSON.stringify(sessions, null, 2));
|
|
1623
1581
|
return 0;
|
|
1624
1582
|
}
|
|
1625
1583
|
const rows = flattenDevSessions(sessions);
|
|
1626
1584
|
if (rows.length === 0) {
|
|
1627
|
-
console.log(`No Tapi dev sessions found for project ${sessions.projectId ||
|
|
1585
|
+
console.log(`No Tapi dev sessions found for project ${sessions.projectId || authenticated.projectId}.`);
|
|
1628
1586
|
return 0;
|
|
1629
1587
|
}
|
|
1630
|
-
if (!
|
|
1631
|
-
return await runSessionsPicker(sessions,
|
|
1588
|
+
if (!authenticated.noInteractive && process.stdin.isTTY && process.stdout.isTTY && rows.some((session) => session.openable)) {
|
|
1589
|
+
return await runSessionsPicker(sessions, authenticated);
|
|
1632
1590
|
}
|
|
1633
1591
|
console.log(renderDevSessions(sessions));
|
|
1634
1592
|
const openable = rows.find((session) => session.openable);
|
|
@@ -1645,15 +1603,16 @@ async function listDevSessions(args) {
|
|
|
1645
1603
|
async function openDevSession(args) {
|
|
1646
1604
|
try {
|
|
1647
1605
|
const { sessionId, options } = parseSessionsOptions(args);
|
|
1606
|
+
const authenticated = await withSdkAuth(options);
|
|
1648
1607
|
if (!sessionId) {
|
|
1649
1608
|
throw new Error("sessions open requires a session id.");
|
|
1650
1609
|
}
|
|
1651
|
-
const sessions = await fetchDevSessions(
|
|
1610
|
+
const sessions = await fetchDevSessions(authenticated);
|
|
1652
1611
|
const session = findDevSession(sessions, sessionId);
|
|
1653
1612
|
if (!session) {
|
|
1654
1613
|
throw new Error(`Tapi session not found: ${sessionId}`);
|
|
1655
1614
|
}
|
|
1656
|
-
return await openDevSessionInStudio(session,
|
|
1615
|
+
return await openDevSessionInStudio(session, authenticated);
|
|
1657
1616
|
}
|
|
1658
1617
|
catch (error) {
|
|
1659
1618
|
console.error(formatError(error));
|
|
@@ -1663,7 +1622,7 @@ async function openDevSession(args) {
|
|
|
1663
1622
|
async function fetchDevSessions(options) {
|
|
1664
1623
|
const client = new TapiClient({
|
|
1665
1624
|
baseUrl: options.apiBaseUrl,
|
|
1666
|
-
|
|
1625
|
+
authToken: options.authToken,
|
|
1667
1626
|
tappId: options.projectId,
|
|
1668
1627
|
projectId: options.projectId,
|
|
1669
1628
|
});
|
|
@@ -2103,7 +2062,7 @@ function rejectNonPolicyTriggerRuntimeOwner(runtime, fieldName) {
|
|
|
2103
2062
|
async function fetchApiCatalog(options) {
|
|
2104
2063
|
const client = new TapiClient({
|
|
2105
2064
|
baseUrl: options.apiBaseUrl,
|
|
2106
|
-
|
|
2065
|
+
authToken: options.authToken,
|
|
2107
2066
|
tappId: options.projectId,
|
|
2108
2067
|
projectId: options.projectId,
|
|
2109
2068
|
});
|
|
@@ -2425,7 +2384,7 @@ async function installService(options) {
|
|
|
2425
2384
|
destination: zipPath,
|
|
2426
2385
|
expectedSha256: manifest.sha256,
|
|
2427
2386
|
});
|
|
2428
|
-
await
|
|
2387
|
+
await downloadAndVerify(manifest.url, zipPath, manifest.sha256, "Tapi Service artifact");
|
|
2429
2388
|
await event.phase("download.verified", {
|
|
2430
2389
|
zipPath,
|
|
2431
2390
|
expectedSha256: manifest.sha256,
|
|
@@ -2880,7 +2839,7 @@ async function installStudio(options) {
|
|
|
2880
2839
|
destination: installerPath,
|
|
2881
2840
|
expectedSha256: manifest.sha256,
|
|
2882
2841
|
});
|
|
2883
|
-
await
|
|
2842
|
+
await downloadAndVerify(manifest.url, installerPath, manifest.sha256);
|
|
2884
2843
|
await event.phase("download.verified", {
|
|
2885
2844
|
installerPath,
|
|
2886
2845
|
expectedSha256: manifest.sha256,
|
|
@@ -3033,23 +2992,24 @@ async function openStudio(options) {
|
|
|
3033
2992
|
if (options.downloadOnly) {
|
|
3034
2993
|
throw new Error("--download-only is only supported with `tapi studio install`.");
|
|
3035
2994
|
}
|
|
3036
|
-
await
|
|
2995
|
+
const launchOptions = await resolveStudioTappSelection(options);
|
|
2996
|
+
await ensureServiceReadyForStudio(launchOptions);
|
|
3037
2997
|
await closeExistingPortableStudioServersForFreshLaunch();
|
|
3038
|
-
if (!
|
|
3039
|
-
const portable = await ensurePortableStudioServer(
|
|
2998
|
+
if (!launchOptions.exePath) {
|
|
2999
|
+
const portable = await ensurePortableStudioServer(launchOptions);
|
|
3040
3000
|
if (portable) {
|
|
3041
|
-
await launchPortableStudioServer(portable.exePath,
|
|
3001
|
+
await launchPortableStudioServer(portable.exePath, launchOptions, portable.manifest);
|
|
3042
3002
|
return 0;
|
|
3043
3003
|
}
|
|
3044
3004
|
}
|
|
3045
|
-
let exePath = findStudioExecutable(
|
|
3046
|
-
if (!exePath && !
|
|
3005
|
+
let exePath = findStudioExecutable(launchOptions);
|
|
3006
|
+
if (!exePath && !launchOptions.exePath) {
|
|
3047
3007
|
console.log("Tapi Studio executable was not found. Installing Tapi Studio now...");
|
|
3048
|
-
const installCode = await installStudio(
|
|
3008
|
+
const installCode = await installStudio(launchOptions);
|
|
3049
3009
|
if (installCode !== 0) {
|
|
3050
3010
|
return installCode;
|
|
3051
3011
|
}
|
|
3052
|
-
exePath = findStudioExecutable(
|
|
3012
|
+
exePath = findStudioExecutable(launchOptions);
|
|
3053
3013
|
}
|
|
3054
3014
|
if (!exePath || !existsSync(exePath)) {
|
|
3055
3015
|
console.error("Tapi Studio executable was not found.");
|
|
@@ -3058,7 +3018,7 @@ async function openStudio(options) {
|
|
|
3058
3018
|
}
|
|
3059
3019
|
const child = spawn(exePath, [], {
|
|
3060
3020
|
detached: true,
|
|
3061
|
-
env: buildStudioLaunchEnv(
|
|
3021
|
+
env: buildStudioLaunchEnv(launchOptions),
|
|
3062
3022
|
stdio: "ignore",
|
|
3063
3023
|
windowsHide: false,
|
|
3064
3024
|
});
|
|
@@ -3066,6 +3026,177 @@ async function openStudio(options) {
|
|
|
3066
3026
|
console.log(`Opened Tapi Studio: ${exePath}`);
|
|
3067
3027
|
return 0;
|
|
3068
3028
|
}
|
|
3029
|
+
async function resolveStudioTappSelection(options) {
|
|
3030
|
+
if (options.tappSelectionMode === "none") {
|
|
3031
|
+
return options.projectId ? await rememberSelectedStudioTapp(options, options.projectId) : options;
|
|
3032
|
+
}
|
|
3033
|
+
if (options.tappSelectionMode === "auto" && options.projectIdSource === "env" && options.projectId) {
|
|
3034
|
+
return await rememberSelectedStudioTapp(options, options.projectId);
|
|
3035
|
+
}
|
|
3036
|
+
const lastSelection = await readStudioSelectionRecord();
|
|
3037
|
+
if (options.tappSelectionMode === "last") {
|
|
3038
|
+
if (!lastSelection?.lastTappId) {
|
|
3039
|
+
throw new Error("No previous Studio Tapp selection exists. Run `tapi studio --select` or `tapi studio --tapp <tapp>`.");
|
|
3040
|
+
}
|
|
3041
|
+
return await rememberSelectedStudioTapp(options, lastSelection.lastTappId);
|
|
3042
|
+
}
|
|
3043
|
+
let tapps = [];
|
|
3044
|
+
try {
|
|
3045
|
+
const authenticated = await withSdkAuth({ authToken: undefined });
|
|
3046
|
+
tapps = await fetchAvailableTappsForStudio(options, authenticated.authToken);
|
|
3047
|
+
}
|
|
3048
|
+
catch (error) {
|
|
3049
|
+
if (options.projectId) {
|
|
3050
|
+
console.warn(`Could not load Tapp list (${formatError(error)}). Opening Studio with ${options.projectId}.`);
|
|
3051
|
+
return await rememberSelectedStudioTapp(options, options.projectId);
|
|
3052
|
+
}
|
|
3053
|
+
throw error;
|
|
3054
|
+
}
|
|
3055
|
+
if (!tapps.length) {
|
|
3056
|
+
if (options.projectId) {
|
|
3057
|
+
return await rememberSelectedStudioTapp(options, options.projectId);
|
|
3058
|
+
}
|
|
3059
|
+
throw new Error("No Tapps found for this account. Create one with `tapi tapp create <name>`, then run `tapi studio`.");
|
|
3060
|
+
}
|
|
3061
|
+
const preferred = preferredStudioTappId(options, lastSelection, tapps);
|
|
3062
|
+
if (!studioTappPickerEnabled()) {
|
|
3063
|
+
const selected = options.projectId
|
|
3064
|
+
|| lastSelection?.lastTappId
|
|
3065
|
+
|| (tapps.length === 1 ? tapps[0]?.tappId : "");
|
|
3066
|
+
if (!selected) {
|
|
3067
|
+
throw new Error("Multiple Tapps are available. Use `tapi studio --tapp <tapp>` or run in an interactive terminal.");
|
|
3068
|
+
}
|
|
3069
|
+
return await rememberSelectedStudioTapp(options, selected);
|
|
3070
|
+
}
|
|
3071
|
+
const selected = await select({
|
|
3072
|
+
message: "Select Tapp",
|
|
3073
|
+
choices: tapps.map((tapp) => ({
|
|
3074
|
+
name: formatTappChoice(tapp),
|
|
3075
|
+
value: tapp.tappId,
|
|
3076
|
+
})),
|
|
3077
|
+
default: preferred || tapps[0]?.tappId,
|
|
3078
|
+
});
|
|
3079
|
+
return await rememberSelectedStudioTapp(options, selected);
|
|
3080
|
+
}
|
|
3081
|
+
async function fetchAvailableTappsForStudio(options, authToken, fetchImpl = fetch) {
|
|
3082
|
+
const response = await fetchImpl(`${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/tapps`, {
|
|
3083
|
+
method: "GET",
|
|
3084
|
+
headers: sdkJsonHeaders(authToken, options.projectId),
|
|
3085
|
+
});
|
|
3086
|
+
const body = await readJsonBody(response);
|
|
3087
|
+
if (!response.ok) {
|
|
3088
|
+
throw new Error(`Failed to list Tapps: HTTP ${response.status}`);
|
|
3089
|
+
}
|
|
3090
|
+
return normalizeTappList(body);
|
|
3091
|
+
}
|
|
3092
|
+
function normalizeTappList(body) {
|
|
3093
|
+
const raw = isRecord(body) && Array.isArray(body.tapps) ? body.tapps : [];
|
|
3094
|
+
const seen = new Set();
|
|
3095
|
+
const tapps = [];
|
|
3096
|
+
for (const item of raw) {
|
|
3097
|
+
if (!isRecord(item)) {
|
|
3098
|
+
continue;
|
|
3099
|
+
}
|
|
3100
|
+
const tappId = typeof item.tappId === "string" ? item.tappId.trim() : "";
|
|
3101
|
+
if (!tappId || seen.has(tappId)) {
|
|
3102
|
+
continue;
|
|
3103
|
+
}
|
|
3104
|
+
seen.add(tappId);
|
|
3105
|
+
tapps.push({
|
|
3106
|
+
tappId,
|
|
3107
|
+
name: typeof item.name === "string" && item.name.trim() ? item.name.trim() : tappId,
|
|
3108
|
+
serviceCount: normalizeNonnegativeInteger(item.serviceCount),
|
|
3109
|
+
queueCount: normalizeNonnegativeInteger(item.queueCount),
|
|
3110
|
+
});
|
|
3111
|
+
}
|
|
3112
|
+
return tapps;
|
|
3113
|
+
}
|
|
3114
|
+
function normalizeNonnegativeInteger(value) {
|
|
3115
|
+
const number = typeof value === "number" ? value : Number(value);
|
|
3116
|
+
return Number.isFinite(number) && number > 0 ? Math.floor(number) : 0;
|
|
3117
|
+
}
|
|
3118
|
+
function preferredStudioTappId(options, lastSelection, tapps) {
|
|
3119
|
+
const candidates = [
|
|
3120
|
+
options.projectId,
|
|
3121
|
+
lastSelection?.lastTappId,
|
|
3122
|
+
tapps[0]?.tappId,
|
|
3123
|
+
].map((value) => String(value || "").trim()).filter(Boolean);
|
|
3124
|
+
const available = new Set(tapps.map((tapp) => tapp.tappId));
|
|
3125
|
+
return candidates.find((candidate) => available.has(candidate)) || "";
|
|
3126
|
+
}
|
|
3127
|
+
function formatTappChoice(tapp) {
|
|
3128
|
+
const details = [
|
|
3129
|
+
`${tapp.serviceCount} service${tapp.serviceCount === 1 ? "" : "s"}`,
|
|
3130
|
+
`${tapp.queueCount} queue${tapp.queueCount === 1 ? "" : "s"}`,
|
|
3131
|
+
].join(", ");
|
|
3132
|
+
return tapp.name === tapp.tappId
|
|
3133
|
+
? `${tapp.tappId} ${pc.dim(`(${details})`)}`
|
|
3134
|
+
: `${tapp.name} ${pc.dim(`${tapp.tappId} (${details})`)}`;
|
|
3135
|
+
}
|
|
3136
|
+
function studioTappPickerEnabled() {
|
|
3137
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
3138
|
+
return false;
|
|
3139
|
+
}
|
|
3140
|
+
const raw = String(process.env.TAPI_STUDIO_TAPP_PICKER || "").trim().toLowerCase();
|
|
3141
|
+
if (["0", "false", "no", "off"].includes(raw)) {
|
|
3142
|
+
return false;
|
|
3143
|
+
}
|
|
3144
|
+
return true;
|
|
3145
|
+
}
|
|
3146
|
+
async function rememberSelectedStudioTapp(options, tappId) {
|
|
3147
|
+
const selected = normalizeProjectValue(tappId, "tapp");
|
|
3148
|
+
const projectSlug = options.projectId === selected && options.projectSlug
|
|
3149
|
+
? options.projectSlug
|
|
3150
|
+
: selected;
|
|
3151
|
+
await writeStudioSelectionRecord(selected);
|
|
3152
|
+
return {
|
|
3153
|
+
...options,
|
|
3154
|
+
projectId: selected,
|
|
3155
|
+
projectSlug,
|
|
3156
|
+
projectIdSource: "selection",
|
|
3157
|
+
workspaceMode: true,
|
|
3158
|
+
launchUrlQuery: studioLaunchQueryWithProject(options.launchUrlQuery, selected),
|
|
3159
|
+
};
|
|
3160
|
+
}
|
|
3161
|
+
function studioLaunchQueryWithProject(query, projectId) {
|
|
3162
|
+
const params = new URLSearchParams(String(query || "").replace(/^\?+/, ""));
|
|
3163
|
+
params.set("project", projectId);
|
|
3164
|
+
return params.toString();
|
|
3165
|
+
}
|
|
3166
|
+
async function readStudioSelectionRecord() {
|
|
3167
|
+
try {
|
|
3168
|
+
const payload = JSON.parse(await readFile(studioSelectionRecordPath(), "utf8"));
|
|
3169
|
+
if (!isRecord(payload) || payload.version !== 1) {
|
|
3170
|
+
return null;
|
|
3171
|
+
}
|
|
3172
|
+
const lastTappId = typeof payload.lastTappId === "string" ? payload.lastTappId.trim() : "";
|
|
3173
|
+
if (!lastTappId) {
|
|
3174
|
+
return null;
|
|
3175
|
+
}
|
|
3176
|
+
return {
|
|
3177
|
+
version: 1,
|
|
3178
|
+
lastTappId,
|
|
3179
|
+
updatedAt: typeof payload.updatedAt === "string" ? payload.updatedAt : "",
|
|
3180
|
+
};
|
|
3181
|
+
}
|
|
3182
|
+
catch (error) {
|
|
3183
|
+
if (error.code === "ENOENT") {
|
|
3184
|
+
return null;
|
|
3185
|
+
}
|
|
3186
|
+
return null;
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
async function writeStudioSelectionRecord(tappId) {
|
|
3190
|
+
await mkdir(getDefaultTapiDataDir(), { recursive: true });
|
|
3191
|
+
await writeJsonFile(studioSelectionRecordPath(), {
|
|
3192
|
+
version: 1,
|
|
3193
|
+
lastTappId: tappId,
|
|
3194
|
+
updatedAt: new Date().toISOString(),
|
|
3195
|
+
});
|
|
3196
|
+
}
|
|
3197
|
+
function studioSelectionRecordPath() {
|
|
3198
|
+
return join(getDefaultTapiDataDir(), "studio-selection.json");
|
|
3199
|
+
}
|
|
3069
3200
|
function findStudioExecutable(options) {
|
|
3070
3201
|
if (options.exePath) {
|
|
3071
3202
|
return existsSync(options.exePath) ? options.exePath : undefined;
|
|
@@ -3136,7 +3267,7 @@ async function installPortableStudioServer(options, manifest, event) {
|
|
|
3136
3267
|
destination: artifactPath,
|
|
3137
3268
|
expectedSha256: manifest.sha256,
|
|
3138
3269
|
});
|
|
3139
|
-
await
|
|
3270
|
+
await downloadAndVerify(manifest.url, artifactPath, manifest.sha256, "Tapi Studio server artifact");
|
|
3140
3271
|
await event.phase("download.verified", {
|
|
3141
3272
|
artifactPath,
|
|
3142
3273
|
expectedSha256: manifest.sha256,
|
|
@@ -3198,6 +3329,7 @@ async function launchPortableStudioServer(exePath, options, manifest) {
|
|
|
3198
3329
|
pid: childPid || undefined,
|
|
3199
3330
|
apiBaseUrl: options.apiBaseUrl,
|
|
3200
3331
|
projectId: options.projectId,
|
|
3332
|
+
tappId: options.projectId,
|
|
3201
3333
|
workspaceRoot: options.workspaceRoot,
|
|
3202
3334
|
workspaceConfig: options.workspace?.configPath,
|
|
3203
3335
|
manifestVersion: manifest.version,
|
|
@@ -3503,6 +3635,8 @@ async function downloadFile(url, destination, label = "download", options = {})
|
|
|
3503
3635
|
let destinationStream;
|
|
3504
3636
|
let receivedBytes = 0;
|
|
3505
3637
|
let nextProgressLogBytes = DOWNLOAD_PROGRESS_LOG_BYTES;
|
|
3638
|
+
let progressBar;
|
|
3639
|
+
let expectedBytes = 0;
|
|
3506
3640
|
const failIfStalled = (phase) => {
|
|
3507
3641
|
if (stallTimer) {
|
|
3508
3642
|
clearTimeout(stallTimer);
|
|
@@ -3516,7 +3650,6 @@ async function downloadFile(url, destination, label = "download", options = {})
|
|
|
3516
3650
|
}, stallTimeoutMs);
|
|
3517
3651
|
};
|
|
3518
3652
|
try {
|
|
3519
|
-
console.log(`Downloading ${label}...`);
|
|
3520
3653
|
failIfStalled("connecting");
|
|
3521
3654
|
const response = await fetch(url, { signal: controller.signal });
|
|
3522
3655
|
if (!response.ok) {
|
|
@@ -3526,12 +3659,29 @@ async function downloadFile(url, destination, label = "download", options = {})
|
|
|
3526
3659
|
throw new Error(`${label} response did not include a body.`);
|
|
3527
3660
|
}
|
|
3528
3661
|
failIfStalled("receiving data");
|
|
3662
|
+
expectedBytes = parseContentLength(typeof response.headers?.get === "function" ? response.headers.get("content-length") : null);
|
|
3663
|
+
progressBar = createDownloadProgressBar(label, expectedBytes);
|
|
3664
|
+
if (progressBar) {
|
|
3665
|
+
progressBar.start(expectedBytes, 0, {
|
|
3666
|
+
value: formatBytes(0),
|
|
3667
|
+
total: formatBytes(expectedBytes),
|
|
3668
|
+
});
|
|
3669
|
+
}
|
|
3670
|
+
else {
|
|
3671
|
+
console.log(`Downloading ${label}...`);
|
|
3672
|
+
}
|
|
3529
3673
|
source = Readable.fromWeb(response.body);
|
|
3530
3674
|
progressStream = new Transform({
|
|
3531
3675
|
transform(chunk, _encoding, callback) {
|
|
3532
3676
|
receivedBytes += downloadChunkByteLength(chunk);
|
|
3533
3677
|
failIfStalled("receiving data");
|
|
3534
|
-
if (
|
|
3678
|
+
if (progressBar) {
|
|
3679
|
+
progressBar.update(Math.min(receivedBytes, expectedBytes), {
|
|
3680
|
+
value: formatBytes(receivedBytes),
|
|
3681
|
+
total: formatBytes(expectedBytes),
|
|
3682
|
+
});
|
|
3683
|
+
}
|
|
3684
|
+
else if (receivedBytes >= nextProgressLogBytes) {
|
|
3535
3685
|
console.log(`Downloaded ${label}: ${formatBytes(receivedBytes)}...`);
|
|
3536
3686
|
while (receivedBytes >= nextProgressLogBytes) {
|
|
3537
3687
|
nextProgressLogBytes += DOWNLOAD_PROGRESS_LOG_BYTES;
|
|
@@ -3542,9 +3692,23 @@ async function downloadFile(url, destination, label = "download", options = {})
|
|
|
3542
3692
|
});
|
|
3543
3693
|
destinationStream = createWriteStream(destination);
|
|
3544
3694
|
await pipeline(source, progressStream, destinationStream);
|
|
3545
|
-
|
|
3695
|
+
if (progressBar) {
|
|
3696
|
+
progressBar.update(expectedBytes, {
|
|
3697
|
+
value: formatBytes(receivedBytes),
|
|
3698
|
+
total: formatBytes(expectedBytes),
|
|
3699
|
+
});
|
|
3700
|
+
progressBar.stop();
|
|
3701
|
+
progressBar = undefined;
|
|
3702
|
+
}
|
|
3703
|
+
else {
|
|
3704
|
+
console.log(`Downloaded ${label}: ${formatBytes(receivedBytes)}.`);
|
|
3705
|
+
}
|
|
3546
3706
|
}
|
|
3547
3707
|
catch (error) {
|
|
3708
|
+
if (progressBar) {
|
|
3709
|
+
progressBar.stop();
|
|
3710
|
+
progressBar = undefined;
|
|
3711
|
+
}
|
|
3548
3712
|
if (stallError) {
|
|
3549
3713
|
throw stallError;
|
|
3550
3714
|
}
|
|
@@ -3800,6 +3964,7 @@ async function readStudioInstanceRecord(options) {
|
|
|
3800
3964
|
pid: typeof parsed.pid === "number" && Number.isFinite(parsed.pid) ? parsed.pid : undefined,
|
|
3801
3965
|
apiBaseUrl: typeof parsed.apiBaseUrl === "string" ? parsed.apiBaseUrl : "",
|
|
3802
3966
|
projectId: typeof parsed.projectId === "string" ? parsed.projectId : undefined,
|
|
3967
|
+
tappId: typeof parsed.tappId === "string" ? parsed.tappId : undefined,
|
|
3803
3968
|
workspaceRoot: typeof parsed.workspaceRoot === "string" ? parsed.workspaceRoot : undefined,
|
|
3804
3969
|
workspaceConfig: typeof parsed.workspaceConfig === "string" ? parsed.workspaceConfig : undefined,
|
|
3805
3970
|
manifestVersion: typeof parsed.manifestVersion === "string" ? parsed.manifestVersion : undefined,
|
|
@@ -3925,6 +4090,31 @@ async function readJsonBody(response) {
|
|
|
3925
4090
|
return null;
|
|
3926
4091
|
}
|
|
3927
4092
|
}
|
|
4093
|
+
function createDownloadProgressBar(label, totalBytes) {
|
|
4094
|
+
if (!downloadProgressEnabled() || totalBytes <= 0) {
|
|
4095
|
+
return undefined;
|
|
4096
|
+
}
|
|
4097
|
+
return new SingleBar({
|
|
4098
|
+
format: `${label} |{bar}| {percentage}% | {value}/{total}`,
|
|
4099
|
+
stream: process.stderr,
|
|
4100
|
+
clearOnComplete: false,
|
|
4101
|
+
hideCursor: true,
|
|
4102
|
+
barsize: 32,
|
|
4103
|
+
barCompleteChar: "#",
|
|
4104
|
+
barIncompleteChar: "-",
|
|
4105
|
+
}, Presets.shades_classic);
|
|
4106
|
+
}
|
|
4107
|
+
function downloadProgressEnabled() {
|
|
4108
|
+
const raw = String(process.env.TAPI_CLI_PROGRESS || "").trim().toLowerCase();
|
|
4109
|
+
if (["0", "false", "no", "off"].includes(raw)) {
|
|
4110
|
+
return false;
|
|
4111
|
+
}
|
|
4112
|
+
return Boolean(process.stderr.isTTY);
|
|
4113
|
+
}
|
|
4114
|
+
function parseContentLength(value) {
|
|
4115
|
+
const parsed = Number(value || "");
|
|
4116
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : 0;
|
|
4117
|
+
}
|
|
3928
4118
|
async function readRequestJson(request) {
|
|
3929
4119
|
let body = "";
|
|
3930
4120
|
for await (const chunk of request) {
|
|
@@ -4382,6 +4572,9 @@ function installEventOptions(options) {
|
|
|
4382
4572
|
downloadOnly: options.downloadOnly,
|
|
4383
4573
|
silent: options.silent,
|
|
4384
4574
|
exePath: options.exePath,
|
|
4575
|
+
projectId: options.projectId,
|
|
4576
|
+
projectIdSource: options.projectIdSource,
|
|
4577
|
+
tappSelectionMode: options.tappSelectionMode,
|
|
4385
4578
|
installTokenProvided: Boolean(options.installToken),
|
|
4386
4579
|
installTokenSource: options.installTokenSource,
|
|
4387
4580
|
};
|
|
@@ -4508,11 +4701,14 @@ function readSdkVersion() {
|
|
|
4508
4701
|
function printHelp() {
|
|
4509
4702
|
console.log(`Tapi CLI
|
|
4510
4703
|
|
|
4511
|
-
Usage:
|
|
4512
|
-
tapi
|
|
4704
|
+
Usage:
|
|
4705
|
+
tapi login
|
|
4706
|
+
tapi init --project PROJECT
|
|
4513
4707
|
tapi link --project PROJECT
|
|
4514
|
-
tapi studio install [--channel pilot] [--api-base-url URL]
|
|
4515
|
-
tapi studio
|
|
4708
|
+
tapi studio install [--channel pilot] [--api-base-url URL]
|
|
4709
|
+
tapi studio
|
|
4710
|
+
tapi studio --tapp <tapp>
|
|
4711
|
+
tapi studio --select
|
|
4516
4712
|
tapi studio open
|
|
4517
4713
|
tapi studio doctor
|
|
4518
4714
|
tapi tapp create <tapp>
|
|
@@ -4528,12 +4724,13 @@ Usage:
|
|
|
4528
4724
|
tapi service status
|
|
4529
4725
|
tapi doctor
|
|
4530
4726
|
|
|
4531
|
-
Commands:
|
|
4532
|
-
|
|
4727
|
+
Commands:
|
|
4728
|
+
login Sign in with Firebase credentials for SDK commands
|
|
4729
|
+
init Create .tapi/project.json for this repo
|
|
4533
4730
|
link Rebind this repo to an existing Tapi project
|
|
4534
4731
|
studio install Download, verify, and run the Tapi Studio installer
|
|
4535
|
-
studio Open Tapi Studio for
|
|
4536
|
-
studio open Open Tapi Studio for
|
|
4732
|
+
studio Open Tapi Studio for a selected Tapp
|
|
4733
|
+
studio open Open Tapi Studio for a selected Tapp
|
|
4537
4734
|
studio doctor Check local SDK and Studio release configuration
|
|
4538
4735
|
tapp create Create a Tapp
|
|
4539
4736
|
queue create Create a queue for service-run routing
|
|
@@ -4551,18 +4748,26 @@ Commands:
|
|
|
4551
4748
|
doctor Alias for studio doctor
|
|
4552
4749
|
`);
|
|
4553
4750
|
}
|
|
4751
|
+
function printLoginHelp() {
|
|
4752
|
+
console.log(`Tapi login
|
|
4753
|
+
|
|
4754
|
+
Usage:
|
|
4755
|
+
tapi login
|
|
4756
|
+
|
|
4757
|
+
Signs in with the browser and caches Firebase credentials for SDK commands.
|
|
4758
|
+
`);
|
|
4759
|
+
}
|
|
4554
4760
|
function printTappHelp() {
|
|
4555
4761
|
console.log(`Tapi Tapp commands
|
|
4556
4762
|
|
|
4557
4763
|
Usage:
|
|
4558
|
-
tapi tapp create <tapp> [--name NAME] [--api-base-url URL]
|
|
4559
|
-
tapi tapp service add <tapp> <service.call> --service-map ID --entry ENTRY [--api-base-url URL]
|
|
4560
|
-
tapi tapp queue add <tapp> <queue-id> [--api-base-url URL]
|
|
4764
|
+
tapi tapp create <tapp> [--name NAME] [--api-base-url URL]
|
|
4765
|
+
tapi tapp service add <tapp> <service.call> --service-map ID --entry ENTRY [--api-base-url URL]
|
|
4766
|
+
tapi tapp queue add <tapp> <queue-id> [--api-base-url URL]
|
|
4561
4767
|
|
|
4562
4768
|
Options:
|
|
4563
4769
|
--api-base-url <url> Tapi API base URL
|
|
4564
4770
|
--server <url> Alias for --api-base-url
|
|
4565
|
-
--api-key <key> Tapi API key; defaults to TAPI_API_KEY
|
|
4566
4771
|
--tapp-id <id> Explicit Tapp id for tapp create
|
|
4567
4772
|
--name <name> Display name for tapp create
|
|
4568
4773
|
--service-map <id> Backing ServiceMap id
|
|
@@ -4574,12 +4779,11 @@ function printQueueHelp() {
|
|
|
4574
4779
|
console.log(`Tapi queue commands
|
|
4575
4780
|
|
|
4576
4781
|
Usage:
|
|
4577
|
-
tapi queue create [display-name] [--max-slots N] [--project TAPP] [--api-base-url URL]
|
|
4782
|
+
tapi queue create [display-name] [--max-slots N] [--project TAPP] [--api-base-url URL]
|
|
4578
4783
|
|
|
4579
4784
|
Options:
|
|
4580
4785
|
--api-base-url <url> Tapi API base URL
|
|
4581
4786
|
--server <url> Alias for --api-base-url
|
|
4582
|
-
--api-key <key> Tapi API key; defaults to TAPI_API_KEY
|
|
4583
4787
|
--project <id> Optional project scope for the queue request
|
|
4584
4788
|
--max-slots <n> Maximum runner slots this queue may use
|
|
4585
4789
|
`);
|
|
@@ -4588,13 +4792,12 @@ function printRunnerHelp() {
|
|
|
4588
4792
|
console.log(`Tapi runner commands
|
|
4589
4793
|
|
|
4590
4794
|
Usage:
|
|
4591
|
-
tapi runner setup --runner-id RUNNER --project TAPP [--port PORT] [--no-open] [--api-base-url URL]
|
|
4592
|
-
tapi runner slot set <runner-id> <slot-index> <queue-id> [--project TAPP] [--api-base-url URL]
|
|
4795
|
+
tapi runner setup --runner-id RUNNER --project TAPP [--port PORT] [--no-open] [--api-base-url URL]
|
|
4796
|
+
tapi runner slot set <runner-id> <slot-index> <queue-id> [--project TAPP] [--api-base-url URL]
|
|
4593
4797
|
|
|
4594
4798
|
Options:
|
|
4595
4799
|
--api-base-url <url> Tapi API base URL
|
|
4596
4800
|
--server <url> Alias for --api-base-url
|
|
4597
|
-
--api-key <key> Tapi API key; defaults to TAPI_API_KEY
|
|
4598
4801
|
--project <id> Tapi project id; defaults to TAPI_PROJECT_ID or .tapi/project.json
|
|
4599
4802
|
--runner-id <id> Runner id for setup; defaults to TAPI_RUNNER_ID
|
|
4600
4803
|
--port <n> Local setup UI port; defaults to 17687
|
|
@@ -4604,16 +4807,15 @@ Options:
|
|
|
4604
4807
|
function printSessionsHelp() {
|
|
4605
4808
|
console.log(`Tapi dev session commands
|
|
4606
4809
|
|
|
4607
|
-
Usage:
|
|
4608
|
-
tapi sessions [--api-base-url URL] [--
|
|
4609
|
-
tapi sessions list [--json] [--site SITE]
|
|
4610
|
-
tapi sessions open <session-id> [--api-base-url URL] [--
|
|
4611
|
-
|
|
4612
|
-
Options:
|
|
4613
|
-
--api-base-url <url> Tapi API base URL
|
|
4614
|
-
--server <url> Alias for --api-base-url
|
|
4615
|
-
--
|
|
4616
|
-
--project <id> Tapi project id; defaults to TAPI_PROJECT_ID or .tapi/project.json
|
|
4810
|
+
Usage:
|
|
4811
|
+
tapi sessions [--api-base-url URL] [--project PROJECT]
|
|
4812
|
+
tapi sessions list [--json] [--site SITE]
|
|
4813
|
+
tapi sessions open <session-id> [--api-base-url URL] [--project PROJECT]
|
|
4814
|
+
|
|
4815
|
+
Options:
|
|
4816
|
+
--api-base-url <url> Tapi API base URL
|
|
4817
|
+
--server <url> Alias for --api-base-url
|
|
4818
|
+
--project <id> Tapi project id; defaults to TAPI_PROJECT_ID or .tapi/project.json
|
|
4617
4819
|
--site <name> Limit sessions to one sitemap/site
|
|
4618
4820
|
--json Print raw grouped session JSON
|
|
4619
4821
|
--no-interactive Print the grouped list without the arrow-key picker
|
|
@@ -4622,30 +4824,28 @@ Options:
|
|
|
4622
4824
|
function printServicesHelp() {
|
|
4623
4825
|
console.log(`Tapi ServiceMap service-run commands
|
|
4624
4826
|
|
|
4625
|
-
Usage:
|
|
4626
|
-
tapi services describe <servicemap.run> [--api-base-url URL] [--
|
|
4627
|
-
tapi services sync [--api-base-url URL] [--
|
|
4628
|
-
|
|
4629
|
-
Options:
|
|
4630
|
-
--api-base-url <url> Tapi API base URL
|
|
4631
|
-
--server <url> Alias for --api-base-url
|
|
4632
|
-
--
|
|
4633
|
-
--project <id> Tapi project id; defaults to TAPI_PROJECT_ID or .tapi/project.json
|
|
4827
|
+
Usage:
|
|
4828
|
+
tapi services describe <servicemap.run> [--api-base-url URL] [--project PROJECT]
|
|
4829
|
+
tapi services sync [--api-base-url URL] [--project PROJECT]
|
|
4830
|
+
|
|
4831
|
+
Options:
|
|
4832
|
+
--api-base-url <url> Tapi API base URL
|
|
4833
|
+
--server <url> Alias for --api-base-url
|
|
4834
|
+
--project <id> Tapi project id; defaults to TAPI_PROJECT_ID or .tapi/project.json
|
|
4634
4835
|
--catalog <path> Catalog JSON output path
|
|
4635
4836
|
`);
|
|
4636
4837
|
}
|
|
4637
4838
|
function printTriggersHelp() {
|
|
4638
4839
|
console.log(`Tapi service trigger commands
|
|
4639
4840
|
|
|
4640
|
-
Usage:
|
|
4641
|
-
tapi triggers sync [--config FILE] [--api-base-url URL] [--
|
|
4642
|
-
|
|
4643
|
-
Options:
|
|
4644
|
-
--config <path> Trigger config path; defaults to tapi.config.ts/js/json in the workspace root
|
|
4645
|
-
--api-base-url <url> Tapi API base URL
|
|
4646
|
-
--server <url> Alias for --api-base-url
|
|
4647
|
-
--
|
|
4648
|
-
--project <id> Tapi project id; defaults to TAPI_PROJECT_ID or .tapi/project.json
|
|
4841
|
+
Usage:
|
|
4842
|
+
tapi triggers sync [--config FILE] [--api-base-url URL] [--project PROJECT]
|
|
4843
|
+
|
|
4844
|
+
Options:
|
|
4845
|
+
--config <path> Trigger config path; defaults to tapi.config.ts/js/json in the workspace root
|
|
4846
|
+
--api-base-url <url> Tapi API base URL
|
|
4847
|
+
--server <url> Alias for --api-base-url
|
|
4848
|
+
--project <id> Tapi project id; defaults to TAPI_PROJECT_ID or .tapi/project.json
|
|
4649
4849
|
|
|
4650
4850
|
Config:
|
|
4651
4851
|
export default {
|
|
@@ -4669,14 +4869,18 @@ Usage:
|
|
|
4669
4869
|
tapi studio open [options]
|
|
4670
4870
|
tapi studio doctor [options]
|
|
4671
4871
|
|
|
4672
|
-
Options:
|
|
4673
|
-
--channel <name> Release channel: pilot, stable, or nightly
|
|
4674
|
-
--api-base-url <url> Tapi API base URL for install authorization and protected downloads
|
|
4675
|
-
--server <url> Alias for --api-base-url
|
|
4676
|
-
--
|
|
4677
|
-
--
|
|
4678
|
-
--
|
|
4679
|
-
--
|
|
4872
|
+
Options:
|
|
4873
|
+
--channel <name> Release channel: pilot, stable, or nightly
|
|
4874
|
+
--api-base-url <url> Tapi API base URL for install authorization and protected downloads
|
|
4875
|
+
--server <url> Alias for --api-base-url
|
|
4876
|
+
--tapp <id> Open this Tapp directly
|
|
4877
|
+
--select Force the account Tapp picker
|
|
4878
|
+
--last Reuse the last selected Tapp
|
|
4879
|
+
--no-select Skip the picker and use env/workspace/default context
|
|
4880
|
+
--workspace <path> Search this directory for .tapi/project.json
|
|
4881
|
+
--no-workspace Do not load .tapi/project.json
|
|
4882
|
+
--project <id> Alias for --tapp
|
|
4883
|
+
--project-slug <slug> Optional display slug for the bound project
|
|
4680
4884
|
--install-token <tok> Preissued Studio install token (skips browser sign-in)
|
|
4681
4885
|
--manifest <url> Exact release manifest URL for doctor only
|
|
4682
4886
|
--cache-dir <path> Installer download cache directory
|