mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 20:05:46 -06:00
fix(macos): prefer supported version-manager Node runtimes (#129564)
This commit is contained in:
committed by
GitHub
parent
955d23d6b4
commit
482fbdc161
@@ -220,26 +220,17 @@ enum CommandResolver {
|
||||
return []
|
||||
}
|
||||
|
||||
func parseVersion(_ name: String) -> [Int] {
|
||||
let trimmed = name.hasPrefix("v") ? String(name.dropFirst()) : name
|
||||
return trimmed.split(separator: ".").compactMap { Int($0) }
|
||||
}
|
||||
|
||||
let sorted = entries.sorted { a, b in
|
||||
let va = parseVersion(a)
|
||||
let vb = parseVersion(b)
|
||||
let maxCount = max(va.count, vb.count)
|
||||
for i in 0..<maxCount {
|
||||
let ai = i < va.count ? va[i] : 0
|
||||
let bi = i < vb.count ? vb[i] : 0
|
||||
if ai != bi { return ai > bi }
|
||||
}
|
||||
// If identical numerically, keep stable ordering.
|
||||
return a > b
|
||||
let sorted = entries.compactMap { entry -> (name: String, version: RuntimeVersion)? in
|
||||
guard let version = RuntimeVersion.from(string: entry),
|
||||
RuntimeLocator.isSupportedNodeVersion(version)
|
||||
else { return nil }
|
||||
return (entry, version)
|
||||
}.sorted { first, second in
|
||||
first.version == second.version ? first.name > second.name : first.version > second.version
|
||||
}
|
||||
|
||||
var paths: [String] = []
|
||||
for entry in sorted {
|
||||
for (entry, _) in sorted {
|
||||
let binDir = base.appendingPathComponent(entry).appendingPathComponent(suffix)
|
||||
let node = binDir.appendingPathComponent("node")
|
||||
if FileManager().isExecutableFile(atPath: node.path) {
|
||||
@@ -721,10 +712,4 @@ enum CommandResolver {
|
||||
args.append(contentsOf: remoteCommand)
|
||||
return args
|
||||
}
|
||||
|
||||
#if SWIFT_PACKAGE
|
||||
static func _testNodeManagerBinPaths(home: URL) -> [String] {
|
||||
self.nodeManagerBinPaths(home: home)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -3,28 +3,79 @@ import Testing
|
||||
@testable import OpenClaw
|
||||
|
||||
struct NodeManagerPathsTests {
|
||||
@Test func `fnm node bins prefer newest installed version`() throws {
|
||||
@Test func `fnm node bins prefer the newest supported installed version`() throws {
|
||||
let home = try makeTempDirForTests()
|
||||
defer { try? FileManager.default.removeItem(at: home) }
|
||||
|
||||
let v20Bin = home
|
||||
.appendingPathComponent(".local/share/fnm/node-versions/v20.19.5/installation/bin/node")
|
||||
let v25Bin = home
|
||||
.appendingPathComponent(".local/share/fnm/node-versions/v25.1.0/installation/bin/node")
|
||||
try makeExecutableForTests(at: v20Bin)
|
||||
try makeExecutableForTests(at: v25Bin)
|
||||
let v22Node = home
|
||||
.appendingPathComponent(".local/share/fnm/node-versions/v22.22.3/installation/bin/node")
|
||||
let v25Node = home
|
||||
.appendingPathComponent(".local/share/fnm/node-versions/v25.9.0/installation/bin/node")
|
||||
try makeExecutableForTests(at: v22Node)
|
||||
try makeExecutableForTests(at: v25Node)
|
||||
|
||||
let bins = CommandResolver._testNodeManagerBinPaths(home: home)
|
||||
#expect(bins.first == v25Bin.deletingLastPathComponent().path)
|
||||
#expect(bins.contains(v20Bin.deletingLastPathComponent().path))
|
||||
let paths = CommandResolver.preferredPaths(home: home, current: [], projectRoot: home)
|
||||
let newestIndex = try #require(paths.firstIndex(of: v25Node.deletingLastPathComponent().path))
|
||||
let olderIndex = try #require(paths.firstIndex(of: v22Node.deletingLastPathComponent().path))
|
||||
|
||||
#expect(newestIndex < olderIndex)
|
||||
}
|
||||
|
||||
@Test(arguments: [
|
||||
(".local/share/fnm/node-versions", "installation/bin"),
|
||||
(".nvm/versions/node", "bin"),
|
||||
])
|
||||
func `unsupported newer manager runtimes cannot hide a supported installed Node`(
|
||||
managerRoot: String,
|
||||
binarySuffix: String) async throws
|
||||
{
|
||||
let home = try makeTempDirForTests()
|
||||
defer { try? FileManager.default.removeItem(at: home) }
|
||||
let versions = ["v25.8.1", "v24.15.0", "v23.11.0", "v22.22.3"]
|
||||
|
||||
for version in versions {
|
||||
let node = home
|
||||
.appendingPathComponent(managerRoot)
|
||||
.appendingPathComponent(version)
|
||||
.appendingPathComponent(binarySuffix)
|
||||
.appendingPathComponent("node")
|
||||
try makeExecutableForTests(at: node)
|
||||
try "#!/bin/sh\necho \(version)\n".write(to: node, atomically: true, encoding: .utf8)
|
||||
try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: node.path)
|
||||
}
|
||||
|
||||
let unsupported = home
|
||||
.appendingPathComponent(managerRoot)
|
||||
.appendingPathComponent("v25.8.1")
|
||||
.appendingPathComponent(binarySuffix)
|
||||
let expectedNode = home
|
||||
.appendingPathComponent(managerRoot)
|
||||
.appendingPathComponent("v24.15.0")
|
||||
.appendingPathComponent(binarySuffix)
|
||||
.appendingPathComponent("node")
|
||||
let searchPaths = CommandResolver.preferredPaths(
|
||||
home: home,
|
||||
current: [unsupported.path],
|
||||
projectRoot: home)
|
||||
|
||||
let result = await RuntimeLocator.resolve(searchPaths: searchPaths)
|
||||
|
||||
guard case let .success(runtime) = result else {
|
||||
Issue.record("A newer unsupported manager runtime hid an installed supported Node: \(result)")
|
||||
return
|
||||
}
|
||||
#expect(runtime.path == expectedNode.path)
|
||||
#expect(runtime.version == RuntimeVersion(major: 24, minor: 15, patch: 0))
|
||||
}
|
||||
|
||||
@Test func `ignores entries without node executable`() throws {
|
||||
let home = try makeTempDirForTests()
|
||||
defer { try? FileManager.default.removeItem(at: home) }
|
||||
let missingNodeBin = home
|
||||
.appendingPathComponent(".local/share/fnm/node-versions/v99.0.0/installation/bin")
|
||||
try FileManager().createDirectory(at: missingNodeBin, withIntermediateDirectories: true)
|
||||
|
||||
let bins = CommandResolver._testNodeManagerBinPaths(home: home)
|
||||
#expect(!bins.contains(missingNodeBin.path))
|
||||
let paths = CommandResolver.preferredPaths(home: home, current: [], projectRoot: home)
|
||||
#expect(!paths.contains(missingNodeBin.path))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user