@tapi-dev/sdk 0.1.28 → 0.1.29
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 -4
- package/dist/cli.js +89 -50
- package/package.json +1 -1
package/dist/cli.d.ts
CHANGED
|
@@ -114,10 +114,9 @@ export declare function validateStudioManifest(input: unknown): StudioReleaseMan
|
|
|
114
114
|
export declare function validateServiceReleaseManifest(input: unknown): ServiceReleaseManifest;
|
|
115
115
|
export declare function compareVersions(left: string, right: string): number;
|
|
116
116
|
export declare function memoizeStudioInstallToken(options: StudioCliOptions, installToken?: string): string;
|
|
117
|
-
export declare function
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
reason?: string;
|
|
117
|
+
export declare function closeExistingPortableStudioServersForFreshLaunch(stopProcesses?: () => Promise<number>): Promise<{
|
|
118
|
+
stopped: number;
|
|
119
|
+
recordsRemoved: number;
|
|
121
120
|
}>;
|
|
122
121
|
export declare function findPortablePlaywrightBrowsersDir(exePath: string): string | undefined;
|
|
123
122
|
export declare function waitForHttpOk(url: string, timeoutMs?: number, intervalMs?: number, fetchImpl?: FetchLike): Promise<void>;
|
package/dist/cli.js
CHANGED
|
@@ -2123,13 +2123,10 @@ async function openStudio(options) {
|
|
|
2123
2123
|
throw new Error("--download-only is only supported with `tapi studio install`.");
|
|
2124
2124
|
}
|
|
2125
2125
|
await ensureServiceReadyForStudio(options);
|
|
2126
|
+
await closeExistingPortableStudioServersForFreshLaunch();
|
|
2126
2127
|
if (!options.exePath) {
|
|
2127
2128
|
const portable = await ensurePortableStudioServer(options);
|
|
2128
2129
|
if (portable) {
|
|
2129
|
-
const existing = await tryOpenExistingPortableStudioServer(options, portable.manifest);
|
|
2130
|
-
if (existing.opened) {
|
|
2131
|
-
return 0;
|
|
2132
|
-
}
|
|
2133
2130
|
await launchPortableStudioServer(portable.exePath, options, portable.manifest);
|
|
2134
2131
|
return 0;
|
|
2135
2132
|
}
|
|
@@ -2164,53 +2161,16 @@ function findStudioExecutable(options) {
|
|
|
2164
2161
|
}
|
|
2165
2162
|
return getStudioExecutableCandidates().find((candidate) => existsSync(candidate));
|
|
2166
2163
|
}
|
|
2167
|
-
export async function
|
|
2168
|
-
const
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
if ((record.manifestVersion || "") !== manifest.version) {
|
|
2173
|
-
return { opened: false, reason: "manifest_version_mismatch" };
|
|
2174
|
-
}
|
|
2175
|
-
try {
|
|
2176
|
-
const identityResponse = await fetchWithTimeout(`${record.baseUrl.replace(/\/+$/, "")}/api/studio/control/identity`, { headers: { Accept: "application/json" } }, 1_000, fetchImpl);
|
|
2177
|
-
if (!identityResponse.ok) {
|
|
2178
|
-
await unlinkIfExists(studioInstanceRecordPathForOptions(options));
|
|
2179
|
-
return { opened: false, reason: `identity_http_${identityResponse.status}` };
|
|
2180
|
-
}
|
|
2181
|
-
const identity = await readJsonBody(identityResponse);
|
|
2182
|
-
if (!studioInstanceIdentityMatches(identity, options)) {
|
|
2183
|
-
return { opened: false, reason: "identity_mismatch" };
|
|
2184
|
-
}
|
|
2185
|
-
const launchUrl = studioLaunchUrl(record.baseUrl, options);
|
|
2186
|
-
await requestExistingStudioRuntimeCleanup(record.baseUrl, fetchImpl);
|
|
2187
|
-
openBrowserImpl(launchUrl);
|
|
2188
|
-
console.log(`Opened existing Tapi Studio ${record.manifestVersion || manifest.version}: ${launchUrl}`);
|
|
2189
|
-
return { opened: true, baseUrl: record.baseUrl };
|
|
2190
|
-
}
|
|
2191
|
-
catch (error) {
|
|
2192
|
-
await unlinkIfExists(studioInstanceRecordPathForOptions(options));
|
|
2193
|
-
return { opened: false, reason: formatError(error) };
|
|
2194
|
-
}
|
|
2195
|
-
}
|
|
2196
|
-
async function requestExistingStudioRuntimeCleanup(baseUrl, fetchImpl = fetch) {
|
|
2197
|
-
const url = `${baseUrl.replace(/\/+$/, "")}/api/studio/control/cleanup-runtime-session`;
|
|
2198
|
-
try {
|
|
2199
|
-
const response = await fetchWithTimeout(url, {
|
|
2200
|
-
method: "POST",
|
|
2201
|
-
headers: {
|
|
2202
|
-
Accept: "application/json",
|
|
2203
|
-
"Content-Type": "application/json",
|
|
2204
|
-
},
|
|
2205
|
-
body: JSON.stringify({ reason: "tapi_studio_reopen" }),
|
|
2206
|
-
}, 5_000, fetchImpl);
|
|
2207
|
-
if (!response.ok && response.status !== 404) {
|
|
2208
|
-
console.warn(`Tapi Studio runtime cleanup returned HTTP ${response.status}; opening Studio anyway.`);
|
|
2209
|
-
}
|
|
2164
|
+
export async function closeExistingPortableStudioServersForFreshLaunch(stopProcesses = stopPortableStudioServerProcesses) {
|
|
2165
|
+
const stopped = await stopProcesses();
|
|
2166
|
+
const recordsRemoved = await clearStudioInstanceRecords();
|
|
2167
|
+
if (stopped > 0) {
|
|
2168
|
+
await delay(500);
|
|
2210
2169
|
}
|
|
2211
|
-
|
|
2212
|
-
console.
|
|
2170
|
+
if (stopped > 0 || recordsRemoved > 0) {
|
|
2171
|
+
console.log(`Closed existing Tapi Studio servers before fresh launch: stopped=${stopped}, records=${recordsRemoved}`);
|
|
2213
2172
|
}
|
|
2173
|
+
return { stopped, recordsRemoved };
|
|
2214
2174
|
}
|
|
2215
2175
|
async function ensurePortableStudioServer(options) {
|
|
2216
2176
|
const event = await createCliWideEvent("tapi_cli.studio_server_ensure", {
|
|
@@ -2490,6 +2450,53 @@ foreach ($pidValue in $owners.Keys) {
|
|
|
2490
2450
|
return 0;
|
|
2491
2451
|
}
|
|
2492
2452
|
}
|
|
2453
|
+
async function stopPortableStudioServerProcesses() {
|
|
2454
|
+
if (process.platform !== "win32") {
|
|
2455
|
+
return 0;
|
|
2456
|
+
}
|
|
2457
|
+
const script = `
|
|
2458
|
+
$ErrorActionPreference = 'SilentlyContinue'
|
|
2459
|
+
$currentPid = $PID
|
|
2460
|
+
$processes = @(Get-CimInstance Win32_Process | Where-Object {
|
|
2461
|
+
$pidValue = [int]$_.ProcessId
|
|
2462
|
+
if ($pidValue -le 0 -or $pidValue -eq $currentPid) {
|
|
2463
|
+
$false
|
|
2464
|
+
} else {
|
|
2465
|
+
$path = [string]$_.ExecutablePath
|
|
2466
|
+
$cmd = [string]$_.CommandLine
|
|
2467
|
+
$name = [string]$_.Name
|
|
2468
|
+
(
|
|
2469
|
+
$name -ieq 'tapi-studio-server.exe' -or
|
|
2470
|
+
$path -match '\\\\Tapi\\\\Studio\\\\server\\\\releases\\\\' -or
|
|
2471
|
+
$cmd -match 'tapi-studio-server' -or
|
|
2472
|
+
$cmd -match 'scripts[\\\\/]studio\\.py'
|
|
2473
|
+
)
|
|
2474
|
+
}
|
|
2475
|
+
})
|
|
2476
|
+
$stopped = 0
|
|
2477
|
+
foreach ($process in $processes) {
|
|
2478
|
+
try {
|
|
2479
|
+
Stop-Process -Id ([int]$process.ProcessId) -Force -ErrorAction Stop
|
|
2480
|
+
$stopped += 1
|
|
2481
|
+
} catch {}
|
|
2482
|
+
}
|
|
2483
|
+
[pscustomobject]@{ stopped = $stopped } | ConvertTo-Json -Compress
|
|
2484
|
+
`;
|
|
2485
|
+
try {
|
|
2486
|
+
const output = await runProcessCapture("powershell.exe", [
|
|
2487
|
+
"-NoProfile",
|
|
2488
|
+
"-ExecutionPolicy",
|
|
2489
|
+
"Bypass",
|
|
2490
|
+
"-Command",
|
|
2491
|
+
script,
|
|
2492
|
+
]);
|
|
2493
|
+
const payload = JSON.parse(output.trim() || "{}");
|
|
2494
|
+
return typeof payload.stopped === "number" ? payload.stopped : 0;
|
|
2495
|
+
}
|
|
2496
|
+
catch {
|
|
2497
|
+
return 0;
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2493
2500
|
async function runDoctor(options) {
|
|
2494
2501
|
console.log(`Tapi SDK: ${sdkVersion}`);
|
|
2495
2502
|
console.log(`Node: ${process.version}`);
|
|
@@ -2795,7 +2802,39 @@ export function studioInstanceRecordPathForOptions(options) {
|
|
|
2795
2802
|
}))
|
|
2796
2803
|
.digest("hex")
|
|
2797
2804
|
.slice(0, 24);
|
|
2798
|
-
return join(
|
|
2805
|
+
return join(studioInstanceRecordsDir(), `${key}.json`);
|
|
2806
|
+
}
|
|
2807
|
+
function studioInstanceRecordsDir() {
|
|
2808
|
+
return join(getDefaultTapiDataDir(), "Studio", "instances");
|
|
2809
|
+
}
|
|
2810
|
+
async function clearStudioInstanceRecords() {
|
|
2811
|
+
let entries;
|
|
2812
|
+
try {
|
|
2813
|
+
entries = await readdir(studioInstanceRecordsDir(), { withFileTypes: true });
|
|
2814
|
+
}
|
|
2815
|
+
catch (error) {
|
|
2816
|
+
if (error.code === "ENOENT") {
|
|
2817
|
+
return 0;
|
|
2818
|
+
}
|
|
2819
|
+
throw error;
|
|
2820
|
+
}
|
|
2821
|
+
let removed = 0;
|
|
2822
|
+
for (const entry of entries) {
|
|
2823
|
+
if (!entry.isFile() || !entry.name.toLowerCase().endsWith(".json")) {
|
|
2824
|
+
continue;
|
|
2825
|
+
}
|
|
2826
|
+
const path = join(studioInstanceRecordsDir(), entry.name);
|
|
2827
|
+
try {
|
|
2828
|
+
await unlink(path);
|
|
2829
|
+
removed += 1;
|
|
2830
|
+
}
|
|
2831
|
+
catch (error) {
|
|
2832
|
+
if (error.code !== "ENOENT") {
|
|
2833
|
+
throw error;
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
return removed;
|
|
2799
2838
|
}
|
|
2800
2839
|
async function writeStudioInstanceRecord(options, record) {
|
|
2801
2840
|
await writeJsonFile(studioInstanceRecordPathForOptions(options), record);
|