@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 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 tryOpenExistingPortableStudioServer(options: StudioCliOptions, manifest: StudioReleaseManifest, fetchImpl?: FetchLike, openBrowserImpl?: (url: string) => void): Promise<{
118
- opened: boolean;
119
- baseUrl?: string;
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 tryOpenExistingPortableStudioServer(options, manifest, fetchImpl = fetch, openBrowserImpl = openBrowser) {
2168
- const record = await readStudioInstanceRecord(options);
2169
- if (!record) {
2170
- return { opened: false, reason: "instance_record_missing" };
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
- catch (error) {
2212
- console.warn(`Tapi Studio runtime cleanup failed: ${formatError(error)}. Opening Studio anyway.`);
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(getDefaultTapiDataDir(), "Studio", "instances", `${key}.json`);
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tapi-dev/sdk",
3
- "version": "0.1.28",
3
+ "version": "0.1.29",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",