From cefebd1eca5f4d840ed50e38d8e07a1a1f9ee610 Mon Sep 17 00:00:00 2001 From: luvs01 <27862058+luvs01@users.noreply.github.com> Date: Sun, 20 Sep 2026 12:21:39 +0900 Subject: [PATCH 1/2] fix(integrations): harden Raycast defaults probe --- src/integrations/raycast-detect.ts | 21 +++++++++++++++++---- tests/clients/raycast-detect.test.ts | 21 ++++++++++++++++++++- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/integrations/raycast-detect.ts b/src/integrations/raycast-detect.ts index 7ae70edf461..1fafd992ff9 100644 --- a/src/integrations/raycast-detect.ts +++ b/src/integrations/raycast-detect.ts @@ -44,10 +44,19 @@ export interface RaycastDetectDeps { */ const RAYCAST_DEFAULTS_DOMAIN = "com.raycast.macos.v1"; const RAYCAST_SUBSCRIPTION_KEY = "subscriptions_active"; +const DEFAULTS_PATH = "/usr/bin/defaults"; +const DEFAULTS_TIMEOUT_MS = 2_000; -export function realRaycastDetectDeps(): RaycastDetectDeps { +interface RealRaycastDetectRuntime { + platform?: string; + spawnSync?: typeof Bun.spawnSync; +} + +export function realRaycastDetectDeps(runtime: RealRaycastDetectRuntime = {}): RaycastDetectDeps { + const platform = runtime.platform ?? process.platform; + const spawnSync = runtime.spawnSync ?? Bun.spawnSync; return { - platform: process.platform, + platform, homedir: homedir(), env: process.env, exists: path => { @@ -59,9 +68,13 @@ export function realRaycastDetectDeps(): RaycastDetectDeps { }, readDefault: (domain, key) => { // `defaults` is macOS-only; elsewhere the plan is simply unknown. - if (process.platform !== "darwin") return null; + if (platform !== "darwin") return null; try { - const result = Bun.spawnSync(["defaults", "read", domain, key], { stdout: "pipe", stderr: "pipe" }); + const result = spawnSync([DEFAULTS_PATH, "read", domain, key], { + stdout: "pipe", + stderr: "pipe", + timeout: DEFAULTS_TIMEOUT_MS, + }); if (result.exitCode !== 0) return null; return result.stdout.toString().trim(); } catch { diff --git a/tests/clients/raycast-detect.test.ts b/tests/clients/raycast-detect.test.ts index 4e4268b29b3..c2865a5fa76 100644 --- a/tests/clients/raycast-detect.test.ts +++ b/tests/clients/raycast-detect.test.ts @@ -1,5 +1,9 @@ import { describe, expect, test } from "bun:test"; -import { detectRaycast, type RaycastDetectDeps } from "../../src/integrations/raycast-detect"; +import { + detectRaycast, + realRaycastDetectDeps, + type RaycastDetectDeps, +} from "../../src/integrations/raycast-detect"; /** * Stubbed deps only. The real detector spawns `defaults` and reads the @@ -29,6 +33,21 @@ function fakeDeps( } describe("detectRaycast", () => { + test("the macOS preference probe uses the system binary with a bounded runtime", () => { + let invocation: { command: string[]; timeout?: number } | undefined; + const spawnSync = ((command: string[], options: { timeout?: number }) => { + invocation = { command, timeout: options.timeout }; + return { exitCode: 0, stdout: Buffer.from("1") }; + }) as typeof Bun.spawnSync; + + const deps = realRaycastDetectDeps({ platform: "darwin", spawnSync }); + expect(deps.readDefault("com.raycast.macos.v1", "subscriptions_active")).toBe("1"); + expect(invocation).toEqual({ + command: ["/usr/bin/defaults", "read", "com.raycast.macos.v1", "subscriptions_active"], + timeout: 2_000, + }); + }); + test("darwin: a Pro subscription, the app bundle and the revealed ai folder", () => { const deps = fakeDeps("darwin", ["/Applications/Raycast.app", "/home/u/.config/raycast/ai"], { defaultValue: "1" }); expect(detectRaycast(deps)).toEqual({ From 05d48b028b58e9a72c98083dc043f3537acb735d Mon Sep 17 00:00:00 2001 From: luvs01 <27862058+luvs01@users.noreply.github.com> Date: Sun, 20 Sep 2026 14:55:02 +0900 Subject: [PATCH 2/2] test(integrations): cover killed defaults probe in Raycast detection --- src/integrations/raycast-detect.ts | 2 ++ tests/clients/raycast-detect.test.ts | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/src/integrations/raycast-detect.ts b/src/integrations/raycast-detect.ts index 1fafd992ff9..e0ef3191f6d 100644 --- a/src/integrations/raycast-detect.ts +++ b/src/integrations/raycast-detect.ts @@ -75,6 +75,8 @@ export function realRaycastDetectDeps(runtime: RealRaycastDetectRuntime = {}): R stderr: "pipe", timeout: DEFAULTS_TIMEOUT_MS, }); + // A timed-out or signal-killed probe reports exitCode === null; that + // and any non-zero exit mean the preference was not read. if (result.exitCode !== 0) return null; return result.stdout.toString().trim(); } catch { diff --git a/tests/clients/raycast-detect.test.ts b/tests/clients/raycast-detect.test.ts index c2865a5fa76..afdc3dbeb4f 100644 --- a/tests/clients/raycast-detect.test.ts +++ b/tests/clients/raycast-detect.test.ts @@ -74,6 +74,14 @@ describe("detectRaycast", () => { expect(detectRaycast(fakeDeps("darwin", [], { defaultValue: "" })).plan).toBe("unknown"); }); + test("darwin: a timed-out or killed defaults probe is unknown, not a false positive", () => { + // Bun reports exitCode === null when the 2s timeout kills the process. + const spawnSync = (() => ({ exitCode: null, stdout: Buffer.from("") })) as typeof Bun.spawnSync; + const deps = realRaycastDetectDeps({ platform: "darwin", spawnSync }); + expect(deps.readDefault("com.raycast.macos.v1", "subscriptions_active")).toBeNull(); + expect(detectRaycast(deps).plan).toBe("unknown"); + }); + test("win32: LOCALAPPDATA\\Programs\\Raycast is the install path and the plan is unknown", () => { const local = "C:\\Users\\u\\AppData\\Local"; const deps = fakeDeps("win32", [`${local}\\Programs\\Raycast`, "C:\\Users\\u\\.config\\raycast\\ai"], {