Skip to content

fix: add cross-platform Node.js executable finder for desktop launches#208

Merged
webdevcody merged 10 commits intomainfrom
fix/electron-node-path-finder-launch-v2
Dec 22, 2025
Merged

fix: add cross-platform Node.js executable finder for desktop launches#208
webdevcody merged 10 commits intomainfrom
fix/electron-node-path-finder-launch-v2

Conversation

@Shironex
Copy link
Collaborator

@Shironex Shironex commented Dec 21, 2025

Summary

  • Adds findNodeExecutable() and buildEnhancedPath() to @automaker/platform
  • Fixes Electron app failing to start when launched from desktop (Finder, Explorer, etc.)
  • Supports cross-platform: macOS, Windows, and Linux

Problem

When the Electron app is launched from desktop environments (macOS Finder, Windows Explorer, Linux desktop launchers), the PATH environment variable is limited and doesn't include Node.js installation paths. This causes the server spawn to fail.

Solution

New node-finder.ts module in @automaker/platform that:

Platform Searches
macOS Homebrew (Apple Silicon & Intel), system /usr/bin, NVM, fnm
Windows Program Files, nvm-windows, fnm, Scoop, Chocolatey
Linux /usr/bin, /usr/local/bin, Snap, NVM, fnm

Features:

  • Semantic version sorting for version managers (prefers latest)
  • buildEnhancedPath() for ensuring child processes can find Node
  • Falls back to shell resolution (which/where) when available
  • Unit tests included

Test plan

  • Build with npm run build:electron:mac:dir
  • Launch the app from Finder (not terminal)
  • Verify the server starts correctly and the app functions
  • Test on Windows if available

should resolve

✏️ Tip: You can customize this high-level summary in your review settings.

Summary by CodeRabbit

  • New Features

    • Cross-platform Node.js detection with intelligent fallback.
    • Enhanced PATH management to improve startup reliability.
  • Bug Fixes

    • Better startup failure reporting with detailed error dialogs.
    • More robust server health checks and timeout handling.
  • Tests

    • New unit tests covering Node detection and PATH behavior.
  • Documentation

    • Quick Start updated to add a local package build step before running.

✏️ Tip: You can customize this high-level summary in your review settings.

Shironex and others added 2 commits December 21, 2025 14:11
When the Electron app is launched from desktop environments (macOS Finder,
Windows Explorer, Linux desktop launchers), the PATH environment variable
is often limited and doesn't include Node.js installation paths.

This adds a new `findNodeExecutable()` utility to @automaker/platform that:
- Searches common installation paths (Homebrew, system, Program Files)
- Supports version managers: NVM, fnm, nvm-windows, Scoop, Chocolatey
- Falls back to shell resolution (which/where) when available
- Enhances PATH for child processes via `buildEnhancedPath()`
- Works cross-platform: macOS, Windows, and Linux

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 21, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a cross-platform Node.js executable finder and PATH helper in libs/platform, integrates them into the Electron startup to resolve and inject Node into spawned servers, adds tests for the finder, updates README to require building local packages before running, and removes Next.js ambient type declarations in apps/app/next-env.d.ts.

Changes

Cohort / File(s) Summary
Next.js type declarations
apps/app/next-env.d.ts
Removed ambient reference directives (/// <reference types="next" />, /// <reference types="next/image-types/global" />), removed import "./.next/dev/types/routes.d.ts"; and the file header note.
Platform public API
libs/platform/src/index.ts
Exported findNodeExecutable, buildEnhancedPath, and types NodeFinderResult, NodeFinderOptions from the new node-finder module.
Node finder implementation
libs/platform/src/node-finder.ts
New cross-platform Node discovery and PATH helper with platform-specific strategies (macOS, Linux, Windows), version-manager probing, shell fallback, optional logger, and exports findNodeExecutable(options?) and buildEnhancedPath(nodePath, currentPath?).
Node finder tests
libs/platform/tests/node-finder.test.ts
New unit tests covering findNodeExecutable() (skipSearch, logger invocation, result shape and source) and buildEnhancedPath() (prepends, deduplication, empty-path and platform edge cases).
Electron main integration
apps/ui/src/main.ts
Integrated node-finder usage into startup: resolve Node executable, build/enhance PATH for spawned servers, improved error dialogs/logging, consistent IPC/string quoting, and path-resolution refinements.
Docs
README.md
Inserted a pre-run step: “Build local shared packages” (npm run build:packages) before running the app; adjusted step numbering.

Sequence Diagram(s)

sequenceDiagram
    participant Electron as Electron Main
    participant Finder as Node Finder
    participant OS as Operating System / Shell
    participant Server as Backend Server (child)

    Note over Electron,Finder: Startup (dev/prod)
    Electron->>Finder: findNodeExecutable(options?)
    Finder->>Finder: probe Homebrew, system dirs, nvm/fnm, Windows locations
    alt probes fail
        Finder->>OS: run "which" / "where"
        OS-->>Finder: node path or none
    end
    Finder-->>Electron: { nodePath, source }
    Electron->>Finder: buildEnhancedPath(nodePath, process.env.PATH)
    Finder-->>Electron: enhanced PATH
    Electron->>Server: spawn server using nodePath with env.PATH=enhanced
    Server-->>Electron: started / error
    alt server started
        Electron->>Electron: create BrowserWindow
    else startup failed
        Electron->>User: show error dialog
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas needing focused review:
    • libs/platform/src/node-finder.ts — probe ordering, version-manager selection logic, platform path lists, and logger usage.
    • buildEnhancedPath() — PATH delimiter handling, deduplication, and cross-platform edge cases.
    • apps/ui/src/main.ts — correct application of resolved nodePath when spawning child processes and ensuring env propagation is safe.
    • Tests in libs/platform/tests/node-finder.test.ts — portability across CI OSes and assumptions about available system Node installs.

Possibly related PRs

Suggested labels

Tests

Poem

🐰 I hop through PATHs and sniff the trail,
I hunt for Node in brew and nail.
I prepend, tidy, skip the mess,
So child servers wake and windows press.
A happy hop — the app says yes.

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding a cross-platform Node.js executable finder to resolve Electron app startup issues on desktop launches.
Linked Issues check ✅ Passed The PR implements cross-platform Node.js discovery mechanisms that directly address issue #183's root cause (missing NODE in limited desktop environment PATHs), enabling reliable server startup on macOS/Windows/Linux desktop launches.
Out of Scope Changes check ✅ Passed All changes are directly aligned with the PR objective: node-finder module addition, main.ts startup logic updates, README setup clarification, and related tests scope to fixing desktop launch reliability; no unrelated changes detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/electron-node-path-finder-launch-v2

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6e341c1 and e40881e.

📒 Files selected for processing (1)
  • README.md
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: e2e
🔇 Additional comments (1)
README.md (1)

121-127: The npm run build:packages script is correctly defined and will work as intended for users following the Quick Start guide. No issues found.


Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @Shironex, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request delivers a crucial fix for the Electron application's startup reliability by implementing a sophisticated, cross-platform Node.js executable finder. This enhancement ensures that the app's backend server can consistently locate and utilize the Node.js runtime, even when the application is launched from desktop shortcuts where the system's PATH environment variable might be restricted. The solution intelligently searches common installation locations and version managers, significantly improving the user experience by preventing startup failures and providing clearer error messages when issues occur.

Highlights

  • Cross-Platform Node.js Executable Finder: Introduced new functions findNodeExecutable() and buildEnhancedPath() within @automaker/platform to reliably locate the Node.js executable across macOS, Windows, and Linux environments.
  • Electron App Startup Fix: Resolved an issue where the Electron application failed to launch its backend server when started from desktop environments (e.g., Finder, Explorer) due to a limited PATH environment variable.
  • Comprehensive Node.js Search Logic: The new finder supports various Node.js installation methods, including Homebrew, system paths, NVM, fnm, nvm-windows, Program Files, Scoop, and Chocolatey, prioritizing the latest version through semantic sorting.
  • Enhanced PATH for Child Processes: The buildEnhancedPath() utility ensures that child processes spawned by the Electron app inherit an environment PATH that correctly includes the discovered Node.js directory.
  • User Feedback on Startup Failure: Added a user-friendly error dialog that appears if the Electron app fails to start, guiding the user to ensure Node.js is installed and accessible.
  • Code Style Consistency: Updated string literals in apps/ui/src/main.ts from double quotes to single quotes for improved consistency.
  • New Module and Tests: A new module node-finder.ts was added to libs/platform/src, complete with dedicated unit tests in libs/platform/tests/node-finder.test.ts.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@claude

This comment was marked as outdated.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a robust, cross-platform mechanism for locating the Node.js executable, which is a crucial improvement for an Electron application launched from a desktop environment. The implementation is comprehensive, covering various installation methods like Homebrew, NVM, Scoop, and system paths across macOS, Windows, and Linux. The addition of a user-facing error dialog on startup failure is also a great UX enhancement. I've identified a couple of potential bugs in the path-finding logic that could make the solution even more reliable.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
libs/platform/src/node-finder.ts (2)

183-197: Consider refining fnm Windows path lookup.

The fnm_multishells directory (line 187) is used for fnm's shell session shims rather than the actual Node.js installations. While the shim may work, it's less reliable than the canonical installation path. The fallback path on line 189 (~/.fnm/node-versions) is correct.

Consider swapping the order to prioritize the more reliable path:

🔎 Suggested reorder
   // fnm on Windows
   const fnmWindowsPaths = [
+    path.join(homeDir, '.fnm', 'node-versions'),
     path.join(
       process.env.LOCALAPPDATA || path.join(homeDir, 'AppData', 'Local'),
-      'fnm_multishells'
+      'fnm', 'node-versions'
     ),
-    path.join(homeDir, '.fnm', 'node-versions'),
   ];

334-337: Substring match may cause false positives in path deduplication.

The currentPath.includes(nodeDir) check can incorrectly match substrings. For example, if nodeDir is /opt/node and currentPath contains /opt/node-v18/bin, the check would incorrectly return currentPath unchanged.

Consider matching against path segments:

🔎 Suggested fix
   // Don't add if already present or if it's just '.'
-  if (nodeDir === '.' || currentPath.includes(nodeDir)) {
+  const pathSegments = currentPath.split(path.delimiter);
+  if (nodeDir === '.' || pathSegments.includes(nodeDir)) {
     return currentPath;
   }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3b39df4 and 49f04cf.

📒 Files selected for processing (5)
  • apps/app/next-env.d.ts (0 hunks)
  • apps/ui/src/main.ts (15 hunks)
  • libs/platform/src/index.ts (1 hunks)
  • libs/platform/src/node-finder.ts (1 hunks)
  • libs/platform/tests/node-finder.test.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • apps/app/next-env.d.ts
🧰 Additional context used
🧬 Code graph analysis (2)
libs/platform/tests/node-finder.test.ts (1)
libs/platform/src/node-finder.ts (2)
  • findNodeExecutable (263-304)
  • buildEnhancedPath (326-341)
libs/platform/src/node-finder.ts (1)
libs/platform/src/index.ts (4)
  • NodeFinderResult (52-52)
  • NodeFinderOptions (53-53)
  • findNodeExecutable (50-50)
  • buildEnhancedPath (51-51)
🪛 GitHub Actions: Test Suite
libs/platform/tests/node-finder.test.ts

[error] 104-104: AssertionError: expected 'C:\Windows\System32' to be 'C:\Program Files\nodejs:C:\Windows\System32' // Object.is equality

🪛 GitHub Check: test
libs/platform/tests/node-finder.test.ts

[failure] 104-104: tests/node-finder.test.ts > node-finder > buildEnhancedPath > should handle Windows-style paths
AssertionError: expected 'C:\Windows\System32' to be 'C:\Program Files\nodejs:C:\Windows\Sy…' // Object.is equality

Expected: "C:\Program Files\nodejs:C:\Windows\System32"
Received: "C:\Windows\System32"

❯ tests/node-finder.test.ts:104:22

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: e2e
  • GitHub Check: claude-review
🔇 Additional comments (6)
libs/platform/src/index.ts (1)

47-54: LGTM!

The new exports follow the established pattern in the file, correctly using type for type-only exports. This cleanly expands the public API surface with the cross-platform Node.js discovery utilities.

libs/platform/src/node-finder.ts (1)

263-304: LGTM!

The main findNodeExecutable function is well-structured with clear fallback logic. The platform-specific search, shell resolution fallback, and ultimate fallback ensure Node.js can be found in various scenarios.

libs/platform/tests/node-finder.test.ts (2)

6-54: LGTM!

The findNodeExecutable tests cover the key behaviors: skip search mode, logger integration, result structure validation, and actual discovery verification.


56-97: LGTM!

The remaining buildEnhancedPath tests cover the key edge cases well: fallback handling, path prepending, deduplication, and default parameter behavior.

Also applies to: 107-114

apps/ui/src/main.ts (2)

130-179: LGTM!

The integration of findNodeExecutable and buildEnhancedPath is well-implemented:

  • Correctly skips search in dev mode where PATH is typically available
  • Logs the Node.js source for debugging
  • Enhances PATH for child processes to ensure they can also find Node.js

339-346: LGTM!

The error dialog provides a clear, actionable message to users when startup fails, mentioning Node.js installation as a potential fix. This is a significant UX improvement over silent failures.

@Shironex Shironex self-assigned this Dec 21, 2025
@Shironex Shironex added Testers-Requested Request for others to test an enhancement or bug fix/etc. Work-In-Progress Currently being addressed. Do Not Merge Use this label if something should not be merged. labels Dec 21, 2025
- Fix PATH collision detection using proper path segment matching
  instead of substring includes() which could cause false positives
- Reorder fnm Windows paths to prioritize canonical installation path
  over shell shims (fnm_multishells)
- Make Windows path test platform-aware since path.dirname handles
  backslash paths differently on non-Windows systems

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude

This comment was marked as outdated.

- Add path.normalize() for Windows mixed separator handling
- Add validation to check Node executable exists after finding it
- Improve error dialog with specific troubleshooting advice for Node.js
  related errors vs general errors
- Include source info in validation error message

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude

This comment was marked as outdated.

- Extract VERSION_DIR_PATTERN regex to named constant
- Pass logger to findNodeViaShell for consistent debug logging
- Fix buildEnhancedPath to not add trailing delimiter for empty currentPath

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude

This comment was marked as outdated.

- Add null byte validation to shell command output (security hardening)
- Expand VERSION_DIR_PATTERN comment to explain intentional pre-release support

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude

This comment was marked as outdated.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
libs/platform/src/node-finder.ts (2)

70-72: Consider logging errors in the catch block.

The empty catch block silently swallows directory read errors. While this is appropriate for a best-effort search, adding a logger call would improve debuggability.

🔎 Suggested improvement
-  } catch {
-    // Directory read failed, skip this location
+  } catch (error) {
+    // Directory read failed, skip this location - this is expected if path doesn't exist or lacks permissions
   }

Note: If you add a logger parameter to this function, you could also log the error for debugging purposes.


350-353: Consider case-insensitive path comparison on Windows.

On Windows, filesystem paths are case-insensitive, but path.normalize() doesn't change the case of path components. This means C:\Program Files and c:\program files would be treated as different paths here, even though they refer to the same location. In practice, this is unlikely to cause issues since environment variables are typically consistent in casing, but for maximum robustness, you could use case-insensitive comparison on Windows.

🔎 Optional enhancement for Windows
  const normalizedNodeDir = path.normalize(nodeDir);
- const pathSegments = currentPath.split(path.delimiter).map((s) => path.normalize(s));
- if (normalizedNodeDir === '.' || pathSegments.includes(normalizedNodeDir)) {
+ const pathSegments = currentPath.split(path.delimiter).map((s) => path.normalize(s));
+ const compareDir = process.platform === 'win32' ? normalizedNodeDir.toLowerCase() : normalizedNodeDir;
+ const compareSegments = process.platform === 'win32' 
+   ? pathSegments.map(s => s.toLowerCase()) 
+   : pathSegments;
+ if (normalizedNodeDir === '.' || compareSegments.includes(compareDir)) {
    return currentPath;
  }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b18672f and b005681.

📒 Files selected for processing (1)
  • libs/platform/src/node-finder.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
libs/platform/src/node-finder.ts (1)
libs/platform/src/index.ts (4)
  • NodeFinderResult (52-52)
  • NodeFinderOptions (53-53)
  • findNodeExecutable (50-50)
  • buildEnhancedPath (51-51)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: e2e
  • GitHub Check: claude-review
🔇 Additional comments (9)
libs/platform/src/node-finder.ts (9)

1-45: LGTM! Well-structured type definitions and setup.

The imports, constants, and type definitions are clear and appropriate. The VERSION_DIR_PATTERN regex is intentionally permissive as documented, which is suitable for this use case with fallback to semantic version sorting.


77-121: LGTM! macOS search strategy is well-designed.

The search order prioritizes Homebrew installations (with Apple Silicon first), then system Node, then version managers (NVM, fnm). The multiple fnm locations cover different installation scenarios on macOS.


123-162: LGTM! Linux search strategy covers common installation scenarios.

The implementation checks standard system paths including Snap, then version managers. The fnm paths cover typical Linux installation locations.


164-224: Windows search strategy is comprehensive.

The implementation covers major Windows installation methods: Program Files, nvm-windows, fnm, Scoop, and Chocolatey. The use of environment variables with fallbacks is appropriate.


226-256: LGTM! Shell-based fallback is well-implemented.

The implementation correctly handles platform differences (where vs which), includes security validation (null byte check), and gracefully handles errors with logging. Taking the first line from where output is appropriate since it can return multiple paths.


258-317: LGTM! Main function has a well-designed fallback chain.

The implementation provides a robust search strategy with clear fallback levels: platform-specific discovery → shell resolution → ultimate fallback. The skipSearch option and logging throughout are helpful for different deployment scenarios and debugging.


339-362: LGTM! Path deduplication is now robust.

The implementation correctly addresses the past review concern about includes() being unreliable. By normalizing paths and comparing exact path segments (line 352), it avoids false positives like matching /usr/bin against /usr/bin-extra. The handling of edge cases (fallback 'node', empty currentPath, '.' directory) is thorough.


190-205: The fnm path prioritization comment is based on incorrect assumptions about Windows installation behavior.

fnm's default installation directory is $HOME/.fnm, with no Windows-specific override documented in official sources. Evidence shows fnm actually uses %APPDATA%\Roaming\fnm\node-versions on Windows, not %LOCALAPPDATA%. The review's suggestion to prioritize LOCALAPPDATA lacks supporting evidence; neither the current path order nor LOCALAPPDATA reflects fnm's actual Windows behavior.


58-62: Use a proper semver comparison function instead of localeCompare for semantic version sorting.

The code uses localeCompare with numeric: true for "semantic version sort," but this approach cannot correctly handle semantic versioning rules, particularly for pre-release versions. According to the semver specification, pre-release versions (e.g., 1.0.0-alpha, 1.0.0-beta) must sort lower than their release versions, and pre-release identifiers follow complex comparison rules involving numeric vs. non-numeric precedence. localeCompare is a locale-aware string comparison tool and does not parse or understand these semver-specific rules.

Consider using the semver npm package or implementing a custom comparator that properly parses version components to ensure correct sorting for all valid semantic versions.

- Add isExecutable() helper to verify files have execute permission
- On Unix: uses fs.constants.X_OK to check execute permission
- On Windows: only checks file existence (X_OK not meaningful)
- Replace fs.existsSync with isExecutable for all node path checks
- Add JSDoc comment documenting version sorting limitations
- Add test to verify found node binary is executable

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude

This comment was marked as outdated.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
libs/platform/src/node-finder.ts (1)

213-221: fnm path search order contradicts the comment.

The comment states "prioritize canonical installation path over shell shims," but the code checks ~/.fnm/node-versions before %LOCALAPPDATA%/fnm/node-versions. On Windows, the canonical fnm installation location is %LOCALAPPDATA%/fnm, so the search order should be reversed to match the comment.

🔎 Proposed fix to prioritize canonical path
   // fnm on Windows (prioritize canonical installation path over shell shims)
   const fnmWindowsPaths = [
-    path.join(homeDir, '.fnm', 'node-versions'),
     path.join(
       process.env.LOCALAPPDATA || path.join(homeDir, 'AppData', 'Local'),
       'fnm',
       'node-versions'
     ),
+    path.join(homeDir, '.fnm', 'node-versions'),
   ];
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b005681 and 6e341c1.

📒 Files selected for processing (2)
  • libs/platform/src/node-finder.ts (1 hunks)
  • libs/platform/tests/node-finder.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • libs/platform/tests/node-finder.test.ts
🧰 Additional context used
🧬 Code graph analysis (1)
libs/platform/src/node-finder.ts (1)
libs/platform/src/index.ts (4)
  • NodeFinderResult (52-52)
  • NodeFinderOptions (53-53)
  • findNodeExecutable (50-50)
  • buildEnhancedPath (51-51)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: e2e
  • GitHub Check: claude-review
🔇 Additional comments (7)
libs/platform/src/node-finder.ts (7)

20-45: LGTM! Clean interface design.

The interfaces are well-structured and cover all the search strategies comprehensively. The optional skipSearch and logger provide useful flexibility for different execution contexts.


51-64: LGTM! Correct cross-platform executability check.

The platform-specific handling (F_OK for Windows, X_OK for Unix-like systems) is appropriate and well-commented.


103-144: LGTM! Comprehensive macOS Node.js search paths.

The search order (Homebrew → system → version managers) is logical, and the fnm locations cover both XDG and macOS-native installation patterns.


149-185: LGTM! Thorough Linux Node.js search paths.

The inclusion of Snap alongside traditional system paths and version managers provides good coverage for common Linux distributions.


252-279: LGTM! Shell fallback with solid security validation.

The null byte check on line 267 and executable validation provide good defense-in-depth. The error handling and logging make it clear when shell resolution fails (expected in desktop launch scenarios).


299-340: LGTM! Well-structured search orchestration.

The search fallback chain (platform-specific → shell → ultimate fallback) is robust and handles desktop launch scenarios effectively. The logging at each discovery point aids troubleshooting.


362-385: LGTM! PATH deduplication properly addresses previous concerns.

The path normalization and exact segment matching (lines 373-375) correctly handles the false-positive issue raised in previous reviews (e.g., /usr/bin vs /usr/bin-extra). The edge-case handling for '.' and empty currentPath is appropriate.

Shironex and others added 2 commits December 21, 2025 15:24
- Added a step to build local shared packages before running Automaker
- Updated the sequence of instructions for clarity
- Add PRE_RELEASE_PATTERN to identify beta, rc, alpha, nightly, canary, dev, pre versions
- Modify findNodeFromVersionManager to try stable versions first
- Pre-release versions are used as fallback if no stable version found
- Add tests for pre-release detection and version prioritization

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Dec 21, 2025

Code Review: Cross-platform Node.js Executable Finder

Overall Assessment

This is a well-designed solution to a real problem (desktop launcher PATH limitations). The code is clean, well-documented, and follows good practices. The implementation is thorough with excellent cross-platform support.


✅ Strengths

1. Architecture & Design

  • Single Responsibility: node-finder.ts is focused solely on finding Node.js executables
  • Good Abstractions: Platform-specific logic is cleanly separated into findNodeMacOS, findNodeLinux, findNodeWindows
  • Extensibility: Easy to add new package managers or installation paths
  • Proper Type Safety: Well-defined interfaces (NodeFinderResult, NodeFinderOptions)

2. Cross-Platform Coverage

Excellent support across platforms:

  • macOS: Homebrew (both architectures), system, NVM, fnm
  • Windows: Program Files, nvm-windows, fnm, Scoop, Chocolatey
  • Linux: System paths, Snap, NVM, fnm

3. Security Considerations

  • ✅ Null byte validation in findNodeViaShell (line 268)
  • ✅ Executable permission checks via isExecutable
  • ✅ Path normalization in buildEnhancedPath (line 374-376)

4. Code Quality

  • Clear, descriptive variable/function names
  • Comprehensive JSDoc comments
  • Good error handling with try-catch blocks
  • Proper use of constants (no magic strings)

5. Testing

  • ✅ Good test coverage in node-finder.test.ts
  • Tests for version sorting, pre-release filtering, path building
  • Platform-specific test considerations

🔍 Issues & Suggestions

Critical: Semantic Version Sorting Logic

Issue: The version sorting in findNodeFromVersionManager (line 81) may not work correctly for all version formats.

.sort((a, b) => b.localeCompare(a, undefined, { numeric: true, sensitivity: 'base' }));

Problem: localeCompare with numeric: true works for simple cases but can fail with complex semantic versions:

  • v18.2.0 vs v18.10.0 → May incorrectly prefer 18.2.0
  • v2.0.0 vs v20.0.0 → Will correctly prefer 20.0.0

Recommendation: Consider using a proper semver library like semver package for robust version comparison:

import semver from 'semver';

const allVersions = fs
  .readdirSync(basePath)
  .filter((v) => VERSION_DIR_PATTERN.test(v))
  .sort((a, b) => {
    const cleanA = a.replace(/^v/, '');
    const cleanB = b.replace(/^v/, '');
    return semver.rcompare(cleanA, cleanB); // reverse compare (newest first)
  });

Or if avoiding dependencies, implement proper version comparison:

function parseVersion(v: string): number[] {
  return v.replace(/^v/, '').split('.').map(n => parseInt(n) || 0);
}

function compareVersions(a: string, b: string): number {
  const partsA = parseVersion(a);
  const partsB = parseVersion(b);
  for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
    const diff = (partsB[i] || 0) - (partsA[i] || 0);
    if (diff !== 0) return diff;
  }
  return 0;
}

Medium: Error Dialog Message Clarity

Location: apps/ui/src/main.ts:349-355

Current:

dialog.showErrorBox(
  'Automaker Failed to Start',
  `The application failed to start.\n\n${errorMessage}\n\n${
    isNodeError
      ? 'Please install Node.js from https://nodejs.org or via a package manager (Homebrew, nvm, fnm).'
      : 'Please check the application logs for more details.'
  }`
);

Issue: The isNodeError detection is simplistic (just checking if error message includes "Node.js"). Could miss errors or misclassify them.

Suggestion: Make error detection more robust:

const isNodeError = errorMessage.includes('Node.js') || 
                    errorMessage.includes('node executable') ||
                    errorMessage.includes('ENOENT') && errorMessage.includes('node');

Low: Missing Edge Case Handling

1. Symlink Resolution
If Node.js is symlinked, the found path might be a symlink. Consider resolving to real path:

function isExecutable(filePath: string): boolean {
  try {
    // Resolve symlinks
    const realPath = fs.realpathSync(filePath);
    if (process.platform === 'win32') {
      fs.accessSync(realPath, fs.constants.F_OK);
    } else {
      fs.accessSync(realPath, fs.constants.X_OK);
    }
    return true;
  } catch {
    return false;
  }
}

2. PATH Manipulation Edge Case
In buildEnhancedPath, the check normalizedNodeDir === '.' handles the current directory case, but what about . appearing in PATH segments? Consider:

if (normalizedNodeDir === '.' || normalizedNodeDir === '' || pathSegments.includes(normalizedNodeDir)) {
  return currentPath;
}

Low: Code Style Consistency

Quote Style Change: The PR changes quote style from double to single quotes throughout main.ts. While this is fine, it should be:

  1. ✅ Consistent with project conventions (check if .prettierrc or ESLint config defines this)
  2. ⚠️ Consider doing quote style changes in a separate commit to make the functional changes clearer

Low: Documentation

Missing:

  • No README or documentation update explaining when/how this feature is used
  • The PR description is excellent, but consider adding a comment in main.ts explaining why findNodeExecutable is needed

Suggestion: Add a comment above the usage in main.ts:131-136:

/**
 * Find Node.js executable for server spawn.
 * When launched from desktop (Finder/Explorer), PATH is limited and may not include
 * Node.js installation paths. This searches common installation locations.
 * In dev mode, we skip the search since PATH is already correct when launched from terminal.
 */
const nodeResult = findNodeExecutable({
  skipSearch: isDev,
  logger: (msg) => console.log(`[Electron] ${msg}`),
});

🧪 Testing Recommendations

Current Tests

  • Good unit test coverage for version sorting
  • PATH building logic tested
  • Platform-specific considerations included

Missing Tests ⚠️

  1. Integration test: Actually spawning a process with the found Node.js
  2. Mock filesystem tests: Test behavior when specific paths exist/don't exist
  3. Windows path normalization: More edge cases for mixed separators
  4. Symlink handling: If you implement symlink resolution

Test Suggestion:

it('should handle version directories with different formats', () => {
  const versions = ['v18.2.0', 'v18.10.0', 'v20.0.0', '19.0.0', 'v16.20.2'];
  // Test that sorting produces expected order
});

🔒 Security Review

✅ Good Security Practices

  • Null byte checking in shell output
  • Executable permission validation
  • Path normalization before comparison
  • No arbitrary command execution
  • Proper use of execSync with stdio configuration

⚠️ Minor Security Consideration

The execSync in findNodeViaShell suppresses stderr. While safe here, consider logging it:

const result = execSync(command, {
  encoding: 'utf8',
  stdio: ['pipe', 'pipe', 'pipe'],
}).trim();

Could become:

try {
  const result = execSync(command, {
    encoding: 'utf8',
    // Don't suppress stderr in case there's useful diagnostic info
  }).trim();
  // ... rest of code
} catch (error) {
  logger(`Shell command failed: ${error.message}`);
}

📊 Performance Considerations

Current Approach: Sequential Search ✅

The implementation searches locations sequentially, which is appropriate for this use case:

  • Only runs once at startup
  • Fails fast (returns as soon as found)
  • No significant performance impact

Potential Optimization (NOT recommended for this PR):

If startup time becomes critical, could parallelize searches, but current approach is cleaner and easier to debug.


✏️ Minor Code Quality Issues

1. Duplicate Conditional Logic (apps/ui/src/main.ts:42-46)

if (process.platform === 'win32') {
  iconFile = 'icon.ico';
} else if (process.platform === 'darwin') {
  iconFile = 'logo_larger.png';
} else {
  iconFile = 'logo_larger.png';
}

Suggestion:

iconFile = process.platform === 'win32' ? 'icon.ico' : 'logo_larger.png';

2. Silent Error Handling

Multiple try-catch blocks that silently ignore errors (lines 94-96, 274-277). While appropriate here, consider if any should log warnings.


🎯 Recommendations Summary

Must Fix (Before Merge):

  1. ⚠️ Verify version sorting - Test with actual version directories or use semver

Should Fix:

  1. Improve error detection in dialog message
  2. Add inline documentation explaining why this is needed

Nice to Have:

  1. Resolve symlinks in isExecutable
  2. Separate quote style changes to different commit
  3. Add integration tests

✅ Approval Recommendation

Conditional Approval - Approve pending verification of version sorting logic. The code is well-written and solves a real problem. The main concern is ensuring version sorting works correctly across all edge cases.

Final Checklist:

  • Test version sorting with actual NVM/fnm directory structures
  • Verify on all three platforms (macOS, Windows, Linux)
  • Confirm quote style change aligns with project conventions
  • Consider adding a brief comment in main.ts explaining the need

Great work overall! This is a thoughtful solution to a tricky platform-specific problem. 🚀

@Shironex Shironex added Enhancement Improvements to existing functionality or UI. and removed Work-In-Progress Currently being addressed. labels Dec 21, 2025
@webdevcody webdevcody merged commit f45ba5a into main Dec 22, 2025
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Do Not Merge Use this label if something should not be merged. Enhancement Improvements to existing functionality or UI. Testers-Requested Request for others to test an enhancement or bug fix/etc.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Apple could not verify “Automaker” is free of malware that may harm your Mac or compromise your privacy.

2 participants