@tapi-dev/sdk 0.1.13 → 0.1.15
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/dist/cli.d.ts +1 -0
- package/dist/cli.js +177 -80
- package/package.json +1 -1
package/dist/cli.d.ts
CHANGED
|
@@ -102,6 +102,7 @@ export declare function getStudioExecutableCandidates(): string[];
|
|
|
102
102
|
export declare function validateStudioManifest(input: unknown): StudioReleaseManifest;
|
|
103
103
|
export declare function validateServiceReleaseManifest(input: unknown): ServiceReleaseManifest;
|
|
104
104
|
export declare function compareVersions(left: string, right: string): number;
|
|
105
|
+
export declare function findPortablePlaywrightBrowsersDir(exePath: string): string | undefined;
|
|
105
106
|
export declare function waitForHttpOk(url: string, timeoutMs?: number, intervalMs?: number, fetchImpl?: FetchLike): Promise<void>;
|
|
106
107
|
export declare function findPortableStudioServerExe(releaseDir: string, manifest: StudioReleaseManifest): string | undefined;
|
|
107
108
|
export {};
|
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@ import { spawn } from "node:child_process";
|
|
|
3
3
|
import { createServer } from "node:http";
|
|
4
4
|
import { createServer as createNetServer } from "node:net";
|
|
5
5
|
import { createHash, randomUUID } from "node:crypto";
|
|
6
|
-
import { createReadStream, createWriteStream, existsSync, readFileSync } from "node:fs";
|
|
6
|
+
import { createReadStream, createWriteStream, existsSync, readFileSync, readdirSync } from "node:fs";
|
|
7
7
|
import { mkdir, readFile, readdir, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
8
8
|
import { homedir } from "node:os";
|
|
9
9
|
import { basename, dirname, join, resolve } from "node:path";
|
|
@@ -34,6 +34,75 @@ class StudioInstallApprovalError extends Error {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
const sdkVersion = readSdkVersion();
|
|
37
|
+
const CLI_SPINNER_FRAMES = ["|", "/", "-", "\\"];
|
|
38
|
+
function cliSpinnerEnabled() {
|
|
39
|
+
const noSpinner = (process.env.TAPI_NO_SPINNER || "").trim().toLowerCase();
|
|
40
|
+
if (["1", "true", "yes", "on"].includes(noSpinner)) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
const spinner = (process.env.TAPI_CLI_SPINNER || "").trim().toLowerCase();
|
|
44
|
+
if (["0", "false", "no", "off"].includes(spinner)) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
return Boolean(process.stderr.isTTY);
|
|
48
|
+
}
|
|
49
|
+
class CliSpinner {
|
|
50
|
+
message;
|
|
51
|
+
frameIndex = 0;
|
|
52
|
+
lastLength = 0;
|
|
53
|
+
timer;
|
|
54
|
+
constructor(message) {
|
|
55
|
+
this.message = message;
|
|
56
|
+
}
|
|
57
|
+
start() {
|
|
58
|
+
if (this.timer) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
this.render();
|
|
62
|
+
this.timer = setInterval(() => this.render(), 90);
|
|
63
|
+
}
|
|
64
|
+
succeed() {
|
|
65
|
+
this.stop("done");
|
|
66
|
+
}
|
|
67
|
+
fail() {
|
|
68
|
+
this.stop("failed");
|
|
69
|
+
}
|
|
70
|
+
render() {
|
|
71
|
+
const frame = CLI_SPINNER_FRAMES[this.frameIndex % CLI_SPINNER_FRAMES.length];
|
|
72
|
+
this.frameIndex += 1;
|
|
73
|
+
this.write(`${frame} ${this.message}`);
|
|
74
|
+
}
|
|
75
|
+
stop(status) {
|
|
76
|
+
if (this.timer) {
|
|
77
|
+
clearInterval(this.timer);
|
|
78
|
+
this.timer = undefined;
|
|
79
|
+
}
|
|
80
|
+
this.write(`${this.message} ${status}`);
|
|
81
|
+
process.stderr.write("\n");
|
|
82
|
+
this.lastLength = 0;
|
|
83
|
+
}
|
|
84
|
+
write(text) {
|
|
85
|
+
const padding = this.lastLength > text.length ? " ".repeat(this.lastLength - text.length) : "";
|
|
86
|
+
process.stderr.write(`\r${text}${padding}`);
|
|
87
|
+
this.lastLength = text.length;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function withCliSpinner(message, work) {
|
|
91
|
+
if (!cliSpinnerEnabled()) {
|
|
92
|
+
return await work();
|
|
93
|
+
}
|
|
94
|
+
const spinner = new CliSpinner(message);
|
|
95
|
+
spinner.start();
|
|
96
|
+
try {
|
|
97
|
+
const result = await work();
|
|
98
|
+
spinner.succeed();
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
spinner.fail();
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
37
106
|
export async function runCli(argv = process.argv.slice(2)) {
|
|
38
107
|
const [command, subcommand, ...rest] = argv;
|
|
39
108
|
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
@@ -339,7 +408,7 @@ async function describeApiOperation(args) {
|
|
|
339
408
|
apiKey: options.apiKey,
|
|
340
409
|
projectId: options.projectId,
|
|
341
410
|
});
|
|
342
|
-
const description = await client.websiteApis.describe(operation);
|
|
411
|
+
const description = await withCliSpinner(`Fetching Tapi API description for ${operation}`, () => client.websiteApis.describe(operation));
|
|
343
412
|
console.log(JSON.stringify(description, null, 2));
|
|
344
413
|
return 0;
|
|
345
414
|
}
|
|
@@ -423,7 +492,7 @@ function parseApiWorkspaceOptions(args) {
|
|
|
423
492
|
async function syncApiCatalog(args) {
|
|
424
493
|
try {
|
|
425
494
|
const options = parseApiWorkspaceOptions(args);
|
|
426
|
-
const catalog = await fetchApiCatalog(options);
|
|
495
|
+
const catalog = await withCliSpinner("Fetching Tapi API catalog", () => fetchApiCatalog(options));
|
|
427
496
|
await writeJsonFile(options.catalogPath, catalog);
|
|
428
497
|
console.log(`Synced Tapi API catalog: ${options.catalogPath}`);
|
|
429
498
|
return 0;
|
|
@@ -436,7 +505,7 @@ async function syncApiCatalog(args) {
|
|
|
436
505
|
async function generateApiClient(args) {
|
|
437
506
|
try {
|
|
438
507
|
const options = parseApiWorkspaceOptions(args);
|
|
439
|
-
const catalog = await fetchApiCatalog(options);
|
|
508
|
+
const catalog = await withCliSpinner("Fetching Tapi API catalog", () => fetchApiCatalog(options));
|
|
440
509
|
await writeJsonFile(options.catalogPath, catalog);
|
|
441
510
|
await writeTextFile(options.typescriptPath, renderGeneratedApiClient(catalog));
|
|
442
511
|
console.log(`Generated Tapi API client: ${options.typescriptPath}`);
|
|
@@ -521,9 +590,11 @@ async function syncApiTriggers(args) {
|
|
|
521
590
|
apiKey: options.apiKey,
|
|
522
591
|
projectId: options.projectId,
|
|
523
592
|
});
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
593
|
+
await withCliSpinner(`Syncing ${triggers.length} Tapi API trigger(s)`, async () => {
|
|
594
|
+
for (const trigger of triggers) {
|
|
595
|
+
await client.triggers.create(trigger);
|
|
596
|
+
}
|
|
597
|
+
});
|
|
527
598
|
console.log(`Synced ${triggers.length} Tapi API trigger(s) from ${options.configPath}.`);
|
|
528
599
|
return 0;
|
|
529
600
|
}
|
|
@@ -906,7 +977,7 @@ async function runServiceCommand(action, args = []) {
|
|
|
906
977
|
return repairService(parseStudioOptions([]));
|
|
907
978
|
}
|
|
908
979
|
const command = servicePowerShell(normalized);
|
|
909
|
-
const output = await runProcessCapture("powershell.exe", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", command]);
|
|
980
|
+
const output = await withCliSpinner(`Running tapi-service ${normalized}`, () => runProcessCapture("powershell.exe", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", command]));
|
|
910
981
|
if (output.trim()) {
|
|
911
982
|
console.log(output.trim());
|
|
912
983
|
}
|
|
@@ -918,7 +989,7 @@ async function runServiceCommand(action, args = []) {
|
|
|
918
989
|
}
|
|
919
990
|
}
|
|
920
991
|
async function repairService(options) {
|
|
921
|
-
const status = await getServiceStatus();
|
|
992
|
+
const status = await withCliSpinner("Checking Tapi Service status", () => getServiceStatus());
|
|
922
993
|
if (!status.installed) {
|
|
923
994
|
return installService(options);
|
|
924
995
|
}
|
|
@@ -964,7 +1035,7 @@ async function getServiceStatus() {
|
|
|
964
1035
|
}
|
|
965
1036
|
}
|
|
966
1037
|
async function ensureServiceReadyForStudio(options) {
|
|
967
|
-
const status = await getServiceStatus();
|
|
1038
|
+
const status = await withCliSpinner("Checking Tapi Service status", () => getServiceStatus());
|
|
968
1039
|
if (!status.installed) {
|
|
969
1040
|
console.log("Tapi Service is required and is not installed. Installing it now...");
|
|
970
1041
|
const code = await installService(options);
|
|
@@ -985,13 +1056,13 @@ async function ensureServiceReadyForStudio(options) {
|
|
|
985
1056
|
}
|
|
986
1057
|
if (status.status !== "Running") {
|
|
987
1058
|
console.log("Starting Tapi Service...");
|
|
988
|
-
const output = await runProcessCapture("powershell.exe", [
|
|
1059
|
+
const output = await withCliSpinner("Starting Tapi Service", () => runProcessCapture("powershell.exe", [
|
|
989
1060
|
"-NoProfile",
|
|
990
1061
|
"-ExecutionPolicy",
|
|
991
1062
|
"Bypass",
|
|
992
1063
|
"-Command",
|
|
993
1064
|
servicePowerShell("start"),
|
|
994
|
-
]);
|
|
1065
|
+
]));
|
|
995
1066
|
if (output.trim()) {
|
|
996
1067
|
console.log(output.trim());
|
|
997
1068
|
}
|
|
@@ -1009,7 +1080,7 @@ async function fetchServiceManifestForEnsure(options) {
|
|
|
1009
1080
|
apiBaseUrl: options.apiBaseUrl,
|
|
1010
1081
|
channel: options.channel,
|
|
1011
1082
|
});
|
|
1012
|
-
installToken = await obtainStudioInstallToken(options, event);
|
|
1083
|
+
installToken = await withCliSpinner("Checking Tapi Service install approval", () => obtainStudioInstallToken(options, event));
|
|
1013
1084
|
await event.phase("auth.install_token.request.success", {
|
|
1014
1085
|
apiBaseUrl: options.apiBaseUrl,
|
|
1015
1086
|
channel: options.channel,
|
|
@@ -1019,7 +1090,7 @@ async function fetchServiceManifestForEnsure(options) {
|
|
|
1019
1090
|
apiBaseUrl: options.apiBaseUrl,
|
|
1020
1091
|
channel: options.channel,
|
|
1021
1092
|
});
|
|
1022
|
-
const manifest = await fetchProtectedServiceManifest(options.apiBaseUrl, options.channel, installToken);
|
|
1093
|
+
const manifest = await withCliSpinner(`Fetching Tapi Service ${options.channel} manifest`, () => fetchProtectedServiceManifest(options.apiBaseUrl, options.channel, installToken));
|
|
1023
1094
|
ensureCompatibleServiceManifest(manifest);
|
|
1024
1095
|
await event.finish(true, {
|
|
1025
1096
|
manifest: serviceManifestEventSummary(manifest),
|
|
@@ -1079,23 +1150,21 @@ async function installService(options) {
|
|
|
1079
1150
|
ensureWindowsHost();
|
|
1080
1151
|
let installToken = options.installToken?.trim();
|
|
1081
1152
|
if (!installToken) {
|
|
1082
|
-
console.log("Checking Tapi Service install approval...");
|
|
1083
1153
|
await event.phase("auth.install_token.request.start", {
|
|
1084
1154
|
apiBaseUrl: options.apiBaseUrl,
|
|
1085
1155
|
channel: options.channel,
|
|
1086
1156
|
});
|
|
1087
|
-
installToken = await obtainStudioInstallToken(options, event);
|
|
1157
|
+
installToken = await withCliSpinner("Checking Tapi Service install approval", () => obtainStudioInstallToken(options, event));
|
|
1088
1158
|
await event.phase("auth.install_token.request.success", {
|
|
1089
1159
|
apiBaseUrl: options.apiBaseUrl,
|
|
1090
1160
|
channel: options.channel,
|
|
1091
1161
|
});
|
|
1092
1162
|
}
|
|
1093
|
-
console.log(`Fetching Tapi Service ${options.channel} manifest...`);
|
|
1094
1163
|
await event.phase("service_manifest.fetch.start", {
|
|
1095
1164
|
apiBaseUrl: options.apiBaseUrl,
|
|
1096
1165
|
channel: options.channel,
|
|
1097
1166
|
});
|
|
1098
|
-
const manifest = await fetchProtectedServiceManifest(options.apiBaseUrl, options.channel, installToken);
|
|
1167
|
+
const manifest = await withCliSpinner(`Fetching Tapi Service ${options.channel} manifest`, () => fetchProtectedServiceManifest(options.apiBaseUrl, options.channel, installToken));
|
|
1099
1168
|
await event.phase("service_manifest.fetch.success", {
|
|
1100
1169
|
manifest: serviceManifestEventSummary(manifest),
|
|
1101
1170
|
});
|
|
@@ -1112,13 +1181,12 @@ async function installService(options) {
|
|
|
1112
1181
|
await event.phase("cache.hit", { zipPath });
|
|
1113
1182
|
}
|
|
1114
1183
|
else {
|
|
1115
|
-
console.log(`Downloading Tapi Service ${manifest.version}...`);
|
|
1116
1184
|
await event.phase("download.start", {
|
|
1117
1185
|
url: manifest.url,
|
|
1118
1186
|
destination: zipPath,
|
|
1119
1187
|
expectedSha256: manifest.sha256,
|
|
1120
1188
|
});
|
|
1121
|
-
await downloadAndVerify(manifest.url, zipPath, manifest.sha256, "Tapi Service artifact");
|
|
1189
|
+
await withCliSpinner(`Downloading Tapi Service ${manifest.version}`, () => downloadAndVerify(manifest.url, zipPath, manifest.sha256, "Tapi Service artifact"));
|
|
1122
1190
|
await event.phase("download.verified", {
|
|
1123
1191
|
zipPath,
|
|
1124
1192
|
expectedSha256: manifest.sha256,
|
|
@@ -1133,7 +1201,7 @@ async function installService(options) {
|
|
|
1133
1201
|
return 0;
|
|
1134
1202
|
}
|
|
1135
1203
|
const releaseDir = join(releasesDir, safePathSegment(manifest.version));
|
|
1136
|
-
await extractZip(zipPath, releaseDir);
|
|
1204
|
+
await withCliSpinner(`Extracting Tapi Service ${manifest.version}`, () => extractZip(zipPath, releaseDir));
|
|
1137
1205
|
const installer = join(releaseDir, manifest.installer || "install_tapi_service.ps1");
|
|
1138
1206
|
const hostExe = join(releaseDir, manifest.serviceHostExecutable || "tapi-service-host.exe");
|
|
1139
1207
|
if (!existsSync(installer)) {
|
|
@@ -1144,7 +1212,6 @@ async function installService(options) {
|
|
|
1144
1212
|
}
|
|
1145
1213
|
const installLog = serviceInstallLogPaths(manifest.version);
|
|
1146
1214
|
await mkdir(dirname(installLog.logPath), { recursive: true });
|
|
1147
|
-
console.log(`Installing Tapi Service ${manifest.version}...`);
|
|
1148
1215
|
await event.phase("service_installer.start", {
|
|
1149
1216
|
installer,
|
|
1150
1217
|
hostExe,
|
|
@@ -1153,7 +1220,7 @@ async function installService(options) {
|
|
|
1153
1220
|
resultPath: installLog.resultPath,
|
|
1154
1221
|
});
|
|
1155
1222
|
try {
|
|
1156
|
-
await runProcess("powershell.exe", [
|
|
1223
|
+
await withCliSpinner(`Installing Tapi Service ${manifest.version}`, () => runProcess("powershell.exe", [
|
|
1157
1224
|
"-NoProfile",
|
|
1158
1225
|
"-ExecutionPolicy",
|
|
1159
1226
|
"Bypass",
|
|
@@ -1167,7 +1234,7 @@ async function installService(options) {
|
|
|
1167
1234
|
installLog.logPath,
|
|
1168
1235
|
"-ResultPath",
|
|
1169
1236
|
installLog.resultPath,
|
|
1170
|
-
]);
|
|
1237
|
+
]));
|
|
1171
1238
|
}
|
|
1172
1239
|
catch (error) {
|
|
1173
1240
|
throw new Error(await formatServiceInstallFailure(error, installLog), { cause: error });
|
|
@@ -1196,42 +1263,45 @@ async function publishLocalApis(args) {
|
|
|
1196
1263
|
if (apis.length === 0) {
|
|
1197
1264
|
throw new Error("No local generated APIs found under .tapi/apis.");
|
|
1198
1265
|
}
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
const
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
}
|
|
1205
|
-
await putJson(`${options.apiBaseUrl.replace(/\/+$/, "")}/api/v1/sitemaps/${encodeURIComponent(site)}?project=${encodeURIComponent(options.projectId)}`, { site_map: sitemap.siteMap }, options.apiKey, options.projectId);
|
|
1206
|
-
sitemapCount += 1;
|
|
1207
|
-
}
|
|
1208
|
-
let contracts = 0;
|
|
1209
|
-
let requests = 0;
|
|
1210
|
-
for (const api of apis) {
|
|
1211
|
-
const apiId = String(api.id || api.name || "").trim();
|
|
1212
|
-
if (!apiId) {
|
|
1213
|
-
continue;
|
|
1214
|
-
}
|
|
1215
|
-
const site = String(api.site || "").trim();
|
|
1216
|
-
await putJson(`${options.apiBaseUrl.replace(/\/+$/, "")}/api/v1/generated-apis/contracts/${encodeURIComponent(apiId)}?project=${encodeURIComponent(options.projectId)}`, { generated_api: api }, options.apiKey, options.projectId);
|
|
1217
|
-
contracts += 1;
|
|
1218
|
-
const apiRequests = api.requests && typeof api.requests === "object" ? Object.values(api.requests) : [];
|
|
1219
|
-
for (const request of apiRequests) {
|
|
1220
|
-
if (!isRecord(request)) {
|
|
1266
|
+
const { sitemapCount, contracts, requests, releaseVersion } = await withCliSpinner("Publishing local Tapi APIs", async () => {
|
|
1267
|
+
let sitemapCount = 0;
|
|
1268
|
+
for (const sitemap of sitemaps) {
|
|
1269
|
+
const site = String(sitemap.site || "").trim();
|
|
1270
|
+
if (!site || !isRecord(sitemap.siteMap)) {
|
|
1221
1271
|
continue;
|
|
1222
1272
|
}
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1273
|
+
await putJson(`${options.apiBaseUrl.replace(/\/+$/, "")}/api/v1/sitemaps/${encodeURIComponent(site)}?project=${encodeURIComponent(options.projectId)}`, { site_map: sitemap.siteMap }, options.apiKey, options.projectId);
|
|
1274
|
+
sitemapCount += 1;
|
|
1275
|
+
}
|
|
1276
|
+
let contracts = 0;
|
|
1277
|
+
let requests = 0;
|
|
1278
|
+
for (const api of apis) {
|
|
1279
|
+
const apiId = String(api.id || api.name || "").trim();
|
|
1280
|
+
if (!apiId) {
|
|
1226
1281
|
continue;
|
|
1227
1282
|
}
|
|
1228
|
-
|
|
1229
|
-
|
|
1283
|
+
const site = String(api.site || "").trim();
|
|
1284
|
+
await putJson(`${options.apiBaseUrl.replace(/\/+$/, "")}/api/v1/generated-apis/contracts/${encodeURIComponent(apiId)}?project=${encodeURIComponent(options.projectId)}`, { generated_api: api }, options.apiKey, options.projectId);
|
|
1285
|
+
contracts += 1;
|
|
1286
|
+
const apiRequests = api.requests && typeof api.requests === "object" ? Object.values(api.requests) : [];
|
|
1287
|
+
for (const request of apiRequests) {
|
|
1288
|
+
if (!isRecord(request)) {
|
|
1289
|
+
continue;
|
|
1290
|
+
}
|
|
1291
|
+
const requestId = String(request.id || request.key || "").trim();
|
|
1292
|
+
const status = String(request.status || "").trim();
|
|
1293
|
+
if (!requestId || !["ready", "published"].includes(status)) {
|
|
1294
|
+
continue;
|
|
1295
|
+
}
|
|
1296
|
+
await postJson(`${options.apiBaseUrl.replace(/\/+$/, "")}/api/v1/generated-apis/contracts/${encodeURIComponent(apiId)}/requests/${encodeURIComponent(requestId)}/publish?project=${encodeURIComponent(options.projectId)}`, { site }, options.apiKey, options.projectId);
|
|
1297
|
+
requests += 1;
|
|
1298
|
+
}
|
|
1230
1299
|
}
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1300
|
+
const releaseResponse = await postJson(`${options.apiBaseUrl.replace(/\/+$/, "")}/api/v1/generated-apis/releases?project=${encodeURIComponent(options.projectId)}`, { environment: "production" }, options.apiKey, options.projectId);
|
|
1301
|
+
const release = isRecord(releaseResponse) && isRecord(releaseResponse.release) ? releaseResponse.release : {};
|
|
1302
|
+
const releaseVersion = typeof release.version === "string" ? release.version : "unknown";
|
|
1303
|
+
return { sitemapCount, contracts, requests, releaseVersion };
|
|
1304
|
+
});
|
|
1235
1305
|
console.log(`Published ${requests} API request(s) from ${contracts} contract(s), synced ${sitemapCount} sitemap(s), activated release ${releaseVersion}.`);
|
|
1236
1306
|
return 0;
|
|
1237
1307
|
}
|
|
@@ -1654,23 +1724,21 @@ async function installStudio(options) {
|
|
|
1654
1724
|
});
|
|
1655
1725
|
}
|
|
1656
1726
|
else {
|
|
1657
|
-
console.log("Checking Studio install approval...");
|
|
1658
1727
|
await event.phase("auth.install_token.request.start", {
|
|
1659
1728
|
apiBaseUrl: options.apiBaseUrl,
|
|
1660
1729
|
channel: options.channel,
|
|
1661
1730
|
});
|
|
1662
|
-
installToken = await obtainStudioInstallToken(options, event);
|
|
1731
|
+
installToken = await withCliSpinner("Checking Studio install approval", () => obtainStudioInstallToken(options, event));
|
|
1663
1732
|
await event.phase("auth.install_token.request.success", {
|
|
1664
1733
|
apiBaseUrl: options.apiBaseUrl,
|
|
1665
1734
|
channel: options.channel,
|
|
1666
1735
|
});
|
|
1667
1736
|
}
|
|
1668
|
-
console.log(`Fetching Tapi Studio ${options.channel} manifest...`);
|
|
1669
1737
|
await event.phase("manifest.fetch.start", {
|
|
1670
1738
|
apiBaseUrl: options.apiBaseUrl,
|
|
1671
1739
|
channel: options.channel,
|
|
1672
1740
|
});
|
|
1673
|
-
const manifest = await fetchProtectedStudioManifest(options.apiBaseUrl, options.channel, installToken);
|
|
1741
|
+
const manifest = await withCliSpinner(`Fetching Tapi Studio ${options.channel} manifest`, () => fetchProtectedStudioManifest(options.apiBaseUrl, options.channel, installToken));
|
|
1674
1742
|
await event.phase("manifest.fetch.success", {
|
|
1675
1743
|
manifest: manifestEventSummary(manifest),
|
|
1676
1744
|
});
|
|
@@ -1702,13 +1770,12 @@ async function installStudio(options) {
|
|
|
1702
1770
|
}
|
|
1703
1771
|
else {
|
|
1704
1772
|
await event.phase("cache.miss", { installerPath });
|
|
1705
|
-
console.log(`Downloading Tapi Studio ${manifest.version}...`);
|
|
1706
1773
|
await event.phase("download.start", {
|
|
1707
1774
|
url: manifest.url,
|
|
1708
1775
|
destination: installerPath,
|
|
1709
1776
|
expectedSha256: manifest.sha256,
|
|
1710
1777
|
});
|
|
1711
|
-
await downloadAndVerify(manifest.url, installerPath, manifest.sha256);
|
|
1778
|
+
await withCliSpinner(`Downloading Tapi Studio ${manifest.version}`, () => downloadAndVerify(manifest.url, installerPath, manifest.sha256));
|
|
1712
1779
|
await event.phase("download.verified", {
|
|
1713
1780
|
installerPath,
|
|
1714
1781
|
expectedSha256: manifest.sha256,
|
|
@@ -1723,12 +1790,11 @@ async function installStudio(options) {
|
|
|
1723
1790
|
return 0;
|
|
1724
1791
|
}
|
|
1725
1792
|
const installerArgs = options.silent ? ["/S"] : [];
|
|
1726
|
-
console.log(`Starting Tapi Studio installer: ${installerPath}`);
|
|
1727
1793
|
await event.phase("installer.start", {
|
|
1728
1794
|
installerPath,
|
|
1729
1795
|
args: installerArgs,
|
|
1730
1796
|
});
|
|
1731
|
-
await runProcess(installerPath, installerArgs);
|
|
1797
|
+
await withCliSpinner("Starting Tapi Studio installer", () => runProcess(installerPath, installerArgs));
|
|
1732
1798
|
console.log("Tapi Studio installer finished.");
|
|
1733
1799
|
await event.finish(true, {
|
|
1734
1800
|
installerPath,
|
|
@@ -1795,7 +1861,7 @@ async function openStudio(options) {
|
|
|
1795
1861
|
if (!options.exePath) {
|
|
1796
1862
|
const portable = await ensurePortableStudioServer(options);
|
|
1797
1863
|
if (portable) {
|
|
1798
|
-
await launchPortableStudioServer(portable.exePath, options);
|
|
1864
|
+
await launchPortableStudioServer(portable.exePath, options, portable.manifest);
|
|
1799
1865
|
return 0;
|
|
1800
1866
|
}
|
|
1801
1867
|
}
|
|
@@ -1841,7 +1907,7 @@ async function ensurePortableStudioServer(options) {
|
|
|
1841
1907
|
apiBaseUrl: options.apiBaseUrl,
|
|
1842
1908
|
channel: options.channel,
|
|
1843
1909
|
});
|
|
1844
|
-
installToken = await obtainStudioInstallToken(options, event);
|
|
1910
|
+
installToken = await withCliSpinner("Checking Studio install approval", () => obtainStudioInstallToken(options, event));
|
|
1845
1911
|
await event.phase("auth.install_token.request.success", {
|
|
1846
1912
|
apiBaseUrl: options.apiBaseUrl,
|
|
1847
1913
|
channel: options.channel,
|
|
@@ -1851,7 +1917,7 @@ async function ensurePortableStudioServer(options) {
|
|
|
1851
1917
|
apiBaseUrl: options.apiBaseUrl,
|
|
1852
1918
|
channel: options.channel,
|
|
1853
1919
|
});
|
|
1854
|
-
const manifest = await fetchProtectedStudioManifest(options.apiBaseUrl, options.channel, installToken);
|
|
1920
|
+
const manifest = await withCliSpinner(`Fetching Tapi Studio ${options.channel} manifest`, () => fetchProtectedStudioManifest(options.apiBaseUrl, options.channel, installToken));
|
|
1855
1921
|
ensureCompatibleManifest(manifest);
|
|
1856
1922
|
await event.phase("manifest.fetch.success", {
|
|
1857
1923
|
manifest: manifestEventSummary(manifest),
|
|
@@ -1893,13 +1959,12 @@ async function installPortableStudioServer(options, manifest, event) {
|
|
|
1893
1959
|
await event.phase("cache.hit", { artifactPath });
|
|
1894
1960
|
}
|
|
1895
1961
|
else {
|
|
1896
|
-
console.log(`Downloading Tapi Studio server ${manifest.version}...`);
|
|
1897
1962
|
await event.phase("download.start", {
|
|
1898
1963
|
url: manifest.url,
|
|
1899
1964
|
destination: artifactPath,
|
|
1900
1965
|
expectedSha256: manifest.sha256,
|
|
1901
1966
|
});
|
|
1902
|
-
await downloadAndVerify(manifest.url, artifactPath, manifest.sha256, "Tapi Studio server artifact");
|
|
1967
|
+
await withCliSpinner(`Downloading Tapi Studio server ${manifest.version}`, () => downloadAndVerify(manifest.url, artifactPath, manifest.sha256, "Tapi Studio server artifact"));
|
|
1903
1968
|
await event.phase("download.verified", {
|
|
1904
1969
|
artifactPath,
|
|
1905
1970
|
expectedSha256: manifest.sha256,
|
|
@@ -1910,7 +1975,7 @@ async function installPortableStudioServer(options, manifest, event) {
|
|
|
1910
1975
|
return artifactPath;
|
|
1911
1976
|
}
|
|
1912
1977
|
const releaseDir = portableStudioServerReleaseDir(manifest);
|
|
1913
|
-
await extractZip(artifactPath, releaseDir);
|
|
1978
|
+
await withCliSpinner(`Extracting Tapi Studio server ${manifest.version}`, () => extractZip(artifactPath, releaseDir));
|
|
1914
1979
|
const exePath = findPortableStudioServerExe(releaseDir, manifest);
|
|
1915
1980
|
if (!exePath) {
|
|
1916
1981
|
throw new Error(`Tapi Studio server executable was not found after extraction: ${releaseDir}`);
|
|
@@ -1918,7 +1983,7 @@ async function installPortableStudioServer(options, manifest, event) {
|
|
|
1918
1983
|
console.log(`Installed Tapi Studio server ${manifest.version}: ${releaseDir}`);
|
|
1919
1984
|
return exePath;
|
|
1920
1985
|
}
|
|
1921
|
-
async function launchPortableStudioServer(exePath, options) {
|
|
1986
|
+
async function launchPortableStudioServer(exePath, options, manifest) {
|
|
1922
1987
|
const host = "127.0.0.1";
|
|
1923
1988
|
const port = await chooseStudioPort();
|
|
1924
1989
|
const env = buildStudioLaunchEnv(options);
|
|
@@ -1930,17 +1995,49 @@ async function launchPortableStudioServer(exePath, options) {
|
|
|
1930
1995
|
env.TAPI_STUDIO_HOST = host;
|
|
1931
1996
|
env.TAPI_STUDIO_PORT = String(port);
|
|
1932
1997
|
env.TAPI_STUDIO_SERVER_PORT = String(port);
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1998
|
+
env.TAPI_STUDIO_SERVER_VERSION = manifest.version;
|
|
1999
|
+
env.TAPI_STUDIO_SERVER_COMMIT = manifest.commit || "";
|
|
2000
|
+
env.TAPI_STUDIO_SERVER_COMMIT_SHORT = manifest.commitShort || manifest.version;
|
|
2001
|
+
const playwrightBrowsersPath = findPortablePlaywrightBrowsersDir(exePath);
|
|
2002
|
+
if (playwrightBrowsersPath) {
|
|
2003
|
+
env.PLAYWRIGHT_BROWSERS_PATH = playwrightBrowsersPath;
|
|
2004
|
+
}
|
|
1940
2005
|
const url = `http://${host}:${port}`;
|
|
1941
|
-
await
|
|
2006
|
+
await withCliSpinner("Starting Tapi Studio", async () => {
|
|
2007
|
+
const child = spawn(exePath, [], {
|
|
2008
|
+
detached: true,
|
|
2009
|
+
env,
|
|
2010
|
+
stdio: "ignore",
|
|
2011
|
+
windowsHide: true,
|
|
2012
|
+
});
|
|
2013
|
+
child.unref();
|
|
2014
|
+
await waitForHttpOk(`${url}/healthz`, PORTABLE_STUDIO_SERVER_READY_TIMEOUT_MS, PORTABLE_STUDIO_SERVER_READY_INTERVAL_MS);
|
|
2015
|
+
});
|
|
1942
2016
|
openBrowser(url);
|
|
1943
|
-
console.log(`Opened Tapi Studio: ${url}`);
|
|
2017
|
+
console.log(`Opened Tapi Studio ${manifest.version}: ${url}`);
|
|
2018
|
+
}
|
|
2019
|
+
export function findPortablePlaywrightBrowsersDir(exePath) {
|
|
2020
|
+
const exeDir = dirname(exePath);
|
|
2021
|
+
const releaseDir = basename(exeDir).toLowerCase() === "tapi-studio-server"
|
|
2022
|
+
? dirname(exeDir)
|
|
2023
|
+
: exeDir;
|
|
2024
|
+
const candidates = [
|
|
2025
|
+
join(releaseDir, "pw"),
|
|
2026
|
+
join(releaseDir, "playwright-browsers"),
|
|
2027
|
+
join(releaseDir, "sidecars", "pw"),
|
|
2028
|
+
join(exeDir, "pw"),
|
|
2029
|
+
join(exeDir, "playwright-browsers"),
|
|
2030
|
+
];
|
|
2031
|
+
return candidates.find(isPlaywrightBrowsersDir);
|
|
2032
|
+
}
|
|
2033
|
+
function isPlaywrightBrowsersDir(path) {
|
|
2034
|
+
try {
|
|
2035
|
+
return existsSync(path)
|
|
2036
|
+
&& readdirSync(path, { withFileTypes: true }).some((entry) => entry.isDirectory() && entry.name.startsWith("chromium_headless_shell-"));
|
|
2037
|
+
}
|
|
2038
|
+
catch {
|
|
2039
|
+
return false;
|
|
2040
|
+
}
|
|
1944
2041
|
}
|
|
1945
2042
|
export async function waitForHttpOk(url, timeoutMs = PORTABLE_STUDIO_SERVER_READY_TIMEOUT_MS, intervalMs = PORTABLE_STUDIO_SERVER_READY_INTERVAL_MS, fetchImpl = fetch) {
|
|
1946
2043
|
const startedAt = performance.now();
|
|
@@ -1998,7 +2095,7 @@ async function runDoctor(options) {
|
|
|
1998
2095
|
const exePath = options.exePath ?? getStudioExecutableCandidates().find((candidate) => existsSync(candidate));
|
|
1999
2096
|
console.log(`Studio executable: ${exePath && existsSync(exePath) ? exePath : "not found"}`);
|
|
2000
2097
|
try {
|
|
2001
|
-
const manifest = await fetchStudioManifest(options.manifestUrl);
|
|
2098
|
+
const manifest = await withCliSpinner("Fetching latest Tapi Studio manifest", () => fetchStudioManifest(options.manifestUrl));
|
|
2002
2099
|
ensureCompatibleManifest(manifest);
|
|
2003
2100
|
console.log(`Latest Studio: ${manifest.version} (${manifest.platform})`);
|
|
2004
2101
|
}
|