mirror of https://github.com/openclaw/openclaw.git
fix(macos): align minimum Node.js version with runtime guard (22.16.0) (#45640)
* macOS: align minimum Node.js version with runtime guard * macOS: add boundary and failure-message coverage for RuntimeLocator * docs: add changelog note for the macOS runtime locator fix * credit: original fix direction from @sumleo, cleaned up and rebased in #45640 by @ImLukeF
This commit is contained in:
parent
66e02b296f
commit
bed661609e
|
|
@ -59,6 +59,7 @@ Docs: https://docs.openclaw.ai
|
||||||
- Gateway/Control UI: restore the operator-only device-auth bypass and classify browser connect failures so origin and device-identity problems no longer show up as auth errors in the Control UI and web chat. (#45512) thanks @sallyom.
|
- Gateway/Control UI: restore the operator-only device-auth bypass and classify browser connect failures so origin and device-identity problems no longer show up as auth errors in the Control UI and web chat. (#45512) thanks @sallyom.
|
||||||
- macOS/voice wake: stop crashing wake-word command extraction when speech segment ranges come from a different transcript instance.
|
- macOS/voice wake: stop crashing wake-word command extraction when speech segment ranges come from a different transcript instance.
|
||||||
- Discord/allowlists: honor raw `guild_id` when hydrated guild objects are missing so allowlisted channels and threads like `#maintainers` no longer get false-dropped before channel allowlist checks.
|
- Discord/allowlists: honor raw `guild_id` when hydrated guild objects are missing so allowlisted channels and threads like `#maintainers` no longer get false-dropped before channel allowlist checks.
|
||||||
|
- macOS/runtime locator: require Node >=22.16.0 during macOS runtime discovery so the app no longer accepts Node versions that the main runtime guard rejects later. Thanks @sumleo.
|
||||||
|
|
||||||
## 2026.3.12
|
## 2026.3.12
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ enum RuntimeResolutionError: Error {
|
||||||
|
|
||||||
enum RuntimeLocator {
|
enum RuntimeLocator {
|
||||||
private static let logger = Logger(subsystem: "ai.openclaw", category: "runtime")
|
private static let logger = Logger(subsystem: "ai.openclaw", category: "runtime")
|
||||||
private static let minNode = RuntimeVersion(major: 22, minor: 0, patch: 0)
|
private static let minNode = RuntimeVersion(major: 22, minor: 16, patch: 0)
|
||||||
|
|
||||||
static func resolve(
|
static func resolve(
|
||||||
searchPaths: [String] = CommandResolver.preferredPaths()) -> Result<RuntimeResolution, RuntimeResolutionError>
|
searchPaths: [String] = CommandResolver.preferredPaths()) -> Result<RuntimeResolution, RuntimeResolutionError>
|
||||||
|
|
@ -91,7 +91,7 @@ enum RuntimeLocator {
|
||||||
switch error {
|
switch error {
|
||||||
case let .notFound(searchPaths):
|
case let .notFound(searchPaths):
|
||||||
[
|
[
|
||||||
"openclaw needs Node >=22.0.0 but found no runtime.",
|
"openclaw needs Node >=22.16.0 but found no runtime.",
|
||||||
"PATH searched: \(searchPaths.joined(separator: ":"))",
|
"PATH searched: \(searchPaths.joined(separator: ":"))",
|
||||||
"Install Node: https://nodejs.org/en/download",
|
"Install Node: https://nodejs.org/en/download",
|
||||||
].joined(separator: "\n")
|
].joined(separator: "\n")
|
||||||
|
|
@ -105,7 +105,7 @@ enum RuntimeLocator {
|
||||||
[
|
[
|
||||||
"Could not parse \(kind.rawValue) version output \"\(raw)\" from \(path).",
|
"Could not parse \(kind.rawValue) version output \"\(raw)\" from \(path).",
|
||||||
"PATH searched: \(searchPaths.joined(separator: ":"))",
|
"PATH searched: \(searchPaths.joined(separator: ":"))",
|
||||||
"Try reinstalling or pinning a supported version (Node >=22.0.0).",
|
"Try reinstalling or pinning a supported version (Node >=22.16.0).",
|
||||||
].joined(separator: "\n")
|
].joined(separator: "\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ struct RuntimeLocatorTests {
|
||||||
@Test func `resolve succeeds with valid node`() throws {
|
@Test func `resolve succeeds with valid node`() throws {
|
||||||
let script = """
|
let script = """
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
echo v22.5.0
|
echo v22.16.0
|
||||||
"""
|
"""
|
||||||
let node = try self.makeTempExecutable(contents: script)
|
let node = try self.makeTempExecutable(contents: script)
|
||||||
let result = RuntimeLocator.resolve(searchPaths: [node.deletingLastPathComponent().path])
|
let result = RuntimeLocator.resolve(searchPaths: [node.deletingLastPathComponent().path])
|
||||||
|
|
@ -25,7 +25,23 @@ struct RuntimeLocatorTests {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
#expect(res.path == node.path)
|
#expect(res.path == node.path)
|
||||||
#expect(res.version == RuntimeVersion(major: 22, minor: 5, patch: 0))
|
#expect(res.version == RuntimeVersion(major: 22, minor: 16, patch: 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func `resolve fails on boundary below minimum`() throws {
|
||||||
|
let script = """
|
||||||
|
#!/bin/sh
|
||||||
|
echo v22.15.9
|
||||||
|
"""
|
||||||
|
let node = try self.makeTempExecutable(contents: script)
|
||||||
|
let result = RuntimeLocator.resolve(searchPaths: [node.deletingLastPathComponent().path])
|
||||||
|
guard case let .failure(.unsupported(_, found, required, path, _)) = result else {
|
||||||
|
Issue.record("Expected unsupported error, got \(result)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
#expect(found == RuntimeVersion(major: 22, minor: 15, patch: 9))
|
||||||
|
#expect(required == RuntimeVersion(major: 22, minor: 16, patch: 0))
|
||||||
|
#expect(path == node.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func `resolve fails when too old`() throws {
|
@Test func `resolve fails when too old`() throws {
|
||||||
|
|
@ -60,7 +76,17 @@ struct RuntimeLocatorTests {
|
||||||
|
|
||||||
@Test func `describe failure includes paths`() {
|
@Test func `describe failure includes paths`() {
|
||||||
let msg = RuntimeLocator.describeFailure(.notFound(searchPaths: ["/tmp/a", "/tmp/b"]))
|
let msg = RuntimeLocator.describeFailure(.notFound(searchPaths: ["/tmp/a", "/tmp/b"]))
|
||||||
|
#expect(msg.contains("Node >=22.16.0"))
|
||||||
#expect(msg.contains("PATH searched: /tmp/a:/tmp/b"))
|
#expect(msg.contains("PATH searched: /tmp/a:/tmp/b"))
|
||||||
|
|
||||||
|
let parseMsg = RuntimeLocator.describeFailure(
|
||||||
|
.versionParse(
|
||||||
|
kind: .node,
|
||||||
|
raw: "garbage",
|
||||||
|
path: "/usr/local/bin/node",
|
||||||
|
searchPaths: ["/usr/local/bin"],
|
||||||
|
))
|
||||||
|
#expect(parseMsg.contains("Node >=22.16.0"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func `runtime version parses with leading V and metadata`() {
|
@Test func `runtime version parses with leading V and metadata`() {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue