@tapi-dev/sdk 0.1.30 → 0.1.32
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 +3 -1
- package/dist/cli.js +89 -29
- package/package.json +1 -1
package/dist/cli.d.ts
CHANGED
|
@@ -60,6 +60,7 @@ export interface StudioCliOptions {
|
|
|
60
60
|
silent: boolean;
|
|
61
61
|
exePath?: string;
|
|
62
62
|
installToken?: string;
|
|
63
|
+
installTokenSource?: "option" | "env" | "minted";
|
|
63
64
|
workspace?: TapiWorkspace;
|
|
64
65
|
workspaceRoot?: string;
|
|
65
66
|
projectId?: string;
|
|
@@ -84,6 +85,7 @@ export declare function trySelectDevSessionInOpenStudio(session: TapiDevSession,
|
|
|
84
85
|
baseUrl?: string;
|
|
85
86
|
reason?: string;
|
|
86
87
|
}>;
|
|
88
|
+
export declare function servicePowerShell(action: string): string;
|
|
87
89
|
export declare function installedServiceVersionFromPathName(pathName: string): string | undefined;
|
|
88
90
|
export declare function serviceNeedsInstall(status: ServiceStatus, manifest: ServiceReleaseManifest): boolean;
|
|
89
91
|
export declare function getDefaultStudioCacheDir(): string;
|
|
@@ -113,7 +115,7 @@ export declare function getStudioExecutableCandidates(): string[];
|
|
|
113
115
|
export declare function validateStudioManifest(input: unknown): StudioReleaseManifest;
|
|
114
116
|
export declare function validateServiceReleaseManifest(input: unknown): ServiceReleaseManifest;
|
|
115
117
|
export declare function compareVersions(left: string, right: string): number;
|
|
116
|
-
export declare function memoizeStudioInstallToken(options: StudioCliOptions, installToken?: string): string;
|
|
118
|
+
export declare function memoizeStudioInstallToken(options: StudioCliOptions, installToken?: string, source?: StudioCliOptions["installTokenSource"]): string;
|
|
117
119
|
export declare function closeExistingPortableStudioServersForFreshLaunch(stopProcesses?: () => Promise<number>): Promise<{
|
|
118
120
|
stopped: number;
|
|
119
121
|
recordsRemoved: number;
|
package/dist/cli.js
CHANGED
|
@@ -28,6 +28,8 @@ const DOWNLOAD_PROGRESS_LOG_BYTES = 50 * 1024 * 1024;
|
|
|
28
28
|
const MAX_WIDE_EVENT_PROCESS_ROOTS = 5;
|
|
29
29
|
const MAX_NONCURRENT_MANAGED_DOWNLOADS = 0;
|
|
30
30
|
const MAX_NONCURRENT_MANAGED_RELEASES = 0;
|
|
31
|
+
const WINDOWS_SERVICE_STATUS_TIMEOUT_MS = 8_000;
|
|
32
|
+
const WINDOWS_SERVICE_COMMAND_TIMEOUT_MS = 30_000;
|
|
31
33
|
const MANAGED_RELEASE_MARKER = ".tapi-managed-release";
|
|
32
34
|
const MIN_EXTRACTION_REQUIRED_BYTES = 1024 * 1024 * 1024;
|
|
33
35
|
const EXTRACTION_REQUIRED_MULTIPLIER = 3;
|
|
@@ -344,6 +346,8 @@ export function parseStudioOptions(args) {
|
|
|
344
346
|
: `${downloadsBaseUrl.replace(/\/+$/, "")}/studio/channels/${channel}/latest.json`;
|
|
345
347
|
const projectId = raw.projectId ?? envString("TAPI_PROJECT_ID") ?? workspace?.projectId;
|
|
346
348
|
const projectSlug = raw.projectSlug ?? envString("TAPI_PROJECT_SLUG") ?? workspace?.projectSlug;
|
|
349
|
+
const envInstallToken = envString("TAPI_STUDIO_INSTALL_TOKEN");
|
|
350
|
+
const installToken = raw.installToken ?? envInstallToken;
|
|
347
351
|
return {
|
|
348
352
|
channel,
|
|
349
353
|
apiBaseUrl,
|
|
@@ -354,7 +358,8 @@ export function parseStudioOptions(args) {
|
|
|
354
358
|
downloadOnly: raw.downloadOnly ?? false,
|
|
355
359
|
silent: raw.silent ?? false,
|
|
356
360
|
exePath: raw.exePath ?? envString("TAPI_STUDIO_EXE"),
|
|
357
|
-
installToken
|
|
361
|
+
installToken,
|
|
362
|
+
installTokenSource: raw.installToken ? "option" : installToken ? "env" : undefined,
|
|
358
363
|
workspace,
|
|
359
364
|
workspaceRoot: workspace?.root,
|
|
360
365
|
projectId,
|
|
@@ -1348,7 +1353,7 @@ async function runServiceCommand(action, args = []) {
|
|
|
1348
1353
|
return repairService(parseStudioOptions([]));
|
|
1349
1354
|
}
|
|
1350
1355
|
const command = servicePowerShell(normalized);
|
|
1351
|
-
const output = await withCliSpinner(`Running tapi-service ${normalized}`, () => runProcessCapture("powershell.exe", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", command]));
|
|
1356
|
+
const output = await withCliSpinner(`Running tapi-service ${normalized}`, () => runProcessCapture("powershell.exe", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", command], { timeoutMs: WINDOWS_SERVICE_COMMAND_TIMEOUT_MS }));
|
|
1352
1357
|
if (output.trim()) {
|
|
1353
1358
|
console.log(output.trim());
|
|
1354
1359
|
}
|
|
@@ -1366,9 +1371,9 @@ async function repairService(options) {
|
|
|
1366
1371
|
}
|
|
1367
1372
|
return runServiceCommand("restart");
|
|
1368
1373
|
}
|
|
1369
|
-
function servicePowerShell(action) {
|
|
1374
|
+
export function servicePowerShell(action) {
|
|
1370
1375
|
const serviceName = "tapi-service";
|
|
1371
|
-
const status = `$svc = Get-Service -Name '${serviceName}' -ErrorAction SilentlyContinue; if ($null -eq $svc) { [pscustomobject]@{ installed = $false; name = '${serviceName}'; status = 'not_installed'; pathName = '' } | ConvertTo-Json -Compress; exit 0 }; $
|
|
1376
|
+
const status = `$svc = Get-Service -Name '${serviceName}' -ErrorAction SilentlyContinue; if ($null -eq $svc) { [pscustomobject]@{ installed = $false; name = '${serviceName}'; status = 'not_installed'; pathName = '' } | ConvertTo-Json -Compress; exit 0 }; $pathName = ''; try { $imagePath = (Get-ItemProperty -LiteralPath 'HKLM:\\SYSTEM\\CurrentControlSet\\Services\\${serviceName}' -Name ImagePath -ErrorAction SilentlyContinue).ImagePath; if ($null -ne $imagePath) { $pathName = [string]$imagePath } } catch {}; [pscustomobject]@{ installed = $true; name = $svc.Name; status = $svc.Status.ToString(); pathName = $pathName } | ConvertTo-Json -Compress`;
|
|
1372
1377
|
if (action === "status") {
|
|
1373
1378
|
return status;
|
|
1374
1379
|
}
|
|
@@ -1390,7 +1395,7 @@ async function getServiceStatus() {
|
|
|
1390
1395
|
"Bypass",
|
|
1391
1396
|
"-Command",
|
|
1392
1397
|
servicePowerShell("status"),
|
|
1393
|
-
]);
|
|
1398
|
+
], { timeoutMs: WINDOWS_SERVICE_STATUS_TIMEOUT_MS });
|
|
1394
1399
|
try {
|
|
1395
1400
|
const payload = JSON.parse(output.trim());
|
|
1396
1401
|
return {
|
|
@@ -1433,7 +1438,7 @@ async function ensureServiceReadyForStudio(options) {
|
|
|
1433
1438
|
"Bypass",
|
|
1434
1439
|
"-Command",
|
|
1435
1440
|
servicePowerShell("start"),
|
|
1436
|
-
]));
|
|
1441
|
+
], { timeoutMs: WINDOWS_SERVICE_COMMAND_TIMEOUT_MS }));
|
|
1437
1442
|
if (output.trim()) {
|
|
1438
1443
|
console.log(output.trim());
|
|
1439
1444
|
}
|
|
@@ -1967,12 +1972,7 @@ async function installStudio(options) {
|
|
|
1967
1972
|
throw new Error("Direct Studio manifest overrides are no longer supported for install. "
|
|
1968
1973
|
+ "Use --channel with the protected install flow instead.");
|
|
1969
1974
|
}
|
|
1970
|
-
const
|
|
1971
|
-
await event.phase("manifest.fetch.start", {
|
|
1972
|
-
apiBaseUrl: options.apiBaseUrl,
|
|
1973
|
-
channel: options.channel,
|
|
1974
|
-
});
|
|
1975
|
-
const manifest = await withCliSpinner(`Fetching Tapi Studio ${options.channel} manifest`, () => fetchProtectedStudioManifest(options.apiBaseUrl, options.channel, installToken));
|
|
1975
|
+
const manifest = await fetchStudioManifestForRun(options, event, "Checking Studio install authorization");
|
|
1976
1976
|
await event.phase("manifest.fetch.success", {
|
|
1977
1977
|
manifest: manifestEventSummary(manifest),
|
|
1978
1978
|
});
|
|
@@ -2086,7 +2086,7 @@ async function obtainStudioInstallToken(options, event) {
|
|
|
2086
2086
|
const issued = await requestStudioInstallToken(options.apiBaseUrl, options.channel, firebaseCreds.idToken);
|
|
2087
2087
|
return issued.installToken;
|
|
2088
2088
|
}
|
|
2089
|
-
export function memoizeStudioInstallToken(options, installToken) {
|
|
2089
|
+
export function memoizeStudioInstallToken(options, installToken, source = "minted") {
|
|
2090
2090
|
const existing = options.installToken?.trim();
|
|
2091
2091
|
if (existing) {
|
|
2092
2092
|
options.installToken = existing;
|
|
@@ -2095,9 +2095,14 @@ export function memoizeStudioInstallToken(options, installToken) {
|
|
|
2095
2095
|
const normalized = installToken?.trim() ?? "";
|
|
2096
2096
|
if (normalized) {
|
|
2097
2097
|
options.installToken = normalized;
|
|
2098
|
+
options.installTokenSource = source;
|
|
2098
2099
|
}
|
|
2099
2100
|
return normalized;
|
|
2100
2101
|
}
|
|
2102
|
+
function clearStudioInstallTokenForRun(options) {
|
|
2103
|
+
options.installToken = undefined;
|
|
2104
|
+
options.installTokenSource = undefined;
|
|
2105
|
+
}
|
|
2101
2106
|
async function ensureStudioInstallTokenForRun(options, event, message) {
|
|
2102
2107
|
const existing = memoizeStudioInstallToken(options);
|
|
2103
2108
|
if (existing) {
|
|
@@ -2111,7 +2116,7 @@ async function ensureStudioInstallTokenForRun(options, event, message) {
|
|
|
2111
2116
|
apiBaseUrl: options.apiBaseUrl,
|
|
2112
2117
|
channel: options.channel,
|
|
2113
2118
|
});
|
|
2114
|
-
const installToken = memoizeStudioInstallToken(options, await withCliSpinner(message, () => obtainStudioInstallToken(options, event)));
|
|
2119
|
+
const installToken = memoizeStudioInstallToken(options, await withCliSpinner(message, () => obtainStudioInstallToken(options, event)), "minted");
|
|
2115
2120
|
await event.phase("auth.install_token.request.success", {
|
|
2116
2121
|
apiBaseUrl: options.apiBaseUrl,
|
|
2117
2122
|
channel: options.channel,
|
|
@@ -2119,6 +2124,39 @@ async function ensureStudioInstallTokenForRun(options, event, message) {
|
|
|
2119
2124
|
});
|
|
2120
2125
|
return installToken;
|
|
2121
2126
|
}
|
|
2127
|
+
async function fetchStudioManifestForRun(options, event, authMessage) {
|
|
2128
|
+
const installToken = await ensureStudioInstallTokenForRun(options, event, authMessage);
|
|
2129
|
+
await event.phase("manifest.fetch.start", {
|
|
2130
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
2131
|
+
channel: options.channel,
|
|
2132
|
+
});
|
|
2133
|
+
try {
|
|
2134
|
+
return await withCliSpinner(`Fetching Tapi Studio ${options.channel} manifest`, () => fetchProtectedStudioManifest(options.apiBaseUrl, options.channel, installToken));
|
|
2135
|
+
}
|
|
2136
|
+
catch (error) {
|
|
2137
|
+
if (!isExpiredStudioInstallTokenError(error) || options.installTokenSource === "option") {
|
|
2138
|
+
throw error;
|
|
2139
|
+
}
|
|
2140
|
+
await event.phase("auth.install_token.expired", {
|
|
2141
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
2142
|
+
channel: options.channel,
|
|
2143
|
+
tokenSource: options.installTokenSource ?? "unknown",
|
|
2144
|
+
error: errorDetails(error),
|
|
2145
|
+
});
|
|
2146
|
+
clearStudioInstallTokenForRun(options);
|
|
2147
|
+
const refreshedToken = await ensureStudioInstallTokenForRun(options, event, "Refreshing Studio install authorization");
|
|
2148
|
+
await event.phase("manifest.fetch.retry.start", {
|
|
2149
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
2150
|
+
channel: options.channel,
|
|
2151
|
+
});
|
|
2152
|
+
return await withCliSpinner(`Retrying Tapi Studio ${options.channel} manifest`, () => fetchProtectedStudioManifest(options.apiBaseUrl, options.channel, refreshedToken));
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
function isExpiredStudioInstallTokenError(error) {
|
|
2156
|
+
const message = formatError(error);
|
|
2157
|
+
return (message.includes("Failed to fetch protected Studio manifest: HTTP 401")
|
|
2158
|
+
&& message.includes("Studio install token has expired"));
|
|
2159
|
+
}
|
|
2122
2160
|
async function openStudio(options) {
|
|
2123
2161
|
ensureWindowsHost();
|
|
2124
2162
|
if (options.downloadOnly) {
|
|
@@ -2180,12 +2218,7 @@ async function ensurePortableStudioServer(options) {
|
|
|
2180
2218
|
options: installEventOptions(options),
|
|
2181
2219
|
});
|
|
2182
2220
|
try {
|
|
2183
|
-
const
|
|
2184
|
-
await event.phase("manifest.fetch.start", {
|
|
2185
|
-
apiBaseUrl: options.apiBaseUrl,
|
|
2186
|
-
channel: options.channel,
|
|
2187
|
-
});
|
|
2188
|
-
const manifest = await withCliSpinner(`Fetching Tapi Studio ${options.channel} manifest`, () => fetchProtectedStudioManifest(options.apiBaseUrl, options.channel, installToken));
|
|
2221
|
+
const manifest = await fetchStudioManifestForRun(options, event, "Checking Studio install authorization");
|
|
2189
2222
|
ensureCompatibleManifest(manifest);
|
|
2190
2223
|
await event.phase("manifest.fetch.success", {
|
|
2191
2224
|
manifest: manifestEventSummary(manifest),
|
|
@@ -3055,7 +3088,7 @@ async function runProcess(command, args) {
|
|
|
3055
3088
|
});
|
|
3056
3089
|
});
|
|
3057
3090
|
}
|
|
3058
|
-
async function runProcessCapture(command, args) {
|
|
3091
|
+
async function runProcessCapture(command, args, options = {}) {
|
|
3059
3092
|
return await new Promise((resolvePromise, rejectPromise) => {
|
|
3060
3093
|
const child = spawn(command, args, {
|
|
3061
3094
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -3063,21 +3096,47 @@ async function runProcessCapture(command, args) {
|
|
|
3063
3096
|
});
|
|
3064
3097
|
let stdout = "";
|
|
3065
3098
|
let stderr = "";
|
|
3099
|
+
let timedOut = false;
|
|
3100
|
+
let settled = false;
|
|
3101
|
+
const timeoutMs = Math.max(0, options.timeoutMs ?? 0);
|
|
3102
|
+
const timeout = timeoutMs > 0
|
|
3103
|
+
? setTimeout(() => {
|
|
3104
|
+
timedOut = true;
|
|
3105
|
+
child.kill();
|
|
3106
|
+
}, timeoutMs)
|
|
3107
|
+
: undefined;
|
|
3108
|
+
timeout?.unref?.();
|
|
3109
|
+
const finish = (callback) => {
|
|
3110
|
+
if (settled) {
|
|
3111
|
+
return;
|
|
3112
|
+
}
|
|
3113
|
+
settled = true;
|
|
3114
|
+
if (timeout) {
|
|
3115
|
+
clearTimeout(timeout);
|
|
3116
|
+
}
|
|
3117
|
+
callback();
|
|
3118
|
+
};
|
|
3066
3119
|
child.stdout.on("data", (chunk) => {
|
|
3067
3120
|
stdout += String(chunk);
|
|
3068
3121
|
});
|
|
3069
3122
|
child.stderr.on("data", (chunk) => {
|
|
3070
3123
|
stderr += String(chunk);
|
|
3071
3124
|
});
|
|
3072
|
-
child.once("error", rejectPromise);
|
|
3125
|
+
child.once("error", (error) => finish(() => rejectPromise(error)));
|
|
3073
3126
|
child.once("exit", (code) => {
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3127
|
+
finish(() => {
|
|
3128
|
+
const err = stderr.trim();
|
|
3129
|
+
if (timedOut) {
|
|
3130
|
+
rejectPromise(new Error(`Process timed out after ${timeoutMs} ms: ${command} ${args.join(" ")}`));
|
|
3131
|
+
return;
|
|
3132
|
+
}
|
|
3133
|
+
if (code === 0) {
|
|
3134
|
+
resolvePromise(stdout);
|
|
3135
|
+
}
|
|
3136
|
+
else {
|
|
3137
|
+
rejectPromise(new Error(err || `Process exited with code ${code ?? "unknown"}.`));
|
|
3138
|
+
}
|
|
3139
|
+
});
|
|
3081
3140
|
});
|
|
3082
3141
|
});
|
|
3083
3142
|
}
|
|
@@ -3416,6 +3475,7 @@ function installEventOptions(options) {
|
|
|
3416
3475
|
silent: options.silent,
|
|
3417
3476
|
exePath: options.exePath,
|
|
3418
3477
|
installTokenProvided: Boolean(options.installToken),
|
|
3478
|
+
installTokenSource: options.installTokenSource,
|
|
3419
3479
|
};
|
|
3420
3480
|
}
|
|
3421
3481
|
function serviceManifestEventSummary(manifest) {
|