test(mac): cover codesign + node manager paths

This commit is contained in:
Peter Steinberger
2025-12-13 18:08:47 +00:00
parent 56fe23549c
commit 0c8b5ed59a
4 changed files with 204 additions and 3 deletions

View File

@@ -272,7 +272,9 @@ final actor ControlSocketServer {
let sCode = staticCode else { return false }
var infoCF: CFDictionary?
guard SecCodeCopySigningInformation(sCode, SecCSFlags(), &infoCF) == errSecSuccess,
// `kSecCodeInfoTeamIdentifier` is only included when requesting signing information.
let flags = SecCSFlags(rawValue: UInt32(kSecCSSigningInformation))
guard SecCodeCopySigningInformation(sCode, flags, &infoCF) == errSecSuccess,
let info = infoCF as? [String: Any],
let teamID = info[kSecCodeInfoTeamIdentifier as String] as? String
else {
@@ -282,3 +284,28 @@ final actor ControlSocketServer {
return allowedTeamIDs.contains(teamID)
}
}
#if SWIFT_PACKAGE
extension ControlSocketServer {
nonisolated static func _testTeamIdentifier(pid: pid_t) -> String? {
let attrs: NSDictionary = [kSecGuestAttributePid: pid]
var secCode: SecCode?
guard SecCodeCopyGuestWithAttributes(nil, attrs, SecCSFlags(), &secCode) == errSecSuccess,
let code = secCode else { return nil }
var staticCode: SecStaticCode?
guard SecCodeCopyStaticCode(code, SecCSFlags(), &staticCode) == errSecSuccess,
let sCode = staticCode else { return nil }
var infoCF: CFDictionary?
let flags = SecCSFlags(rawValue: UInt32(kSecCSSigningInformation))
guard SecCodeCopySigningInformation(sCode, flags, &infoCF) == errSecSuccess,
let info = infoCF as? [String: Any]
else {
return nil
}
return info[kSecCodeInfoTeamIdentifier as String] as? String
}
}
#endif

View File

@@ -264,19 +264,92 @@ enum CommandResolver {
static func preferredPaths() -> [String] {
let current = ProcessInfo.processInfo.environment["PATH"]?
.split(separator: ":").map(String.init) ?? []
let home = FileManager.default.homeDirectoryForCurrentUser
let projectRoot = self.projectRoot()
return self.preferredPaths(home: home, current: current, projectRoot: projectRoot)
}
static func preferredPaths(home: URL, current: [String], projectRoot: URL) -> [String] {
var extras = [
FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("Library/pnpm").path,
home.appendingPathComponent("Library/pnpm").path,
"/opt/homebrew/bin",
"/usr/local/bin",
"/usr/bin",
"/bin",
]
extras.insert(self.projectRoot().appendingPathComponent("node_modules/.bin").path, at: 0)
extras.insert(projectRoot.appendingPathComponent("node_modules/.bin").path, at: 0)
extras.insert(contentsOf: self.nodeManagerBinPaths(home: home), at: 1)
var seen = Set<String>()
// Preserve order while stripping duplicates so PATH lookups remain deterministic.
return (extras + current).filter { seen.insert($0).inserted }
}
private static func nodeManagerBinPaths(home: URL) -> [String] {
var bins: [String] = []
// Volta
let volta = home.appendingPathComponent(".volta/bin")
if FileManager.default.fileExists(atPath: volta.path) {
bins.append(volta.path)
}
// asdf
let asdf = home.appendingPathComponent(".asdf/shims")
if FileManager.default.fileExists(atPath: asdf.path) {
bins.append(asdf.path)
}
// fnm
bins.append(contentsOf: self.versionedNodeBinPaths(
base: home.appendingPathComponent(".local/share/fnm/node-versions"),
suffix: "installation/bin"))
// nvm
bins.append(contentsOf: self.versionedNodeBinPaths(
base: home.appendingPathComponent(".nvm/versions/node"),
suffix: "bin"))
return bins
}
private static func versionedNodeBinPaths(base: URL, suffix: String) -> [String] {
guard FileManager.default.fileExists(atPath: base.path) else { return [] }
let entries: [String]
do {
entries = try FileManager.default.contentsOfDirectory(atPath: base.path)
} catch {
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
}
var paths: [String] = []
for entry in sorted {
let binDir = base.appendingPathComponent(entry).appendingPathComponent(suffix)
let node = binDir.appendingPathComponent("node")
if FileManager.default.isExecutableFile(atPath: node.path) {
paths.append(binDir.path)
}
}
return paths
}
static func findExecutable(named name: String) -> String? {
for dir in self.preferredPaths() {
let candidate = (dir as NSString).appendingPathComponent(name)
@@ -565,4 +638,10 @@ enum CommandResolver {
}
return URL(fileURLWithPath: expanded)
}
#if SWIFT_PACKAGE
static func _testNodeManagerBinPaths(home: URL) -> [String] {
self.nodeManagerBinPaths(home: home)
}
#endif
}

View File

@@ -0,0 +1,49 @@
import Foundation
import Testing
@testable import Clawdis
@Suite struct ControlSocketServerTests {
private static func codesignTeamIdentifier(executablePath: String) -> String? {
let proc = Process()
proc.executableURL = URL(fileURLWithPath: "/usr/bin/codesign")
proc.arguments = ["-dv", "--verbose=4", executablePath]
proc.standardOutput = Pipe()
let stderr = Pipe()
proc.standardError = stderr
do {
try proc.run()
proc.waitUntilExit()
} catch {
return nil
}
guard proc.terminationStatus == 0 else {
return nil
}
let data = stderr.fileHandleForReading.readDataToEndOfFile()
guard let text = String(data: data, encoding: .utf8) else { return nil }
for line in text.split(separator: "\n") {
if line.hasPrefix("TeamIdentifier=") {
let raw = String(line.dropFirst("TeamIdentifier=".count)).trimmingCharacters(in: .whitespacesAndNewlines)
return raw == "not set" ? nil : raw
}
}
return nil
}
@Test func teamIdentifierLookupMatchesCodesign() async {
let pid = getpid()
let execPath = CommandLine.arguments.first ?? ""
let expected = Self.codesignTeamIdentifier(executablePath: execPath)
let actual = ControlSocketServer._testTeamIdentifier(pid: pid)
if let expected, !expected.isEmpty {
#expect(actual == expected)
} else {
#expect(actual == nil || actual?.isEmpty == true)
}
}
}

View File

@@ -0,0 +1,46 @@
import Foundation
import Testing
@testable import Clawdis
@Suite struct NodeManagerPathsTests {
private func makeTempDir() throws -> URL {
let base = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
let dir = base.appendingPathComponent(UUID().uuidString, isDirectory: true)
try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
return dir
}
private func makeExec(at path: URL) throws {
try FileManager.default.createDirectory(
at: path.deletingLastPathComponent(),
withIntermediateDirectories: true)
FileManager.default.createFile(atPath: path.path, contents: Data("echo ok\n".utf8))
try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: path.path)
}
@Test func fnmNodeBinsPreferNewestInstalledVersion() throws {
let home = try self.makeTempDir()
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 self.makeExec(at: v20Bin)
try self.makeExec(at: v25Bin)
let bins = CommandResolver._testNodeManagerBinPaths(home: home)
#expect(bins.first == v25Bin.deletingLastPathComponent().path)
#expect(bins.contains(v20Bin.deletingLastPathComponent().path))
}
@Test func ignoresEntriesWithoutNodeExecutable() throws {
let home = try self.makeTempDir()
let missingNodeBin = home
.appendingPathComponent(".local/share/fnm/node-versions/v99.0.0/installation/bin")
try FileManager.default.createDirectory(at: missingNodeBin, withIntermediateDirectories: true)
let bins = CommandResolver._testNodeManagerBinPaths(home: home)
#expect(!bins.contains(missingNodeBin.path))
}
}