From 055defc552c90cdb0bc39adcc6c4167f4cac0743 Mon Sep 17 00:00:00 2001 From: Vaibhav Arora Date: Fri, 15 May 2026 18:14:28 +0530 Subject: [PATCH] fix: cache menubar PATH discovery and test nvm lookup Avoid repeated filesystem scans by caching discovered user PATH entries once, and add deterministic tests for PATH augmentation and nvm executable discovery behavior. Co-authored-by: Cursor --- .../Security/CodeburnCLI.swift | 59 +++++++++++++++++-- .../CodeburnCLITests.swift | 43 ++++++++++++++ 2 files changed, 98 insertions(+), 4 deletions(-) create mode 100644 mac/Tests/CodeBurnMenubarTests/CodeburnCLITests.swift diff --git a/mac/Sources/CodeBurnMenubar/Security/CodeburnCLI.swift b/mac/Sources/CodeBurnMenubar/Security/CodeburnCLI.swift index 4f4a5f8..f57f2fb 100644 --- a/mac/Sources/CodeBurnMenubar/Security/CodeburnCLI.swift +++ b/mac/Sources/CodeBurnMenubar/Security/CodeburnCLI.swift @@ -13,6 +13,7 @@ enum CodeburnCLI { /// PATH additions for GUI-launched apps, which otherwise get a minimal PATH that misses /// Homebrew and npm global installs. private static let additionalPathEntries = ["/opt/homebrew/bin", "/usr/local/bin"] + private static let cachedDiscoveredUserPathEntries = discoverUserPathEntries() /// Returns the argv that launches the CLI. Dev override via `CODEBURN_BIN` is honoured only /// if every whitespace-delimited token passes `safeArgPattern`. Otherwise falls back to the @@ -36,11 +37,14 @@ enum CodeburnCLI { let process = Process() process.executableURL = URL(fileURLWithPath: "/usr/bin/env") var environment = ProcessInfo.processInfo.environment - environment["PATH"] = augmentedPath(environment["PATH"] ?? "") + let existingPath = environment["PATH"] ?? "" + let combinedPath = augmentedPath(existingPath) + environment["PATH"] = combinedPath process.environment = environment // `env --` treats everything following as argv, not VAR=val pairs -- guards against an // argument accidentally resembling an env assignment. - process.arguments = ["--"] + baseArgv() + subcommand + let argv = ["--"] + baseArgv() + subcommand + process.arguments = argv // The menubar runs as an accessory app with no foreground window, and macOS // background-throttles accessory apps and their children. Without this lift the // codeburn subprocess parses 5-10x slower than the same command run from a @@ -54,11 +58,58 @@ enum CodeburnCLI { return safeArgPattern.firstMatch(in: s, range: range) != nil } - private static func augmentedPath(_ existing: String) -> String { + static func augmentedPath(existing: String, discoveredUserPathEntries: [String]) -> String { var parts = existing.split(separator: ":", omittingEmptySubsequences: true).map(String.init) - for extra in additionalPathEntries where !parts.contains(extra) { + let dynamicEntries = additionalPathEntries + discoveredUserPathEntries + for extra in dynamicEntries where !parts.contains(extra) { parts.append(extra) } return parts.joined(separator: ":") } + + private static func augmentedPath(_ existing: String) -> String { + augmentedPath(existing: existing, discoveredUserPathEntries: cachedDiscoveredUserPathEntries) + } + + private static func discoverUserPathEntries() -> [String] { + let fileManager = FileManager.default + return discoverUserPathEntries( + homeDirectory: NSHomeDirectory(), + fileExists: { fileManager.fileExists(atPath: $0) }, + isExecutableFile: { fileManager.isExecutableFile(atPath: $0) }, + contentsOfDirectory: { try? fileManager.contentsOfDirectory(atPath: $0) } + ) + } + + static func discoverUserPathEntries( + homeDirectory: String, + fileExists: (String) -> Bool, + isExecutableFile: (String) -> Bool, + contentsOfDirectory: (String) -> [String]? + ) -> [String] { + var entries: [String] = [] + + let staticUserCandidates = [ + "\(homeDirectory)/.volta/bin", + "\(homeDirectory)/.npm-global/bin", + "\(homeDirectory)/.asdf/shims", + ] + for candidate in staticUserCandidates where fileExists(candidate) { + entries.append(candidate) + } + + let nvmRoot = "\(homeDirectory)/.nvm/versions/node" + if let nodeVersions = contentsOfDirectory(nvmRoot) { + // Prefer newer versions first to match typical `nvm current` semantics. + for version in nodeVersions.sorted(by: >) { + let binPath = "\(nvmRoot)/\(version)/bin" + let codeburnPath = "\(binPath)/codeburn" + if isExecutableFile(codeburnPath) { + entries.append(binPath) + } + } + } + + return entries + } } diff --git a/mac/Tests/CodeBurnMenubarTests/CodeburnCLITests.swift b/mac/Tests/CodeBurnMenubarTests/CodeburnCLITests.swift new file mode 100644 index 0000000..239c474 --- /dev/null +++ b/mac/Tests/CodeBurnMenubarTests/CodeburnCLITests.swift @@ -0,0 +1,43 @@ +import Testing +@testable import CodeBurnMenubar + +@Suite("CodeburnCLI PATH handling") +struct CodeburnCLITests { + @Test("discovers static user bins and executable nvm bins") + func discoversExpectedUserPathEntries() { + let home = "/Users/tester" + let existingDirs: Set = [ + "\(home)/.volta/bin", + "\(home)/.asdf/shims", + ] + let executableFiles: Set = [ + "\(home)/.nvm/versions/node/v22.3.1/bin/codeburn", + ] + + let entries = CodeburnCLI.discoverUserPathEntries( + homeDirectory: home, + fileExists: { existingDirs.contains($0) }, + isExecutableFile: { executableFiles.contains($0) }, + contentsOfDirectory: { path in + guard path == "\(home)/.nvm/versions/node" else { return nil } + return ["v20.10.0", "v22.3.1"] + } + ) + + #expect(entries == [ + "\(home)/.volta/bin", + "\(home)/.asdf/shims", + "\(home)/.nvm/versions/node/v22.3.1/bin", + ]) + } + + @Test("augmented path appends missing entries once") + func augmentedPathAppendsWithoutDuplicates() { + let augmented = CodeburnCLI.augmentedPath( + existing: "/usr/bin:/opt/homebrew/bin:/custom/bin", + discoveredUserPathEntries: ["/custom/bin", "/Users/tester/.volta/bin"] + ) + + #expect(augmented == "/usr/bin:/opt/homebrew/bin:/custom/bin:/usr/local/bin:/Users/tester/.volta/bin") + } +}