mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-05-16 19:44:14 +00:00
Fix menubar showing empty data after reboot when CLI is installed via fnm/nvm
Login-item launches don't source .zshrc, leaving version-manager bin directories (fnm, nvm, volta, asdf) absent from PATH. The menubar's augmentedPath only covered /opt/homebrew/bin and /usr/local/bin, so codeburn was never found after a cold reboot. - Add discoverNodeManagerBinDirs() that dynamically scans for fnm, nvm, volta, and asdf installations and adds the latest Node version's bin directory to PATH - Add PATH logging to DataClient spawn error for easier future diagnosis - Log the swallowed error in hydrateCache() catch block so silent cache-empty failures are visible in stderr - Add scripts/diagnose-menubar-cli.sh for testing restricted-PATH CLI execution without rebuilding the menubar app
This commit is contained in:
parent
f5b0ac500f
commit
ab87b61bba
4 changed files with 137 additions and 1 deletions
|
|
@ -58,6 +58,8 @@ struct DataClient {
|
|||
do {
|
||||
try process.run()
|
||||
} catch {
|
||||
let path = ProcessInfo.processInfo.environment["PATH"] ?? "(no PATH)"
|
||||
NSLog("CodeBurn: CLI spawn failed. PATH=%@ error=%@", path, error.localizedDescription)
|
||||
throw DataClientError.spawn(error.localizedDescription)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -59,6 +59,64 @@ enum CodeburnCLI {
|
|||
for extra in additionalPathEntries where !parts.contains(extra) {
|
||||
parts.append(extra)
|
||||
}
|
||||
for dir in discoverNodeManagerBinDirs() where !parts.contains(dir) {
|
||||
parts.append(dir)
|
||||
}
|
||||
return parts.joined(separator: ":")
|
||||
}
|
||||
|
||||
/// Login-item launches don't source .zshrc, so nvm / fnm / volta / asdf bin
|
||||
/// directories are absent from PATH. Scan common version-manager locations
|
||||
/// and add the latest Node version's bin dir so `codeburn` can be found.
|
||||
private static func discoverNodeManagerBinDirs() -> [String] {
|
||||
let home = FileManager.default.homeDirectoryForCurrentUser.path
|
||||
let fm = FileManager.default
|
||||
|
||||
// fnm: ~/.local/share/fnm/node-versions/<version>/installation/bin
|
||||
let fnmVersionsDir = "\(home)/.local/share/fnm/node-versions"
|
||||
if let latest = latestVersionDir(in: fnmVersionsDir) {
|
||||
let binDir = "\(fnmVersionsDir)/\(latest)/installation/bin"
|
||||
if fm.fileExists(atPath: "\(binDir)/node") {
|
||||
return [binDir]
|
||||
}
|
||||
}
|
||||
|
||||
// nvm: ~/.nvm/versions/node/<version>/bin
|
||||
let nvmVersionsDir = "\(home)/.nvm/versions/node"
|
||||
if let latest = latestVersionDir(in: nvmVersionsDir) {
|
||||
let binDir = "\(nvmVersionsDir)/\(latest)/bin"
|
||||
if fm.fileExists(atPath: "\(binDir)/node") {
|
||||
return [binDir]
|
||||
}
|
||||
}
|
||||
|
||||
// volta: ~/.volta/bin (flat, no version dirs)
|
||||
let voltaBin = "\(home)/.volta/bin"
|
||||
if fm.fileExists(atPath: "\(voltaBin)/node") {
|
||||
return [voltaBin]
|
||||
}
|
||||
|
||||
// asdf: ~/.asdf/shims (flat shim dir)
|
||||
let asdfShims = "\(home)/.asdf/shims"
|
||||
if fm.fileExists(atPath: "\(asdfShims)/node") {
|
||||
return [asdfShims]
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
/// Returns the latest version directory name (e.g. "v22.15.0") from a
|
||||
/// parent directory containing version-named subdirectories.
|
||||
private static func latestVersionDir(in parent: String) -> String? {
|
||||
let fm = FileManager.default
|
||||
var isDir: ObjCBool = false
|
||||
guard fm.fileExists(atPath: parent, isDirectory: &isDir), isDir.boolValue,
|
||||
let entries = try? fm.contentsOfDirectory(atPath: parent) else {
|
||||
return nil
|
||||
}
|
||||
return entries
|
||||
.filter { $0.hasPrefix("v") }
|
||||
.sorted()
|
||||
.last
|
||||
}
|
||||
}
|
||||
|
|
|
|||
73
scripts/diagnose-menubar-cli.sh
Executable file
73
scripts/diagnose-menubar-cli.sh
Executable file
|
|
@ -0,0 +1,73 @@
|
|||
#!/bin/bash
|
||||
# Replicates the menubar's restricted PATH environment to test if the CLI
|
||||
# can find and run codeburn with the same PATH the menubar provides.
|
||||
#
|
||||
# The menubar augments PATH with: /opt/homebrew/bin /usr/local/bin
|
||||
# The base PATH for a Login Item is typically: /usr/bin:/bin:/usr/sbin:/sbin
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
RESTRICTED_PATH="/usr/bin:/bin:/usr/sbin:/sbin:/opt/homebrew/bin:/usr/local/bin"
|
||||
|
||||
echo "=== Menubar PATH Diagnostic ==="
|
||||
echo ""
|
||||
echo "Using restricted PATH: $RESTRICTED_PATH"
|
||||
echo ""
|
||||
|
||||
# 1. Check if codeburn is found
|
||||
echo "--- Step 1: Locate codeburn binary ---"
|
||||
FOUND=$(PATH="$RESTRICTED_PATH" /usr/bin/env which codeburn 2>&1 || true)
|
||||
if [ -z "$FOUND" ]; then
|
||||
echo "FAIL: codeburn not found in restricted PATH"
|
||||
echo ""
|
||||
echo "Where codeburn actually is:"
|
||||
/usr/bin/env which -a codeburn 2>/dev/null || echo "(not found anywhere)"
|
||||
echo ""
|
||||
echo "Fix: codeburn is installed outside the menubar's PATH. Options:"
|
||||
echo " 1. Add the install directory to CodeburnCLI.additionalPathEntries"
|
||||
echo " 2. Symlink codeburn into /usr/local/bin"
|
||||
exit 1
|
||||
fi
|
||||
echo "OK: codeburn found at: $FOUND"
|
||||
echo ""
|
||||
|
||||
# 2. Check if node is found (needed for codeburn shell wrapper)
|
||||
echo "--- Step 2: Locate node binary ---"
|
||||
NODE_FOUND=$(PATH="$RESTRICTED_PATH" /usr/bin/env which node 2>&1 || true)
|
||||
if [ -z "$NODE_FOUND" ]; then
|
||||
echo "WARNING: node not found in restricted PATH"
|
||||
echo "This may cause codeburn to fail if it's a shell wrapper."
|
||||
echo ""
|
||||
else
|
||||
echo "OK: node found at: $NODE_FOUND"
|
||||
echo "Node version: $(PATH="$RESTRICTED_PATH" node --version 2>&1 || echo 'failed')"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 3. Run the command the menubar spawns
|
||||
echo "--- Step 3: Run menubar-equivalent CLI command ---"
|
||||
echo "Command: codeburn status --format menubar-json --period today --provider all"
|
||||
echo ""
|
||||
|
||||
STDERR_FILE=$(mktemp)
|
||||
trap 'rm -f "$STDERR_FILE"' EXIT
|
||||
|
||||
if PATH="$RESTRICTED_PATH" /usr/bin/env -- codeburn status --format menubar-json --period today --provider all 2>"$STDERR_FILE"; then
|
||||
echo ""
|
||||
if [ -s "$STDERR_FILE" ]; then
|
||||
echo "Warnings/errors on stderr:"
|
||||
cat "$STDERR_FILE"
|
||||
fi
|
||||
echo ""
|
||||
echo "SUCCESS: CLI ran successfully with restricted PATH."
|
||||
else
|
||||
EXIT_CODE=$?
|
||||
echo ""
|
||||
echo "FAIL: CLI exited with code $EXIT_CODE"
|
||||
if [ -s "$STDERR_FILE" ]; then
|
||||
echo ""
|
||||
echo "Stderr output:"
|
||||
cat "$STDERR_FILE"
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
|
@ -32,7 +32,10 @@ async function hydrateCache() {
|
|||
(range) => parseAllSessions(range, 'all'),
|
||||
aggregateProjectsIntoDays,
|
||||
)
|
||||
} catch {
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err)
|
||||
const stack = err instanceof Error && err.stack ? `\n${err.stack}` : ''
|
||||
process.stderr.write(`codeburn: hydrateCache failed, returning empty cache: ${message}${stack}\n`)
|
||||
return emptyCache()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue