-
Notifications
You must be signed in to change notification settings - Fork 0
Spawn Primitives
How startDevServer(cwd) actually starts a dev server. In src/servers.ts.
spawn("/bin/zsh", ["-ilc", 'exec "$0" "$@"', cmd, ...args], {
cwd,
detached: true,
stdio: ["ignore", out, out],
});
child.unref();Raycast's GUI-app subprocess inherits a minimal PATH that doesn't include user-installed tools (nvm, pnpm, bun via Homebrew, etc.).
-
-lreads~/.zprofile -
-ireads~/.zshrc
Both flags are required: we don't know which file the user puts their PATH extensions in. Most put it in ~/.zshrc.
Not cd path && cmd. The path goes through the OS spawn syscall, not the shell. Removes one injection surface for paths with quotes/spaces/newlines.
The command string is the fixed literal exec "$0" "$@". The package manager, run, and the script name are passed as positional args after it (cmd, ...args), so zsh binds $0 to cmd and $@ to the rest, then re-quotes them on exec.
We do not build `exec ${cmd} ${args.join(" ")}` (the earlier form). A package.json script key containing a space ("dev server") or a shell metacharacter ("dev; rm -rf ~") would otherwise either break the command or inject. With the argv form the key is run verbatim; it's just an argument, never parsed as shell.
Note this isn't a privilege boundary: npm run <script> executes whatever the repo's script says anyway. It's defense-in-depth plus correctness for unusual-but-legal script names.
The spawned dev server needs to outlive the extension command. Without these, the FDs would close when Raycast tears down the command, killing the server.
killServer(pid) uses SIGKILL. A graceful-shutdown window races the new spawn for the same port. SIGKILL releases the listener immediately. Then we poll process.kill(pid, 0) until ESRCH (max 500ms) to confirm exit before spawning the replacement.
In a restart, the old server's cwd is what we'll watch for the new server at. If we don't kill-then-confirm-exit-then-spawn, the watch effect can see the old server momentarily and declare success before the new one has even started. Order:
- SIGKILL old PIDs (parallel for multi-target)
- Wait for each
process.kill(pid, 0)to throwESRCH -
spawnnew - Watch for new servers to appear in
fetchServers
dev-servers-spawn-<cwdSlug>.log in os.tmpdir(). Keyed by cwd slug so the file stays meaningful after the PID has been replaced. spawnLogPath(cwd) returns this path for error toasts.