mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-05-10 04:09:40 +00:00
fix: re-exec with new binary after auto-update for all invocations (#1526)
Two bugs in reExecWithArgs(): 1. args.length === 0 early exit: Running bare `spawn` (interactive picker) after an auto-update would print "Run your spawn command again" and exit, requiring the user to manually re-invoke. Now always re-exec so the new flow triggers immediately. 2. process.argv[1] stale binary path: If the installer places the updated binary in a different directory than the currently running binary (e.g. old: ~/.local/bin, new: /usr/local/bin), re-exec would run the old stale binary. Fix: add findUpdatedBinary() which resolves via `which spawn` (PATH lookup) first, falling back to process.argv[1] only if which fails. Bump CLI version 0.5.17 → 0.5.18. Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
be48fe8576
commit
eea43adcad
6 changed files with 86 additions and 18 deletions
27
cli/biome.json
Normal file
27
cli/biome.json
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.4.3/schema.json",
|
||||
"vcs": {
|
||||
"enabled": true,
|
||||
"clientKind": "git",
|
||||
"useIgnoreFile": true
|
||||
},
|
||||
"files": {
|
||||
"ignoreUnknown": false,
|
||||
"includes": ["src/**/*.ts"]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": false
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true,
|
||||
"suspicious": {
|
||||
"noExplicitAny": "error"
|
||||
}
|
||||
}
|
||||
},
|
||||
"assist": {
|
||||
"enabled": false
|
||||
}
|
||||
}
|
||||
19
cli/bun.lock
19
cli/bun.lock
|
|
@ -9,11 +9,30 @@
|
|||
"picocolors": "^1.1.1",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.4.3",
|
||||
"@types/bun": "^1.3.8",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@biomejs/biome": ["@biomejs/biome@2.4.3", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.3", "@biomejs/cli-darwin-x64": "2.4.3", "@biomejs/cli-linux-arm64": "2.4.3", "@biomejs/cli-linux-arm64-musl": "2.4.3", "@biomejs/cli-linux-x64": "2.4.3", "@biomejs/cli-linux-x64-musl": "2.4.3", "@biomejs/cli-win32-arm64": "2.4.3", "@biomejs/cli-win32-x64": "2.4.3" }, "bin": { "biome": "bin/biome" } }, "sha512-cBrjf6PNF6yfL8+kcNl85AjiK2YHNsbU0EvDOwiZjBPbMbQ5QcgVGFpjD0O52p8nec5O8NYw7PKw3xUR7fPAkQ=="],
|
||||
|
||||
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-eOafSFlI/CF4id2tlwq9CVHgeEqvTL5SrhWff6ZORp6S3NL65zdsR3ugybItkgF8Pf4D9GSgtbB6sE3UNgOM9w=="],
|
||||
|
||||
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-V2+av4ilbWcBMNufTtMMXVW00nPwyIjI5qf7n9wSvUaZ+tt0EvMGk46g9sAFDJBEDOzSyoRXiSP6pCvKTOEbPA=="],
|
||||
|
||||
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-0m+O0x9FgK99FAwDK+fiDtjs2wnqq7bvfj17KJVeCkTwT/liI+Q9njJG7lwXK0iSJVXeFNRIxukpVI3SifMYAA=="],
|
||||
|
||||
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-QuFzvsGo8BA4Xm7jGX5idkw6BqFblcCPySMTvq0AhGYnhUej5VJIDJbmTKfHqwjHepZiC4fA+T5i6wmiZolZNw=="],
|
||||
|
||||
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.3", "", { "os": "linux", "cpu": "x64" }, "sha512-NVqh0saIU0u5OfOp/0jFdlKRE59+XyMvWmtx0f6Nm/2OpdxBl04coRIftBbY9d1gfu+23JVv4CItAqPYrjYh5w=="],
|
||||
|
||||
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.3", "", { "os": "linux", "cpu": "x64" }, "sha512-qEc0OCpj/uytruQ4wLM0yWNJLZy0Up8H1Er5MW3SrstqM6J2d4XqdNA86xzCy8MQCHpoVZ3lFye3GBlIL4/ljw=="],
|
||||
|
||||
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-gRO96vrIARilv/Cp2ZnmNNL5LSZg3RO75GPp13hsLO3N4YVpE7saaMDp2bcyV48y2N2Pbit1brkGVGta0yd6VQ=="],
|
||||
|
||||
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.3", "", { "os": "win32", "cpu": "x64" }, "sha512-vSm/vOJe06pf14aGHfHl3Ar91Nlx4YYmohElDJ+17UbRwe99n987S/MhAlQOkONqf1utJor04ChkCPmKb8SWdw=="],
|
||||
|
||||
"@clack/core": ["@clack/core@1.0.0", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-Orf9Ltr5NeiEuVJS8Rk2XTw3IxNC2Bic3ash7GgYeA8LJ/zmSNpSQ/m5UAhe03lA6KFgklzZ5KTHs4OAMA/SAQ=="],
|
||||
|
||||
"@clack/prompts": ["@clack/prompts@1.0.0", "", { "dependencies": { "@clack/core": "1.0.0", "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-rWPXg9UaCFqErJVQ+MecOaWsozjaxol4yjnmYcGNipAWzdaWa2x+VJmKfGq7L0APwBohQOYdHC+9RO4qRXej+A=="],
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@openrouter/spawn",
|
||||
"version": "0.5.17",
|
||||
"version": "0.5.18",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"spawn": "cli.js"
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
"dev": "bun run src/index.ts",
|
||||
"build": "bun build src/index.ts --outfile cli.js --target bun --minify --packages bundle",
|
||||
"compile": "bun build src/index.ts --compile --outfile spawn",
|
||||
"lint": "biome lint src/",
|
||||
"test": "bun test",
|
||||
"test:watch": "bun test --watch"
|
||||
},
|
||||
|
|
@ -17,6 +18,7 @@
|
|||
"picocolors": "^1.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.4.3",
|
||||
"@types/bun": "^1.3.8"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -269,7 +269,7 @@ function setupManifest(manifest: Manifest) {
|
|||
ok: true,
|
||||
json: async () => manifest,
|
||||
text: async () => JSON.stringify(manifest),
|
||||
})) as any;
|
||||
})) as unknown as typeof global.fetch;
|
||||
return loadManifest(true);
|
||||
}
|
||||
|
||||
|
|
@ -283,11 +283,11 @@ describe("cmdAgentInfo - printAgentQuickStart", () => {
|
|||
let savedEnv: Record<string, string | undefined>;
|
||||
|
||||
function getOutput(): string {
|
||||
return consoleSpy.mock.calls.map((c: any[]) => c.join(" ")).join("\n");
|
||||
return consoleSpy.mock.calls.map((c: unknown[]) => c.join(" ")).join("\n");
|
||||
}
|
||||
|
||||
function getOutputLines(): string[] {
|
||||
return consoleSpy.mock.calls.map((c: any[]) => c.join(" "));
|
||||
return consoleSpy.mock.calls.map((c: unknown[]) => c.join(" "));
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
|
|
@ -302,7 +302,7 @@ describe("cmdAgentInfo - printAgentQuickStart", () => {
|
|||
|
||||
processExitSpy = spyOn(process, "exit").mockImplementation((() => {
|
||||
throw new Error("process.exit");
|
||||
}) as any);
|
||||
}) as unknown as (code?: number) => never);
|
||||
|
||||
originalFetch = global.fetch;
|
||||
|
||||
|
|
|
|||
|
|
@ -98,11 +98,11 @@ function createManifest(): Manifest {
|
|||
}
|
||||
|
||||
function infoCalls(): string[] {
|
||||
return mockLogInfo.mock.calls.map((c: any[]) => c.join(" "));
|
||||
return mockLogInfo.mock.calls.map((c: unknown[]) => c.join(" "));
|
||||
}
|
||||
|
||||
function errorCalls(): string[] {
|
||||
return mockLogError.mock.calls.map((c: any[]) => c.join(" "));
|
||||
return mockLogError.mock.calls.map((c: unknown[]) => c.join(" "));
|
||||
}
|
||||
|
||||
// ── Tests ───────────────────────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import "./unicode-detect.js"; // Ensure TERM is set before using symbols
|
||||
import { execSync as nodeExecSync, execFileSync as nodeExecFileSync } from "child_process";
|
||||
import { execSync as nodeExecSync, execFileSync as nodeExecFileSync, type ExecSyncOptions, type ExecFileSyncOptions } from "child_process";
|
||||
import pc from "picocolors";
|
||||
import pkg from "../package.json" with { type: "json" };
|
||||
import { RAW_BASE } from "./manifest.js";
|
||||
|
|
@ -8,8 +8,8 @@ const VERSION = pkg.version;
|
|||
|
||||
// Internal executor for testability - can be replaced in tests
|
||||
export const executor = {
|
||||
execSync: (cmd: string, options?: any) => nodeExecSync(cmd, options),
|
||||
execFileSync: (file: string, args: string[], options?: any) => nodeExecFileSync(file, args, options),
|
||||
execSync: (cmd: string, options?: ExecSyncOptions) => nodeExecSync(cmd, options),
|
||||
execFileSync: (file: string, args: string[], options?: ExecFileSyncOptions) => nodeExecFileSync(file, args, options),
|
||||
};
|
||||
|
||||
// ── Constants ──────────────────────────────────────────────────────────────────
|
||||
|
|
@ -79,19 +79,39 @@ function printUpdateBanner(latestVersion: string): void {
|
|||
console.error();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the spawn binary to re-exec after an update.
|
||||
*
|
||||
* Prefers `which spawn` (PATH resolution) over process.argv[1] because the
|
||||
* installer may place the new binary in a different directory than where the
|
||||
* currently running binary lives, causing re-exec to run the stale old binary.
|
||||
*/
|
||||
function findUpdatedBinary(): string {
|
||||
try {
|
||||
const result = executor.execSync("which spawn 2>/dev/null", {
|
||||
encoding: "utf8",
|
||||
shell: "/bin/bash",
|
||||
});
|
||||
const found = result ? result.toString().trim() : "";
|
||||
if (found) return found;
|
||||
} catch {
|
||||
// fall through to argv fallback
|
||||
}
|
||||
return process.argv[1] || "spawn";
|
||||
}
|
||||
|
||||
/** Re-exec the updated binary with the original CLI arguments, forwarding the exit code */
|
||||
function reExecWithArgs(): void {
|
||||
const args = process.argv.slice(2);
|
||||
if (args.length === 0) {
|
||||
console.error(pc.dim(" Run your spawn command again to use the new version."));
|
||||
console.error();
|
||||
process.exit(0);
|
||||
return; // unreachable in production, but needed when process.exit is mocked in tests
|
||||
}
|
||||
const binPath = findUpdatedBinary();
|
||||
|
||||
const binPath = process.argv[1] || "spawn";
|
||||
console.error(pc.dim(` Rerunning: spawn ${args.join(" ")}`));
|
||||
if (args.length === 0) {
|
||||
console.error(pc.dim(" Restarting spawn with updated version..."));
|
||||
} else {
|
||||
console.error(pc.dim(` Rerunning: spawn ${args.join(" ")}`));
|
||||
}
|
||||
console.error();
|
||||
|
||||
try {
|
||||
executor.execFileSync(binPath, args, {
|
||||
stdio: "inherit",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue