@tapi-dev/sdk 0.1.27 → 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,33 +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" };
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);
2171
2169
  }
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
- openBrowserImpl(launchUrl);
2187
- console.log(`Opened existing Tapi Studio ${record.manifestVersion || manifest.version}: ${launchUrl}`);
2188
- return { opened: true, baseUrl: record.baseUrl };
2189
- }
2190
- catch (error) {
2191
- await unlinkIfExists(studioInstanceRecordPathForOptions(options));
2192
- return { opened: false, reason: formatError(error) };
2170
+ if (stopped > 0 || recordsRemoved > 0) {
2171
+ console.log(`Closed existing Tapi Studio servers before fresh launch: stopped=${stopped}, records=${recordsRemoved}`);
2193
2172
  }
2173
+ return { stopped, recordsRemoved };
2194
2174
  }
2195
2175
  async function ensurePortableStudioServer(options) {
2196
2176
  const event = await createCliWideEvent("tapi_cli.studio_server_ensure", {
@@ -2470,6 +2450,53 @@ foreach ($pidValue in $owners.Keys) {
2470
2450
  return 0;
2471
2451
  }
2472
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
+ }
2473
2500
  async function runDoctor(options) {
2474
2501
  console.log(`Tapi SDK: ${sdkVersion}`);
2475
2502
  console.log(`Node: ${process.version}`);
@@ -2775,7 +2802,39 @@ export function studioInstanceRecordPathForOptions(options) {
2775
2802
  }))
2776
2803
  .digest("hex")
2777
2804
  .slice(0, 24);
2778
- 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;
2779
2838
  }
2780
2839
  async function writeStudioInstanceRecord(options, record) {
2781
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.27",
3
+ "version": "0.1.29",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",