fix(macos): prefer openclaw binary while keeping pnpm fallback (#25512)

Co-authored-by: Peter Machona <7957943+chilu18@users.noreply.github.com>
This commit is contained in:
Peter Steinberger
2026-02-25 00:11:53 +00:00
parent 236b22b6a2
commit 31e6d18538
3 changed files with 68 additions and 16 deletions

View File

@@ -246,15 +246,17 @@ enum CommandResolver {
return ssh
}
let runtimeResult = self.runtimeResolution(searchPaths: searchPaths)
let root = self.projectRoot()
if let openclawPath = self.projectOpenClawExecutable(projectRoot: root) {
return [openclawPath, subcommand] + extraArgs
}
if let openclawPath = self.openclawExecutable(searchPaths: searchPaths) {
return [openclawPath, subcommand] + extraArgs
}
let runtimeResult = self.runtimeResolution(searchPaths: searchPaths)
switch runtimeResult {
case let .success(runtime):
let root = self.projectRoot()
if let openclawPath = self.projectOpenClawExecutable(projectRoot: root) {
return [openclawPath, subcommand] + extraArgs
}
if let entry = self.gatewayEntrypoint(in: root) {
return self.makeRuntimeCommand(
runtime: runtime,
@@ -262,19 +264,21 @@ enum CommandResolver {
subcommand: subcommand,
extraArgs: extraArgs)
}
if let pnpm = self.findExecutable(named: "pnpm", searchPaths: searchPaths) {
// Use --silent to avoid pnpm lifecycle banners that would corrupt JSON outputs.
return [pnpm, "--silent", "openclaw", subcommand] + extraArgs
}
if let openclawPath = self.openclawExecutable(searchPaths: searchPaths) {
return [openclawPath, subcommand] + extraArgs
}
case .failure:
break
}
if let pnpm = self.findExecutable(named: "pnpm", searchPaths: searchPaths) {
// Use --silent to avoid pnpm lifecycle banners that would corrupt JSON outputs.
return [pnpm, "--silent", "openclaw", subcommand] + extraArgs
}
switch runtimeResult {
case .success:
let missingEntry = """
openclaw entrypoint missing (looked for dist/index.js or openclaw.mjs); run pnpm build.
"""
return self.errorCommand(with: missingEntry)
case let .failure(error):
return self.runtimeErrorCommand(error)
}

View File

@@ -66,6 +66,48 @@ import Testing
}
}
@Test func prefersOpenClawBinaryOverPnpm() async throws {
let defaults = self.makeDefaults()
defaults.set(AppState.ConnectionMode.local.rawValue, forKey: connectionModeKey)
let tmp = try makeTempDir()
CommandResolver.setProjectRoot(tmp.path)
let binDir = tmp.appendingPathComponent("bin")
let openclawPath = binDir.appendingPathComponent("openclaw")
let pnpmPath = binDir.appendingPathComponent("pnpm")
try self.makeExec(at: openclawPath)
try self.makeExec(at: pnpmPath)
let cmd = CommandResolver.openclawCommand(
subcommand: "rpc",
defaults: defaults,
configRoot: [:],
searchPaths: [binDir.path])
#expect(cmd.prefix(2).elementsEqual([openclawPath.path, "rpc"]))
}
@Test func usesOpenClawBinaryWithoutNodeRuntime() async throws {
let defaults = self.makeDefaults()
defaults.set(AppState.ConnectionMode.local.rawValue, forKey: connectionModeKey)
let tmp = try makeTempDir()
CommandResolver.setProjectRoot(tmp.path)
let binDir = tmp.appendingPathComponent("bin")
let openclawPath = binDir.appendingPathComponent("openclaw")
try self.makeExec(at: openclawPath)
let cmd = CommandResolver.openclawCommand(
subcommand: "gateway",
defaults: defaults,
configRoot: [:],
searchPaths: [binDir.path])
#expect(cmd.prefix(2).elementsEqual([openclawPath.path, "gateway"]))
}
@Test func fallsBackToPnpm() async throws {
let defaults = self.makeDefaults()
defaults.set(AppState.ConnectionMode.local.rawValue, forKey: connectionModeKey)
@@ -76,7 +118,11 @@ import Testing
let pnpmPath = tmp.appendingPathComponent("node_modules/.bin/pnpm")
try self.makeExec(at: pnpmPath)
let cmd = CommandResolver.openclawCommand(subcommand: "rpc", defaults: defaults, configRoot: [:])
let cmd = CommandResolver.openclawCommand(
subcommand: "rpc",
defaults: defaults,
configRoot: [:],
searchPaths: [tmp.appendingPathComponent("node_modules/.bin").path])
#expect(cmd.prefix(4).elementsEqual([pnpmPath.path, "--silent", "openclaw", "rpc"]))
}
@@ -95,7 +141,8 @@ import Testing
subcommand: "health",
extraArgs: ["--json", "--timeout", "5"],
defaults: defaults,
configRoot: [:])
configRoot: [:],
searchPaths: [tmp.appendingPathComponent("node_modules/.bin").path])
#expect(cmd.prefix(5).elementsEqual([pnpmPath.path, "--silent", "openclaw", "health", "--json"]))
#expect(cmd.suffix(2).elementsEqual(["--timeout", "5"]))