From 40acc318304054d2764d9d4c11b07e91240389bc Mon Sep 17 00:00:00 2001 From: Geoff Franks Date: Sat, 25 Jul 2026 20:54:09 +0000 Subject: [PATCH 1/3] Fix CLI version path resolution --- Sources/CodexBarCLI/CLIIO.swift | 27 +++++++++++++++++-- Tests/CodexBarTests/CLIEntryTests.swift | 35 +++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/Sources/CodexBarCLI/CLIIO.swift b/Sources/CodexBarCLI/CLIIO.swift index f9244f1337..2c8f31a9dc 100644 --- a/Sources/CodexBarCLI/CLIIO.swift +++ b/Sources/CodexBarCLI/CLIIO.swift @@ -55,8 +55,9 @@ extension CodexBarCLI { static func currentVersion( bundle: Bundle = .main, - executablePath: String? = CommandLine.arguments.first) -> String? + executablePath: String? = nil) -> String? { + let executablePath = executablePath ?? Self.runningExecutablePath(bundle: bundle) if let version = self.currentVersion(bundleVersion: nil, executablePath: executablePath) { return version } @@ -65,6 +66,28 @@ extension CodexBarCLI { executablePath: nil) } + static func runningExecutablePath(bundle: Bundle = .main) -> String? { + // Bundle.executableURL keeps this seam deterministic for app bundles and tests; the + // platform-specific fallbacks cover direct standalone launches without a usable bundle. + if let path = bundle.executableURL?.path, !path.isEmpty { + return path + } + + #if canImport(Darwin) + var size: UInt32 = 0 + guard _NSGetExecutablePath(nil, &size) != 0 else { return nil } + var buffer = [Int8](repeating: 0, count: Int(size)) + guard _NSGetExecutablePath(&buffer, &size) == 0 else { return nil } + return String(cString: buffer) + #elseif os(Linux) + let path = "/proc/self/exe" + guard FileManager.default.fileExists(atPath: path) else { return nil } + return URL(fileURLWithPath: path).resolvingSymlinksInPath().path + #else + return nil + #endif + } + static func currentVersion(bundleVersion: String?, executablePath: String?) -> String? { if let executablePath, !executablePath.isEmpty { let executableURL = URL(fileURLWithPath: executablePath).resolvingSymlinksInPath() @@ -90,7 +113,7 @@ extension CodexBarCLI { guard let data = fileManager.contents(atPath: infoURL.path), let plist = try? PropertyListSerialization.propertyList(from: data, format: nil) as? [String: Any] else { return nil } - return plist["CFBundleShortVersionString"] as? String + return Self.normalizedBundleVersion(plist["CFBundleShortVersionString"] as? String) } currentURL.deleteLastPathComponent() } diff --git a/Tests/CodexBarTests/CLIEntryTests.swift b/Tests/CodexBarTests/CLIEntryTests.swift index 3e22667461..234dbb880d 100644 --- a/Tests/CodexBarTests/CLIEntryTests.swift +++ b/Tests/CodexBarTests/CLIEntryTests.swift @@ -92,6 +92,41 @@ final class CLIEntryTests: XCTestCase { try self.expectAdjacentVersionFile(raw: "version-3.2.3\n", expected: "version-3.2.3") } + func test_cliVersionUsesBundleExecutableWhenArgvPathIsUnavailable() throws { + let root = FileManager.default.temporaryDirectory + .appendingPathComponent("codexbar-cli-version-bundle-executable-\(UUID().uuidString)", isDirectory: true) + defer { try? FileManager.default.removeItem(at: root) } + + let appURL = root.appendingPathComponent("CodexBar.app", isDirectory: true) + let contentsURL = appURL.appendingPathComponent("Contents", isDirectory: true) + let macOSURL = contentsURL.appendingPathComponent("MacOS", isDirectory: true) + try FileManager.default.createDirectory(at: macOSURL, withIntermediateDirectories: true) + + let infoURL = contentsURL.appendingPathComponent("Info.plist") + let plist: [String: Any] = [ + "CFBundleExecutable": "CodexBarCLI", + "CFBundleIdentifier": "com.example.CodexBarCLI", + "CFBundlePackageType": "APPL", + "CFBundleShortVersionString": "CodexBar", + ] + let data = try PropertyListSerialization.data(fromPropertyList: plist, format: .xml, options: 0) + try data.write(to: infoURL) + + let executableURL = macOSURL.appendingPathComponent("CodexBarCLI") + try Data().write(to: executableURL) + try "8.7.6\n".write( + to: macOSURL.appendingPathComponent("VERSION"), + atomically: false, + encoding: .utf8) + + guard let bundle = Bundle(url: appURL) else { + XCTFail("Expected test app bundle to load") + return + } + + XCTAssertEqual(CodexBarCLI.currentVersion(bundle: bundle, executablePath: nil), "8.7.6") + } + func test_cliVersionPrefersAdjacentVersionOverStandaloneBundleName() throws { let root = FileManager.default.temporaryDirectory .appendingPathComponent("codexbar-cli-version-bundle-\(UUID().uuidString)", isDirectory: true) From cf79447f6df203ed1a2b2ffc0c2be4a68109c348 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 29 Jul 2026 08:52:00 -0700 Subject: [PATCH 2/3] test: cover CLI version invocation paths --- Tests/CodexBarTests/CLIEntryTests.swift | 96 ++++++++++++++++++------- 1 file changed, 71 insertions(+), 25 deletions(-) diff --git a/Tests/CodexBarTests/CLIEntryTests.swift b/Tests/CodexBarTests/CLIEntryTests.swift index 234dbb880d..963a7473c4 100644 --- a/Tests/CodexBarTests/CLIEntryTests.swift +++ b/Tests/CodexBarTests/CLIEntryTests.swift @@ -92,39 +92,43 @@ final class CLIEntryTests: XCTestCase { try self.expectAdjacentVersionFile(raw: "version-3.2.3\n", expected: "version-3.2.3") } - func test_cliVersionUsesBundleExecutableWhenArgvPathIsUnavailable() throws { + func test_cliVersionFindsAdjacentVersionWhenInvokedViaRelativePathAndSymlink() throws { let root = FileManager.default.temporaryDirectory - .appendingPathComponent("codexbar-cli-version-bundle-executable-\(UUID().uuidString)", isDirectory: true) + .appendingPathComponent("codexbar-cli-version-invocation-\(UUID().uuidString)", isDirectory: true) defer { try? FileManager.default.removeItem(at: root) } - let appURL = root.appendingPathComponent("CodexBar.app", isDirectory: true) - let contentsURL = appURL.appendingPathComponent("Contents", isDirectory: true) - let macOSURL = contentsURL.appendingPathComponent("MacOS", isDirectory: true) - try FileManager.default.createDirectory(at: macOSURL, withIntermediateDirectories: true) + let installURL = root.appendingPathComponent("install/bin", isDirectory: true) + let linksURL = root.appendingPathComponent("links", isDirectory: true) + let workingDirectoryURL = root.appendingPathComponent("work", isDirectory: true) + try FileManager.default.createDirectory(at: installURL, withIntermediateDirectories: true) + try FileManager.default.createDirectory(at: linksURL, withIntermediateDirectories: true) + try FileManager.default.createDirectory(at: workingDirectoryURL, withIntermediateDirectories: true) - let infoURL = contentsURL.appendingPathComponent("Info.plist") - let plist: [String: Any] = [ - "CFBundleExecutable": "CodexBarCLI", - "CFBundleIdentifier": "com.example.CodexBarCLI", - "CFBundlePackageType": "APPL", - "CFBundleShortVersionString": "CodexBar", - ] - let data = try PropertyListSerialization.data(fromPropertyList: plist, format: .xml, options: 0) - try data.write(to: infoURL) - - let executableURL = macOSURL.appendingPathComponent("CodexBarCLI") - try Data().write(to: executableURL) + let executableURL = installURL.appendingPathComponent("CodexBarCLI") + try FileManager.default.copyItem(at: Self.cliExecutableURL, to: executableURL) + try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: executableURL.path) try "8.7.6\n".write( - to: macOSURL.appendingPathComponent("VERSION"), + to: installURL.appendingPathComponent("VERSION"), atomically: false, encoding: .utf8) - guard let bundle = Bundle(url: appURL) else { - XCTFail("Expected test app bundle to load") - return - } - - XCTAssertEqual(CodexBarCLI.currentVersion(bundle: bundle, executablePath: nil), "8.7.6") + XCTAssertEqual( + try Self.runVersionCommand( + executableURL: executableURL, + argv0: "install/bin/CodexBarCLI", + currentDirectoryURL: workingDirectoryURL), + "CodexBar 8.7.6\n") + + let symlinkURL = linksURL.appendingPathComponent("codexbar") + try FileManager.default.createSymbolicLink( + atPath: symlinkURL.path, + withDestinationPath: "../install/bin/CodexBarCLI") + XCTAssertEqual( + try Self.runVersionCommand( + executableURL: symlinkURL, + argv0: "codexbar", + currentDirectoryURL: workingDirectoryURL), + "CodexBar 8.7.6\n") } func test_cliVersionPrefersAdjacentVersionOverStandaloneBundleName() throws { @@ -165,6 +169,48 @@ final class CLIEntryTests: XCTestCase { XCTAssertEqual(CodexBarCLI.currentVersion(bundleVersion: nil, executablePath: helperURL.path), expected) } + private static func runVersionCommand( + executableURL: URL, + argv0: String, + currentDirectoryURL: URL) throws -> String + { + let process = Process() + process.executableURL = URL(fileURLWithPath: "/bin/zsh") + process.arguments = [ + "-c", + "exec -a \"$1\" \"$2\" --version", + "codexbar-version-test", + argv0, + executableURL.path, + ] + process.currentDirectoryURL = currentDirectoryURL + + let stdout = Pipe() + let stderr = Pipe() + process.standardOutput = stdout + process.standardError = stderr + try process.run() + process.waitUntilExit() + + let output = stdout.fileHandleForReading.readDataToEndOfFile() + let errorOutput = stderr.fileHandleForReading.readDataToEndOfFile() + guard process.terminationStatus == 0 else { + let message = String(data: errorOutput, encoding: .utf8) ?? "CodexBarCLI exited without an error message" + throw NSError(domain: "CLIEntryTests", code: Int(process.terminationStatus), userInfo: [ + NSLocalizedDescriptionKey: message, + ]) + } + return String(decoding: output, as: UTF8.self) + } + + private static var cliExecutableURL: URL { + URL(fileURLWithPath: #filePath) + .deletingLastPathComponent() + .deletingLastPathComponent() + .deletingLastPathComponent() + .appendingPathComponent(".build/debug/CodexBarCLI") + } + func test_renderOpenAIWebDashboardTextIncludesSummary() { let event = CreditEvent( date: Date(timeIntervalSince1970: 1_700_000_000), From 92f91b21db40fab5b24c4121f87d08374047fc89 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 29 Jul 2026 09:32:40 -0700 Subject: [PATCH 3/3] Use failable UTF-8 conversion in CLI entry test helper --- Tests/CodexBarTests/CLIEntryTests.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Tests/CodexBarTests/CLIEntryTests.swift b/Tests/CodexBarTests/CLIEntryTests.swift index 963a7473c4..6fa60cdd26 100644 --- a/Tests/CodexBarTests/CLIEntryTests.swift +++ b/Tests/CodexBarTests/CLIEntryTests.swift @@ -195,12 +195,18 @@ final class CLIEntryTests: XCTestCase { let output = stdout.fileHandleForReading.readDataToEndOfFile() let errorOutput = stderr.fileHandleForReading.readDataToEndOfFile() guard process.terminationStatus == 0 else { - let message = String(data: errorOutput, encoding: .utf8) ?? "CodexBarCLI exited without an error message" + let message = String(bytes: errorOutput, encoding: .utf8) + ?? "CodexBarCLI exited without an error message" throw NSError(domain: "CLIEntryTests", code: Int(process.terminationStatus), userInfo: [ NSLocalizedDescriptionKey: message, ]) } - return String(decoding: output, as: UTF8.self) + guard let text = String(bytes: output, encoding: .utf8) else { + throw NSError(domain: "CLIEntryTests", code: -1, userInfo: [ + NSLocalizedDescriptionKey: "CodexBarCLI produced non-UTF-8 output", + ]) + } + return text } private static var cliExecutableURL: URL {