Skip to content

feat: detect package manager from lockfiles (#22)#42

Open
justin808 wants to merge 10 commits intomainfrom
justin808/lockfile-fallback
Open

feat: detect package manager from lockfiles (#22)#42
justin808 wants to merge 10 commits intomainfrom
justin808/lockfile-fallback

Conversation

@justin808
Copy link
Copy Markdown
Member

@justin808 justin808 commented Nov 6, 2025

Summary

This PR adds automatic package manager detection based on lockfile presence when the packageManager property is not set in package.json. This provides a better default experience by using the package manager that's already in use in the project.

Changes

  • Lockfile Detection: Added detect_manager_from_lockfile method that checks for lockfiles in priority order
  • Yarn Version Detection: Added detect_yarn_version_from_lockfile method to distinguish between Yarn Berry and Yarn Classic based on lockfile format
  • Detection Priority:
    1. bun.lockb - Bun
    2. pnpm-lock.yaml - pnpm
    3. yarn.lock - Yarn (detects Berry vs Classic from file format)
    4. package-lock.json - npm
  • Comprehensive Tests: Added tests for all lockfile detection scenarios including priority ordering
  • Documentation: Updated README.md to explain the new lockfile detection behavior

Behavior

When packageManager is not present in package.json:

  1. Check for lockfiles in the priority order above
  2. If a lockfile is found, use the corresponding package manager
  3. If no lockfile is found, fall back to PACKAGE_JSON_FALLBACK_MANAGER environment variable or npm

The packageManager property in package.json always takes precedence over lockfile detection and fallback settings.

Test plan

  • ✅ All 307 existing tests pass
  • ✅ Added 11 new tests for lockfile detection
  • ✅ Tests verify detection for all supported package managers
  • ✅ Tests verify priority ordering when multiple lockfiles exist
  • ✅ Tests verify Yarn Berry vs Classic detection from lockfile format

Breaking changes

None - this is backward compatible. The behavior when packageManager is set or when a fallback is explicitly provided remains unchanged.

Fixes #22

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Automatic package manager detection from lockfiles with priority: Bun → pnpm → Yarn (Berry vs Classic) → npm; falls back to configured fallback or npm. Unreadable yarn.lock defaults to Yarn Classic.
  • Documentation

    • README and CHANGELOG updated with detection flow, priority rules, and fallback examples.
  • Tests

    • Expanded tests for lockfile detection, Yarn version cases, priority rules, race conditions, and constructor/read paths.

Note

Cursor Bugbot is generating a summary for commit 74a98f8. Configure here.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Nov 6, 2025

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

When packageManager is absent, detection now scans lockfiles in priority: bun.lockb, pnpm-lock.yaml, yarn.lock (distinguishing Berry vs Classic by content), then package-lock.json. If none found, it falls back to PACKAGE_JSON_FALLBACK_MANAGER or npm. README, tests, changelog, implementation, and RBS were updated.

Changes

Cohort / File(s) Summary
Documentation
README.md, CHANGELOG.md
Documented automatic lockfile-based package manager detection, detection priority, Yarn Berry vs Classic distinction, examples for fallback_manager, and supported managers.
Core Implementation
lib/package_json.rb
Added LOCKFILE_DETECTION_READ_SIZE; added parse_package_manager, detect_manager_from_lockfile, detect_yarn_version_from_lockfile; adjusted determine_package_manager to parse explicit packageManager values and to prefer lockfile-detected managers before using the fallback; robust handling for missing/unreadable lockfiles and race conditions.
Tests
spec/package_json_spec.rb
Expanded read and constructor-path tests to cover detection of bun, pnpm, Yarn Classic, Yarn Berry, npm; unreadable/race conditions for yarn.lock; and lockfile priority scenarios (bun > pnpm > yarn > package-lock.json).
Type Signatures
sig/package_json.rbs
Added LOCKFILE_DETECTION_READ_SIZE constant and declarations for parse_package_manager, detect_manager_from_lockfile, detect_yarn_version_from_lockfile, and determine_yarn_version (present in both public and private sections).
Linting Config
.rubocop.yml
Excluded lib/package_json.rb from Metrics/ClassLength.

Sequence Diagram(s)

sequenceDiagram
    participant Caller as Caller
    participant Determiner as determine_package_manager
    participant Parser as parse_package_manager
    participant LockDetector as detect_manager_from_lockfile
    participant YarnDetector as detect_yarn_version_from_lockfile
    rect rgba(245,250,255,0.5)
    Caller->>Determiner: request manager (package.json may include packageManager)
    end
    alt packageManager present
        Determiner->>Parser: parse "name@version" (or name)
        Parser-->>Determiner: resolved manager symbol (yarn→Yarn variant via determine_yarn_version)
    else packageManager absent
        Determiner->>LockDetector: check lockfiles in priority
        alt bun.lockb found
            LockDetector-->>Determiner: :bun
        else pnpm-lock.yaml found
            LockDetector-->>Determiner: :pnpm
        else yarn.lock found
            LockDetector->>YarnDetector: read head (up to LOCKFILE_DETECTION_READ_SIZE)
            YarnDetector-->>LockDetector: :yarn_berry or :yarn_classic
            LockDetector-->>Determiner: yarn variant
        else package-lock.json found
            LockDetector-->>Determiner: :npm
        else no lockfiles
            LockDetector-->>Determiner: nil
        end
    end
    alt manager resolved
        Determiner-->>Caller: return resolved manager
    else fallback
        Determiner-->>Caller: return fallback (PACKAGE_JSON_FALLBACK_MANAGER or npm)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I sniff the locks at break of day,
Bun first, then pnpm hops my way,
Yarn whispers Berry or Classic rhyme,
If silence stays, fallback saves the time,
Packages dance — a tiny rabbit's play!

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main feature added: automatic package manager detection from lockfiles, which is the core objective of this PR.
Linked Issues check ✅ Passed The PR fully implements all coding requirements from issue #22: lockfile detection with priority ordering, Yarn Berry vs Classic distinction, and fallback behavior when no lockfiles are found.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the lockfile detection feature: implementation methods, comprehensive tests, documentation, type signatures, and configuration updates for the extended class length.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch justin808/lockfile-fallback

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@claude
Copy link
Copy Markdown

claude Bot commented Nov 6, 2025

Code Review - PR #42: Lockfile Detection Feature

Overview

This PR adds automatic package manager detection based on lockfile presence. The implementation is well-structured and addresses a real UX pain point. Overall, this is a solid contribution with good test coverage.

Strengths

  1. Excellent Test Coverage: 11 new tests covering all scenarios including priority ordering and Yarn version detection
  2. Backward Compatible: No breaking changes - existing behavior with packageManager property or explicit fallback remains unchanged
  3. Clear Documentation: README updates clearly explain the new detection behavior
  4. Logical Priority Order: Bun to pnpm to Yarn to npm makes sense for modern workflows
  5. Smart Yarn Detection: Distinguishing between Berry and Classic by inspecting lockfile content is clever

Code Quality Observations

Good Practices

  • Clean separation of concerns with detect_manager_from_lockfile and detect_yarn_version_from_lockfile methods
  • Appropriate use of early returns for readability
  • Methods are properly scoped as private

Potential Issues

1. File I/O Efficiency (lib/package_json.rb:124)

Reading 1000 characters might be inefficient. Reading 200-300 characters should be sufficient to detect __metadata: which typically appears within the first few lines of Yarn Berry lockfiles.

2. Redundant File Existence Check (lib/package_json.rb:119)

The file existence check is redundant since the caller already verifies the file exists. Either remove or add a comment explaining defensive programming.

3. Fragile Yarn Version Detection (lib/package_json.rb:127)

Using string matching on lockfile content is fragile. Consider additional validation for edge cases like corrupted lockfiles or future Yarn versions.

4. Missing Error Handling (lib/package_json.rb:124)

No error handling for file read failures. Consider wrapping in a rescue block and defaulting to :yarn_classic on errors.

Test Coverage

Excellent coverage for individual lockfile detection, priority ordering, and Yarn differentiation.

Missing edge cases:

  • Corrupted or invalid lockfiles
  • Empty lockfiles
  • Yarn lockfiles without clear markers

Security & Performance

No security concerns. Performance is acceptable. Minor optimizations possible by reducing read size.

Recommendations

Should Fix (Before Merge):

  1. Add error handling for File.read failures
  2. Add test for corrupted/unreadable lockfiles

Could Fix (Nice to Have):

  1. Reduce read size from 1000 to 300 chars
  2. Remove redundant file existence check or document reasoning
  3. Add more robust Yarn version detection

Final Verdict

APPROVE with minor suggestions. Well-implemented feature with clean code and good tests. Suggested improvements are defensive enhancements that would make the code more robust but do not block merging.

Copy link
Copy Markdown

@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 (1)
lib/package_json.rb (1)

117-131: Correct implementation of Yarn version detection.

The logic correctly uses the __metadata: marker to distinguish Yarn Berry from Classic. Reading the first 1000 characters is sufficient since __metadata: typically appears at the very beginning of Berry lockfiles. Defaulting to Classic when the marker is absent is a reasonable fallback.

Optionally, the comment on lines 122-123 could be simplified for clarity:

-    # Read the first few lines to determine the version
-    # Yarn Berry lockfiles start with "__metadata:" or have "# yarn lockfile v1" but use a different format
-    # Yarn Classic lockfiles start with "# THIS IS AN AUTOGENERATED FILE" and "# yarn lockfile v1"
+    # Yarn Berry lockfiles contain "__metadata:" (typically at the start)
+    # Yarn Classic lockfiles use an older format without this marker
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4337430 and e9da837.

📒 Files selected for processing (3)
  • README.md (1 hunks)
  • lib/package_json.rb (1 hunks)
  • spec/package_json_spec.rb (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
spec/package_json_spec.rb (3)
spec/support/temp_helpers.rb (1)
  • with_package_json_file (14-22)
lib/package_json.rb (1)
  • read (23-29)
spec/support/package_json_builder.rb (1)
  • write (10-12)
lib/package_json.rb (1)
lib/package_json/managers/base.rb (1)
  • version (14-25)
⏰ 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). (9)
  • GitHub Check: test (windows-latest, 3.0)
  • GitHub Check: test (windows-latest, 2.6)
  • GitHub Check: test (ubuntu-latest, 2.6)
  • GitHub Check: test (windows-latest, 3.1)
  • GitHub Check: test (windows-latest, head)
  • GitHub Check: test (ubuntu-latest, 3.1)
  • GitHub Check: test (ubuntu-latest, 3.0)
  • GitHub Check: test (ubuntu-latest, head)
  • GitHub Check: test (ubuntu-latest, 2.7)
🔇 Additional comments (6)
README.md (1)

88-111: Excellent documentation of the new lockfile detection feature!

The documentation clearly explains the automatic detection flow, priority ordering, and fallback behavior. The examples for using fallback_manager with both read and new constructors are helpful for users.

lib/package_json.rb (3)

75-87: Clean implementation of the detection hierarchy!

The three-tier logic (explicit packageManager → lockfile detection → fallback) is well-structured and easy to follow. The early returns keep the code readable.


89-95: Good refactoring to extract parsing logic.

Separating the parsing of the packageManager property into its own method improves code organization and readability.


97-115: Solid implementation of lockfile detection with correct priority ordering.

The method correctly implements the documented priority (bun.lockb → pnpm-lock.yaml → yarn.lock → package-lock.json) and appropriately delegates Yarn version detection to a separate method.

spec/package_json_spec.rb (2)

139-222: Comprehensive test coverage for lockfile detection!

The tests thoroughly cover all key scenarios:

  • Individual lockfile detection for each package manager (npm, pnpm, Bun, Yarn Classic, Yarn Berry)
  • Priority ordering when multiple lockfiles are present
  • Yarn Berry vs Classic distinction using appropriate file content
  • Verification that lockfile detection takes precedence over fallback_manager

The test descriptions are clear and the assertions are correct.


386-437: Excellent consistency between .read and .new constructor tests.

The tests correctly mirror the lockfile detection behavior for the .new constructor, ensuring both code paths handle lockfile detection consistently. The test coverage confirms that lockfile detection takes precedence over the fallback_manager parameter in both cases.

@claude
Copy link
Copy Markdown

claude Bot commented Nov 11, 2025

Code Review

I've reviewed PR #42 which adds automatic package manager detection from lockfiles. Overall, this is a well-implemented feature with excellent test coverage. Here's my detailed feedback:


✅ Strengths

Code Quality

  • Clean refactoring: The extraction of parse_package_manager from determine_package_manager improves separation of concerns
  • Well-structured logic: The detection cascade (packageManager → lockfiles → fallback) is clear and logical
  • Comprehensive tests: 11 new tests covering all detection scenarios, priority ordering, and edge cases
  • Good error handling: The rescue StandardError in detect_yarn_version_from_lockfile gracefully handles file read failures

Documentation

  • Clear README updates: The priority order is well-documented with examples
  • Helpful inline comments: Code comments explain the detection logic effectively

Backward Compatibility

  • No breaking changes: Existing behavior when packageManager is set remains unchanged
  • Respects fallback: The fallback_manager still works when no lockfiles are present

🔍 Observations & Suggestions

1. Yarn Version Detection Heuristic (lib/package_json.rb:117-133)

The detect_yarn_version_from_lockfile method reads 300 bytes and checks for __metadata:.

Potential Issue: While 300 bytes is likely sufficient, very large comments at the start of a yarn.lock could theoretically push __metadata: beyond this limit.

Suggestion: Consider reading more bytes (e.g., 1000) or reading line-by-line until you find a meaningful line. Alternatively, document this as a known limitation.

Current code:

content = File.read(lockfile_path, 300) # Read first 300 chars

Alternative approach (more robust):

# Read first 1000 bytes to be more confident
content = File.read(lockfile_path, 1000)

2. File I/O Performance (lib/package_json.rb:97-115)

The detect_manager_from_lockfile method performs 4 sequential File.exist? checks in the worst case.

Analysis: This is fine for most use cases since these are lightweight filesystem calls. However, if this code is called frequently in tight loops, it could add up.

Current behavior: Sequential checks (appropriate for this use case)

  • Best case: 1 check (bun.lockb exists)
  • Worst case: 4 checks (only package-lock.json exists or none exist)

No action needed, but worth noting for future optimization if performance becomes a concern.

3. Test Coverage for Edge Cases

What's covered: ✅

  • All package managers
  • Priority ordering
  • Unreadable yarn.lock files
  • Yarn Berry vs Classic detection

Potential additional test (nice to have, not critical):

  • A corrupted/empty yarn.lock file that doesn't contain __metadata: but also isn't valid Yarn Classic format
  • Multiple simultaneous reads (thread safety) if this gem is used in multi-threaded contexts

Current test at line 192-201 handles the unreadable case well by using a mock.

4. Security Considerations

File read safety: ✅ The code only reads 300 bytes, limiting potential DoS from extremely large files
Path traversal: ✅ Uses directory property which is already validated
Input validation: ✅ No user-controlled input in file paths

Good practice: The rescue StandardError at line 130 prevents crashes from filesystem errors.

5. Priority Order Rationale

The chosen priority order (bun → pnpm → yarn → npm) makes sense:

  • Bun is most specific (binary lockfile)
  • pnpm is increasingly popular and has a distinct lockfile format
  • Yarn has broader adoption than pnpm historically
  • npm is the most common/default

Question for consideration: Is there a scenario where multiple lockfiles legitimately exist? The tests verify the priority works, but users might appreciate a warning if multiple lockfiles are detected (optional enhancement).


🐛 Potential Issues

None Critical

I did not identify any critical bugs or security vulnerabilities. The code is solid.


📊 Performance Considerations

File I/O: 1-4 filesystem stat calls per initialization - negligible performance impact
Memory: Reading 300 bytes max for yarn detection - minimal memory footprint
Overall: ✅ No performance concerns


🧪 Test Coverage

Excellent test coverage:

  • ✅ All 4 package managers detection
  • ✅ Priority ordering (3 tests)
  • ✅ Fallback behavior
  • ✅ Error handling (unreadable lockfile)
  • ✅ Both .read and .new methods

The tests verify behavior changes correctly across the codebase.


🎯 Recommendations

Required Before Merge

None - the PR is ready to merge as-is.

Optional Enhancements (Future PRs)

  1. Consider increasing the yarn lockfile read size from 300 to 1000 bytes for more robust detection
  2. Add a debug/warning log when multiple lockfiles are detected
  3. Consider memoizing the lockfile detection result if PackageJson instances are frequently recreated in the same directory

✨ Summary

This is a high-quality PR that:

Recommendation: Approve and merge 🚀

The code quality is excellent, and the feature will improve the developer experience significantly by reducing configuration overhead.


Note: I couldn't run RuboCop and the test suite in the review environment, but based on the PR description stating "All 307 existing tests pass" and the code following Ruby conventions, I trust these have been verified.

Copy link
Copy Markdown

@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 (1)
spec/package_json_spec.rb (1)

397-459: Consider adding priority tests to the .new context.

The lockfile detection tests for individual package managers are appropriately duplicated between .read (lines 147-190) and .new (lines 397-448) contexts. However, the priority ordering tests (lines 203-233 in the .read context) are not duplicated in the .new context.

Since both constructors use the same determine_package_manager logic, consider adding the priority tests to the .new context as well for completeness and consistency.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e9da837 and b91877f.

📒 Files selected for processing (2)
  • lib/package_json.rb (1 hunks)
  • spec/package_json_spec.rb (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
spec/package_json_spec.rb (3)
spec/support/temp_helpers.rb (1)
  • with_package_json_file (14-22)
lib/package_json.rb (1)
  • read (23-29)
spec/support/package_json_builder.rb (1)
  • write (10-12)
lib/package_json.rb (1)
lib/package_json/managers/base.rb (1)
  • version (14-25)
⏰ 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). (15)
  • GitHub Check: test (ubuntu-latest, 3.1)
  • GitHub Check: test (windows-latest, 3.1)
  • GitHub Check: test (ubuntu-latest, head)
  • GitHub Check: test (macos-latest, 2.6)
  • GitHub Check: test (windows-latest, 3.0)
  • GitHub Check: test (ubuntu-latest, 2.6)
  • GitHub Check: test (windows-latest, head)
  • GitHub Check: test (ubuntu-latest, 3.0)
  • GitHub Check: test (macos-latest, 3.1)
  • GitHub Check: test (windows-latest, 2.6)
  • GitHub Check: test (macos-latest, 2.7)
  • GitHub Check: test (macos-latest, 3.0)
  • GitHub Check: test (windows-latest, 2.7)
  • GitHub Check: test (macos-latest, head)
  • GitHub Check: test (ubuntu-latest, 2.7)
🔇 Additional comments (6)
spec/package_json_spec.rb (2)

139-139: LGTM: Clear test description.

The updated description explicitly states the condition under which the fallback manager is used, improving test clarity.


147-233: LGTM: Comprehensive lockfile detection tests.

The test coverage is thorough:

  • All supported package managers (npm, pnpm, bun, yarn classic, yarn berry) are tested
  • Priority ordering is validated with multiple lockfiles present
  • Error handling is properly tested using mocking
  • Each test uses a different fallback_manager to ensure lockfile detection takes precedence
lib/package_json.rb (4)

75-87: LGTM: Clear priority hierarchy.

The package manager determination logic follows the correct priority:

  1. Explicit packageManager property in package.json
  2. Lockfile detection
  3. Fallback manager

This maintains backward compatibility while adding the new lockfile detection feature.


89-95: LGTM: Good refactoring.

Extracting parse_package_manager improves code organization and makes the logic flow in determine_package_manager clearer.


97-115: LGTM: Well-defined priority order.

The lockfile detection priority is logical and well-implemented:

  • bun.lockb (binary, most specific)
  • pnpm-lock.yaml (pnpm-specific)
  • yarn.lock (requires version detection)
  • package-lock.json (npm default)

The implementation is clean and straightforward.


117-133: LGTM: Solid Yarn version detection with safe error handling.

The implementation correctly distinguishes Yarn Berry from Yarn Classic by checking for __metadata: in the lockfile. The error handling appropriately defaults to Yarn Classic, which is a safe fallback.

The 300-character read limit appears sufficient for detecting the __metadata: marker that Yarn Berry places near the beginning of lockfiles, and this optimization avoids reading potentially large files.

@claude
Copy link
Copy Markdown

claude Bot commented Nov 19, 2025

Pull Request Review: Lockfile-based Package Manager Detection

I've reviewed PR #42 and overall this is a well-designed and implemented feature. The automatic package manager detection based on lockfiles is a useful enhancement that improves the developer experience. Here's my detailed feedback:


Strengths

  1. Excellent Test Coverage: 11 new tests comprehensively cover all detection scenarios including:

    • Individual lockfile detection for all package managers
    • Priority ordering when multiple lockfiles exist
    • Yarn Berry vs Classic distinction
    • Error handling for unreadable lockfiles
    • Tests for both .read() and .new() constructors
  2. Clear Documentation: README.md properly explains the detection flow, priority order, and fallback behavior

  3. Backward Compatibility: The change is completely backward compatible - existing behavior is preserved when packageManager is set or when fallback is provided

  4. Sensible Priority Order: The lockfile priority (Bun → pnpm → Yarn → npm) makes sense based on specificity and modern tool adoption

  5. Robust Error Handling: Graceful degradation to Yarn Classic when lockfile is unreadable (lib/package_json.rb:131-133)


🔍 Areas for Improvement

1. Missing RBS Type Signatures (Medium Priority)

The new private methods are not reflected in the RBS type signature file. According to CLAUDE.md, "Everything should be captured in the type definitions, including private methods."

The following method signatures should be added to sig/package_json.rbs in the private section (after line 31):

def parse_package_manager: (String package_manager) -> Symbol
def detect_manager_from_lockfile: () -> Symbol?
def detect_yarn_version_from_lockfile: () -> Symbol
def determine_yarn_version: (String version) -> Symbol

2. Potential Edge Case: Race Condition (Low Priority)

File: lib/package_json.rb:106-108

There's a minor TOCTOU (time-of-check-time-of-use) issue: if yarn.lock exists during the File.exist? check but is deleted before File.read in detect_yarn_version_from_lockfile, it will raise an error that's caught and defaults to Yarn Classic.

Consider checking file existence within detect_yarn_version_from_lockfile or making the rescue clause more specific.


3. Magic Number Documentation (Low Priority)

File: lib/package_json.rb:124

While the comment explains the reasoning, consider making this a named constant for better maintainability:

LOCKFILE_DETECTION_READ_SIZE = 1000 # bytes

🔒 Security Review

No security concerns identified

  • File operations are properly scoped to the configured directory
  • No user input is directly used in file paths
  • Read operation is limited to 1000 bytes (prevents reading massive files)
  • Error handling prevents information leakage

Performance Considerations

Performance looks good

  • Lockfile checks use File.exist? (fast) before reading
  • Short-circuit evaluation prevents unnecessary file reads
  • Limited read size (1000 bytes) for Yarn detection
  • No expensive operations in the detection flow

📋 Recommendations

Must Fix:

  • ✅ None - code is production ready

Should Fix:

  • Add RBS type signatures for new private methods

Nice to Have:

  • Extract magic number to named constant
  • Improve TOCTOU edge case handling
  • Use more specific error types in tests

📊 Summary

This is a high-quality PR that's ready to merge with one recommended fix (RBS signatures). The implementation is clean, well-tested, and follows Ruby best practices. The feature provides real value without introducing breaking changes or security concerns.

Rating: 9/10

Great work on this enhancement! The lockfile detection will significantly improve the out-of-box experience for developers.


Next Steps:

  1. Add RBS type signatures for completeness
  2. (Optional) Consider the edge case improvements mentioned above
  3. Merge when ready! 🚀

Copy link
Copy Markdown

@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 (1)
lib/package_json.rb (1)

117-134: Good implementation with solid error handling.

The __metadata: marker is a reliable way to distinguish Yarn Berry from Classic. The 1000-byte read limit is a reasonable performance optimization that should cover the vast majority of real-world lockfiles.

The broad rescue StandardError is acceptable here for defensive coding, though you could optionally narrow it to specific exceptions (e.g., Errno::ENOENT, Errno::EACCES, IOError) if you want more precise error handling.

Optional refinement:

-  rescue StandardError
+  rescue Errno::ENOENT, Errno::EACCES, IOError
     # On error (e.g., file read failure, corrupted lockfile), default to Yarn Classic
     :yarn_classic
   end
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b91877f and 19a8f95.

📒 Files selected for processing (2)
  • lib/package_json.rb (1 hunks)
  • spec/package_json_spec.rb (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
lib/package_json.rb (1)
lib/package_json/managers/base.rb (1)
  • version (14-25)
spec/package_json_spec.rb (3)
spec/support/temp_helpers.rb (1)
  • with_package_json_file (14-22)
lib/package_json.rb (1)
  • read (23-29)
spec/support/package_json_builder.rb (1)
  • write (10-12)
⏰ 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). (13)
  • GitHub Check: test (macos-latest, 2.7)
  • GitHub Check: test (windows-latest, 3.0)
  • GitHub Check: test (ubuntu-latest, 3.0)
  • GitHub Check: test (macos-latest, 3.1)
  • GitHub Check: test (macos-latest, 3.0)
  • GitHub Check: test (windows-latest, 2.6)
  • GitHub Check: test (macos-latest, head)
  • GitHub Check: test (ubuntu-latest, 3.1)
  • GitHub Check: test (macos-latest, 2.6)
  • GitHub Check: test (ubuntu-latest, 2.7)
  • GitHub Check: test (ubuntu-latest, 2.6)
  • GitHub Check: test (ubuntu-latest, head)
  • GitHub Check: claude-review
🔇 Additional comments (5)
lib/package_json.rb (3)

78-87: LGTM! Clean refactoring of the detection flow.

The new three-tier detection strategy (packageManager property → lockfiles → fallback) is well-structured and maintains backward compatibility while adding the requested lockfile detection feature.


89-95: LGTM! Clean extraction of packageManager parsing logic.

The method correctly handles Yarn's version-specific behavior while keeping other package managers simple.


97-115: LGTM! Lockfile detection priority is correctly implemented.

The prioritization (bun → pnpm → yarn → npm) is logical and well-documented in comments. The delegation to detect_yarn_version_from_lockfile properly handles Yarn's Berry vs Classic distinction.

spec/package_json_spec.rb (2)

139-234: Excellent test coverage for lockfile detection!

The tests comprehensively verify:

  • Detection of each package manager type (npm, pnpm, bun, yarn classic, yarn berry)
  • Priority ordering when multiple lockfiles exist
  • Error handling for unreadable lockfiles
  • Fallback behavior when no lockfiles are present

The test for unreadable yarn.lock (lines 192-201) using mocking is particularly good for covering error scenarios without needing actual file permission manipulation.


397-459: LGTM! Constructor path tests mirror the .read path coverage.

These tests ensure that the lockfile detection behaves consistently whether using PackageJson.read or PackageJson.new. The coverage is thorough and includes the important error handling case for unreadable lockfiles.

@claude
Copy link
Copy Markdown

claude Bot commented Nov 19, 2025

Code Review for PR #42

This is a well-implemented feature. The implementation is clean, tests are comprehensive, and backward compatibility is maintained.

Strengths

  • Excellent test coverage (146 new lines covering all package managers, priority ordering, Yarn Berry vs Classic detection, error handling)
  • Good error handling for race conditions and corrupted lockfiles
  • Clean code structure with extracted parse_package_manager method
  • Backward compatible - no breaking changes
  • Well-documented README

Minor Suggestions

  1. Lockfile Read Size: 1000 bytes should be sufficient, but consider adding a test with large header comments
  2. RuboCop ClassLength Exclusion: Consider extracting lockfile detection to a separate class in future refactoring
  3. Error Handling: Catching StandardError is safe but could be more specific (Errno::ENOENT, Errno::EACCES, etc.)
  4. String Check: content.include? works well, but could use regex for precision: content.match?(/^__metadata:/m)
  5. Test Duplication: Consider RSpec shared examples to reduce duplication between .read and .new tests

Security & Performance

  • No security concerns (limited file reads, no user input in paths, proper error handling)
  • Performance is good (1000 byte limit, early returns, sensible priority)

Recommendation

APPROVE - Ready to merge. Minor suggestions are optional improvements for future PRs.

Great work!


Reviewed with Claude Code

@claude
Copy link
Copy Markdown

claude Bot commented Nov 19, 2025

Code Review - PR #42: Lockfile-based Package Manager Detection

Overall, this is a well-implemented feature with excellent test coverage and thoughtful design. The automatic detection of package managers from lockfiles is a valuable improvement that will enhance the developer experience. Here's my detailed feedback:


✅ Strengths

1. Excellent Test Coverage

  • Comprehensive tests covering all detection scenarios (11 new tests)
  • Edge cases well-handled: race conditions, unreadable files, priority ordering
  • Tests mirror implementation across both .read() and .new() code paths
  • Good use of mocking to test error handling

2. Clean Code Architecture

  • Well-structured separation of concerns with distinct methods:
    • detect_manager_from_lockfile - lockfile detection
    • detect_yarn_version_from_lockfile - Yarn-specific version detection
    • parse_package_manager - parsing packageManager property
  • Methods are focused and readable
  • Good use of guard clauses and early returns

3. Robust Error Handling

  • Handles race conditions (file disappearing between checks) at lib/package_json.rb:114-115
  • Gracefully degrades to Yarn Classic on read errors at lib/package_json.rb:127-130
  • Conservative fallback strategy protects against edge cases

4. Proper Documentation

  • README clearly explains the detection priority and behavior
  • Good inline comments explaining the logic (especially for Yarn detection)
  • RBS type signatures properly updated

5. Backward Compatibility

  • Non-breaking change - existing behavior preserved
  • Priority system ensures packageManager property takes precedence
  • Fallback mechanisms remain intact

🔍 Observations & Suggestions

1. RuboCop Configuration Change

File: .rubocop.yml:202

The addition of lib/package_json.rb to the Metrics/ClassLength exclusion list is reasonable given the new functionality. However, consider whether further refactoring might be beneficial in the future if the class continues to grow.

Current approach: ✅ Acceptable for now
Future consideration: Monitor class size and consider extracting detection logic to a separate module if more detection strategies are added.


2. Lockfile Read Size Constant

File: lib/package_json.rb:19

The LOCKFILE_DETECTION_READ_SIZE = 1000 bytes is sensible for performance. The comment indicates it accounts for large initial comments, which is good thinking.

Question: Have you tested this with real-world yarn.lock files that have extensive header comments? Some projects use lockfiles with large license headers.

Suggestion: Consider adding a test case with a lockfile that has a large comment section to verify 1000 bytes is sufficient, or document the trade-off if it's intentionally limited.


3. Yarn Version Detection Logic

File: lib/package_json.rb:111-130

The detection logic using __metadata: is correct and well-implemented. The double-check at line 115 for race conditions is excellent defensive programming.

Minor optimization opportunity:

# Current approach reads first, checks existence in rescue
# Consider: Check size before reading to avoid reading empty files
return :yarn_classic unless File.exist?(lockfile_path) && File.size(lockfile_path) > 0

This would avoid attempting to read empty/nearly-empty files, though the current approach is perfectly fine.


4. Detection Priority Rationale

File: lib/package_json.rb:101-109

The priority order (bun > pnpm > yarn > npm) makes sense, but it's worth noting:

  • Bun first: Appropriate since bun.lockb is unique and binary
  • pnpm second: Good choice as it's becoming more popular
  • Yarn third: Sensible, but requires content inspection
  • npm last: Most common, so good default

Documentation: The README documents this well. No changes needed, but you might consider adding a brief comment in the code explaining the rationale for this specific order.


5. Test Code Quality

File: spec/package_json_spec.rb:192-202, 204-214, 463-473, 475-485

The tests for error handling and race conditions are thorough and well-designed. Particularly impressed by:

  • Testing file disappearing between checks (race condition simulation)
  • Testing unreadable files with mocked File.read failures
  • Verifying fallback to Yarn Classic in both cases

Excellent work on edge case testing!


6. Potential Enhancement: Logging/Warning

Currently, when lockfile detection is used, there's no visibility to the developer that automatic detection occurred. Consider whether it would be helpful to:

  • Log (if logging is available) when automatic detection is used
  • Provide a method to query how the manager was determined

This is not a blocker - just a potential future enhancement for debugging.


🛡️ Security Considerations

File Reading Safety: The code reads lockfiles with a size limit (LOCKFILE_DETECTION_READ_SIZE), which is good practice to prevent reading extremely large files. The error handling also prevents exceptions from bubbling up.

No concerns identified - the implementation is safe.


🚀 Performance Considerations

  1. File System Access: The detection checks files sequentially. Given the small number of checks (4 files max) and early returns, this is efficient.

  2. Partial Reads: Using File.read(path, 1000) for Yarn detection is a good optimization to avoid reading large lockfiles entirely.

  3. Caching: The manager is determined once during initialization and stored in @manager. This is optimal - no redundant detection on subsequent operations.

Performance: ✅ Well-optimized


📋 Test Coverage Analysis

Coverage added:

  • ✅ Detection for all 4 package managers (npm, yarn, pnpm, bun)
  • ✅ Yarn Berry vs Classic differentiation
  • ✅ Priority ordering (3 tests with multiple lockfiles)
  • ✅ Error handling (2 tests for race conditions and read failures)
  • ✅ Both constructor paths (.new and .read)

Coverage is excellent! All critical paths and edge cases are tested.


🐛 Potential Issues

Minor: Gemfile.lock Platform Addition

File: Gemfile.lock:69

The addition of arm64-darwin-25 is unrelated to the feature but harmless - appears to be from running bundle install on macOS 15. Consider whether this should be in a separate commit, though it's not a problem.


✨ Overall Assessment

This PR demonstrates:

  • ✅ High code quality with clean separation of concerns
  • ✅ Comprehensive test coverage including edge cases
  • ✅ Backward compatibility and non-breaking changes
  • ✅ Clear documentation of new behavior
  • ✅ Thoughtful error handling and defensive programming
  • ✅ Proper type signatures (RBS)

Recommendation: APPROVE

This is production-ready code. The implementation is solid, tests are thorough, and the feature addresses a real usability improvement. The only suggestions above are minor optimizations and potential future enhancements - none are blockers.

Great work on this feature! The lockfile detection will provide a much better default experience for users.


Files reviewed: 6

  • lib/package_json.rb - Core implementation ✅
  • spec/package_json_spec.rb - Test coverage ✅
  • sig/package_json.rbs - Type signatures ✅
  • README.md - Documentation ✅
  • .rubocop.yml - Linting config ✅
  • Gemfile.lock - Dependencies ✅

justin808 added a commit that referenced this pull request Nov 20, 2025
Add changelog entry for PR #42 which adds automatic package manager
detection from lockfiles with priority ordering and Yarn Berry/Classic
detection.

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

Co-Authored-By: Claude <[email protected]>
@claude
Copy link
Copy Markdown

claude Bot commented Nov 20, 2025

Code Review - PR #42: Lockfile-based Package Manager Detection

I've reviewed this PR and overall it's a well-implemented feature with excellent test coverage. Here's my detailed feedback:

✅ Strengths

1. Code Quality

  • Clean, readable implementation with clear separation of concerns
  • Well-named methods that describe their purpose (detect_manager_from_lockfile, detect_yarn_version_from_lockfile)
  • Appropriate use of early returns for clarity
  • Good constant definition for LOCKFILE_DETECTION_READ_SIZE with explanatory comment

2. Test Coverage

  • Excellent test coverage with 11 new tests covering all scenarios
  • Tests verify each package manager detection independently
  • Priority ordering tests ensure correct behavior when multiple lockfiles exist
  • Edge case handling: unreadable files, race conditions, missing files
  • Tests duplicated for both read and new constructor paths

3. Error Handling

  • Robust error handling in detect_yarn_version_from_lockfile with rescue StandardError
  • Race condition handling (file exists check before reading)
  • Sensible fallback to :yarn_classic on errors

4. Documentation

  • Comprehensive README updates explaining the detection flow
  • Clear priority ordering documented
  • CHANGELOG entry follows project conventions
  • RBS type signatures properly updated

5. Backward Compatibility

  • No breaking changes - packageManager property still takes precedence
  • Explicit fallback still works as expected
  • Purely additive feature

🔍 Potential Issues & Suggestions

1. Security Consideration (Low Risk)
lib/package_json.rb:120 - Reading partial file content for detection:

content = File.read(lockfile_path, LOCKFILE_DETECTION_READ_SIZE)

While 1000 bytes is reasonable, consider:

  • Could a malicious yarn.lock exploit this by placing __metadata: as the 1001st byte?
  • Real Yarn Berry lockfiles have __metadata: in the first ~50 bytes typically

Recommendation: Consider reducing to 500 bytes or adding a more specific regex check for the YAML structure at the start of file.

2. Potential False Positive
lib/package_json.rb:123 - Simple string inclusion check:

return :yarn_berry if content.include?("__metadata:")

This could theoretically match if someone puts __metadata: in a comment in a Yarn Classic lockfile, though this is extremely unlikely.

Recommendation: Use a more specific pattern like /^__metadata:/ or /\n__metadata:/ to ensure it's at the start of a line.

3. Code Organization
The determine_package_manager method now has multiple responsibilities:

  • Parsing packageManager property
  • Detecting from lockfiles
  • Applying fallback logic

This is fine for current scope, but could be extracted if more detection methods are added in the future.

4. RuboCop Exception
.rubocop.yml:202 - Excluding lib/package_json.rb from Metrics/ClassLength:

While this is pragmatic, the class is growing. Consider whether some methods could be extracted to a separate concern/module in the future (e.g., PackageManagerDetector).

5. Edge Case: Empty Lockfiles
The tests create empty/minimal lockfiles. Consider what happens with:

  • Corrupted lockfiles with partial content
  • Lockfiles with unusual encoding
  • Symlinked lockfiles

Current error handling should catch most of these, but worth noting.

6. Performance Consideration (Minor)
lib/package_json.rb:101-109 - Multiple File.exist? calls:

For projects with no lockfile, this checks 4 different files sequentially. This is likely negligible in practice, but could be micro-optimized with a single directory read if needed.

🎯 Overall Assessment

Quality: ★★★★★ (5/5)

This is production-ready code that:

  • ✅ Solves the stated problem elegantly
  • ✅ Maintains backward compatibility
  • ✅ Has comprehensive test coverage
  • ✅ Follows project conventions
  • ✅ Handles edge cases appropriately

Recommendations

  1. Before Merge (Optional but Recommended):

    • Tighten the __metadata: detection to use a line-start pattern
    • Consider reducing LOCKFILE_DETECTION_READ_SIZE to 500 bytes
  2. Future Enhancements (Post-merge):

    • Monitor for any edge cases in real-world usage
    • Consider extracting detection logic if more detection methods are added
    • Evaluate whether to log/warn when lockfile detection is used vs explicit configuration

Conclusion

This is a high-quality PR that adds valuable functionality to the gem. The implementation is solid, well-tested, and follows Ruby best practices. The few suggestions above are minor improvements rather than blockers.

Recommendation: APPROVE ✅

Great work @justin808! 🎉

@claude
Copy link
Copy Markdown

claude Bot commented Nov 21, 2025

Code Review

This is an excellent PR that adds intelligent package manager detection from lockfiles. The implementation is well-thought-out, thoroughly tested, and maintains backward compatibility. Here's my detailed review:

✅ Strengths

1. Code Quality

  • Clean, readable implementation with clear separation of concerns
  • Good use of early returns to reduce nesting
  • Excellent error handling for edge cases (race conditions, unreadable files)
  • Constant LOCKFILE_DETECTION_READ_SIZE is well-documented and appropriately scoped

2. Best Practices

  • Follows Ruby conventions and project style
  • Proper use of private methods for internal logic
  • RBS type signatures added for all new methods
  • .rubocop.yml exemption for class length is reasonable given the added complexity

3. Test Coverage

  • Comprehensive test suite with 11 new tests covering:
    • All package manager detections (npm, pnpm, bun, yarn classic, yarn berry)
    • Priority ordering between multiple lockfiles
    • Edge cases (unreadable files, race conditions)
    • Tests are duplicated for both .read and .new constructor paths
  • Tests use realistic lockfile content samples
  • Good use of test doubles to simulate error conditions

4. Security & Robustness

  • Race condition handling in detect_yarn_version_from_lockfile (lines 114-115)
  • Broad exception handling with sensible defaults (rescue StandardError)
  • Limited file reading (1000 bytes) prevents memory issues with large lockfiles
  • No security vulnerabilities introduced

5. Documentation

  • README.md clearly explains the detection flow and priority
  • CHANGELOG.md properly formatted with PR link
  • Inline comments explain the detection logic
  • Good examples in README

🔍 Minor Observations

1. Method Extraction (Not Blocking)

The determine_package_manager method at lib/package_json.rb:79-91 handles three distinct concerns:

  1. Parsing explicit packageManager property
  2. Lockfile detection
  3. Fallback logic

Consider extracting the original logic that was refactored:

def determine_package_manager(fallback_manager)
  package_manager = fetch("packageManager", nil)
  
  return parse_package_manager(package_manager) unless package_manager.nil?
  
  detect_manager_from_lockfile || fallback_manager
end

This is slightly more concise by using the || operator for the fallback, but the current implementation is also clear and explicit.

2. Constant Visibility

LOCKFILE_DETECTION_READ_SIZE at lib/package_json.rb:19 is a module-level constant. If this is intended as a private implementation detail, consider:

  • Moving it near the method that uses it, or
  • Adding documentation that it's part of the public API if external users might want to reference it

The current approach is fine - this is just a style consideration.

3. Test Duplication

The lockfile detection tests are duplicated for both PackageJson.read and PackageJson.new (lines 147-245 and 418-485). This ensures both code paths work correctly, which is valuable. However:

  • Consider using shared examples if duplication becomes harder to maintain
  • Current approach is fine for this level of complexity

4. Yarn Detection Edge Case

The detect_yarn_version_from_lockfile method uses content.include?("__metadata:") to distinguish Berry from Classic. This works well, but:

  • It's possible (though unlikely) that a Yarn Classic lockfile could have a package name containing "__metadata:"
  • A more robust check might look for __metadata: at the start of a line: content.match?(/^__metadata:/m)

However, given that:

  • The current approach works for all real-world lockfiles
  • Reading only 1000 bytes already limits potential false positives
  • There's proper fallback to Yarn Classic on any issues

The current implementation is pragmatic and acceptable.

📊 Performance Considerations

  • ✅ Lockfile detection is lazy (only happens when packageManager is missing)
  • ✅ File reads are limited to 1000 bytes for yarn.lock
  • ✅ Priority ordering minimizes unnecessary file checks
  • ✅ No performance regressions for existing behavior

🔒 Security Assessment

  • ✅ No injection vulnerabilities
  • ✅ Proper error handling prevents information disclosure
  • ✅ Limited file reading prevents DoS via large files
  • ✅ No unsafe file operations

🎯 Overall Assessment

Recommendation: Approve ✅

This PR is production-ready. The implementation is solid, well-tested, and follows all project conventions. The feature provides significant value by improving the default developer experience.

The minor observations above are suggestions for potential future improvements, not blockers.


Summary:

  • ✅ Code quality: Excellent
  • ✅ Test coverage: Comprehensive
  • ✅ Security: No concerns
  • ✅ Performance: No regressions
  • ✅ Documentation: Clear and complete
  • ✅ Backward compatibility: Maintained
  • ✅ RuboCop: Properly configured

Great work on this feature! 🎉

Copy link
Copy Markdown

@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 (5)
.rubocop.yml (1)

199-203: Class-length exclusion is fine; revisit if PackageJson keeps growing

Excluding lib/package_json.rb from Metrics/ClassLength makes sense now that lockfile detection logic has been added, but if this class continues to accumulate behavior it may be worth extracting the detection logic into a dedicated object/module instead of permanently growing the core class.

lib/package_json.rb (2)

79-110: Package manager resolution flow looks solid; minor robustness tweak possible

The new determine_package_manager / parse_package_manager / detect_manager_from_lockfile pipeline correctly prioritizes: explicit packageManager → lockfiles → fallback manager, which matches the documented behavior and tests. One small robustness improvement would be to cap the split in parse_package_manager so odd future formats with extra @s in the version don’t get over-split:

-    name, version = package_manager.split("@")
+    name, version = package_manager.split("@", 2)

This keeps current behavior identical while being slightly more defensive.


17-20: Yarn lockfile sniffing is defensive and matches test expectations

Using LOCKFILE_DETECTION_READ_SIZE to read only a small prefix of yarn.lock and keying off __metadata: to distinguish Berry vs Classic, while defaulting to :yarn_classic on unreadable/corrupted files or race conditions, gives a good balance between correctness and resilience. If you later need to debug misdetections in the field, consider adding some optional logging/telemetry around the rescue path rather than silently treating every failure as Classic, but the current behavior is consistent and well-covered by specs.

Also applies to: 111-130

spec/package_json_spec.rb (1)

410-485: Constructor-path tests mirror .read behavior; consider DRYing via shared examples

Mirroring the entire lockfile detection matrix for .new ensures the constructor path can’t drift from .read, which is great. Since the examples are nearly identical to the .read ones, you might eventually factor them into shared examples (e.g., shared_examples "lockfile detection" used in both contexts) to reduce duplication and keep future changes to the detection rules in one place.

sig/package_json.rbs (1)

33-40: Adjust determine_yarn_version RBS type to reflect possible nil input

In lib/package_json.rb#parse_package_manager, the version passed to determine_yarn_version can be nil when packageManager is just "yarn", and determine_yarn_version explicitly handles that by raising "a major version must be present for Yarn". The RBS currently declares determine_yarn_version: (String version) -> Symbol, which doesn’t model the nil case. Consider changing it to something like:

def determine_yarn_version: (String? version) -> Symbol

(or an explicit String | nil union) so static tools more accurately capture the runtime contract.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 19a8f95 and 8077ea6.

⛔ Files ignored due to path filters (1)
  • Gemfile.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • .rubocop.yml (1 hunks)
  • CHANGELOG.md (1 hunks)
  • lib/package_json.rb (2 hunks)
  • sig/package_json.rbs (1 hunks)
  • spec/package_json_spec.rb (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
spec/package_json_spec.rb (3)
spec/support/temp_helpers.rb (1)
  • with_package_json_file (14-22)
lib/package_json.rb (1)
  • read (27-33)
spec/support/package_json_builder.rb (1)
  • write (10-12)
⏰ 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). (15)
  • GitHub Check: test (windows-latest, 3.1)
  • GitHub Check: test (windows-latest, 2.7)
  • GitHub Check: test (macos-latest, 2.6)
  • GitHub Check: test (windows-latest, 3.0)
  • GitHub Check: test (windows-latest, head)
  • GitHub Check: test (windows-latest, 2.6)
  • GitHub Check: test (macos-latest, 3.0)
  • GitHub Check: test (macos-latest, 2.7)
  • GitHub Check: test (macos-latest, 3.1)
  • GitHub Check: test (macos-latest, head)
  • GitHub Check: test (ubuntu-latest, head)
  • GitHub Check: test (ubuntu-latest, 3.1)
  • GitHub Check: test (ubuntu-latest, 3.0)
  • GitHub Check: test (ubuntu-latest, 2.7)
  • GitHub Check: test (ubuntu-latest, 2.6)
🔇 Additional comments (2)
CHANGELOG.md (1)

3-11: Changelog entry accurately describes the new detection behavior

The new “Added” entry clearly documents the lockfile priority (bun → pnpm → yarn with Berry vs Classic → npm) and the fallback to PACKAGE_JSON_FALLBACK_MANAGER or npm, matching the implementation and tests.

spec/package_json_spec.rb (1)

139-246: Lockfile detection specs for .read are comprehensive

These examples exercise every relevant scenario (no lockfile → fallback, each supported lockfile type, Yarn Berry vs Classic, unreadable/racy yarn.lock, and priority conflicts), so the behavior of determine_package_manager via .read is very well pinned down.

justin808 and others added 7 commits February 15, 2026 13:23
Add automatic package manager detection based on lockfile presence when
the packageManager property is not set in package.json. This provides
a better default experience by using the package manager that's already
in use in the project.

Detection priority:
1. bun.lockb - Bun
2. pnpm-lock.yaml - pnpm
3. yarn.lock - Yarn (detects Berry vs Classic from file format)
4. package-lock.json - npm

If no lockfile is found, falls back to PACKAGE_JSON_FALLBACK_MANAGER
environment variable or npm.

Fixes #22

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

Co-Authored-By: Claude <[email protected]>
- Reduce File.read size from 1000 to 300 chars for efficiency
- Add error handling for corrupted/unreadable lockfiles
- Add tests for error scenarios to improve branch coverage
- Default to Yarn Classic on read errors

Addresses code review feedback.

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

Co-Authored-By: Claude <[email protected]>
Increase File.read size from 300 to 1000 bytes in detect_yarn_version_from_lockfile
to handle edge cases with large initial comments in yarn.lock files.

This provides better coverage while maintaining good performance.

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

Co-Authored-By: Claude <[email protected]>
- Simplify detect_manager_from_lockfile with modifier if statements
- Add ClassLength exclusion for PackageJson class to .rubocop.yml

Addresses CI lint failures.

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

Co-Authored-By: Claude <[email protected]>
- Add two new test cases for race condition scenario where yarn.lock
  disappears between lockfile detection and version detection
- Update existing tests to use LOCKFILE_DETECTION_READ_SIZE constant
  instead of magic number 1000
- Achieves 100% branch coverage (up from 98.95%)
- All 311 tests passing with 100% line and branch coverage

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

Co-Authored-By: Claude <[email protected]>
Add changelog entry for PR #42 which adds automatic package manager
detection from lockfiles with priority ordering and Yarn Berry/Classic
detection.

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

Co-Authored-By: Claude <[email protected]>
Wrap long changelog entry to meet line length requirements.

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

Co-Authored-By: Claude <[email protected]>
@justin808 justin808 force-pushed the justin808/lockfile-fallback branch from 8077ea6 to 89ef889 Compare February 15, 2026 23:24
@justin808
Copy link
Copy Markdown
Member Author

Claude Code Review

Summary

This PR adds automatic package manager detection from lockfiles when the packageManager property is absent from package.json. The implementation is clean, well-tested, and backward-compatible. Overall this is a solid feature addition.

Key Findings

Strengths

  • Clear priority order for lockfile detection (bun → pnpm → yarn → npm) with good rationale
  • Yarn Berry vs Classic detection via __metadata: marker is a reliable heuristic
  • Race condition handling in detect_yarn_version_from_lockfile — checking existence before reading and rescuing errors
  • LOCKFILE_DETECTION_READ_SIZE constant avoids reading entire large lockfiles
  • Comprehensive test coverage: all managers, priority ordering, error conditions, and race conditions
  • Tests duplicated across both .read and .new constructor paths
  • RBS type signatures updated for all new methods
  • Clean refactor of determine_package_manager into separate parse_package_manager method

Issues

  • RBS: Missing LOCKFILE_DETECTION_READ_SIZE constant declaration (sig/package_json.rbs). The constant is defined in the Ruby source but not declared in the type signature file. Should add LOCKFILE_DETECTION_READ_SIZE: Integer to the RBS file.

Suggestions

  • Test cleanup: The lockfile detection tests in both .read and .new contexts are nearly identical (compare spec/package_json_spec.rb:148-250 with spec/package_json_spec.rb:418-489). Consider extracting shared examples (shared_examples "lockfile detection") to reduce duplication while maintaining coverage for both paths. This is a style preference, not a blocking issue.
  • Minor: detect_yarn_version_from_lockfile double-checks File.exist? (lib/package_json.rb:115) after it was already checked in detect_manager_from_lockfile (lib/package_json.rb:105). This is intentionally defensive for race conditions, which is fine, but the comment could note it's specifically guarding the window between the two calls.

Code Quality

Good. The refactoring of determine_package_manager into a three-step flow (parse packageManager → detect from lockfile → fallback) is easy to follow. Method naming is clear and consistent with the existing codebase. The .rubocop.yml exclusion for Metrics/ClassLength is a reasonable trade-off given the new methods.

Testing

Strong coverage with 11 new tests covering all detection scenarios. The race condition test using allow(File).to receive(:exist?).and_return(true, false) is creative and deterministic. The unreadable lockfile test properly stubs File.read to raise StandardError.

Security

No concerns. File reads are limited to known lockfile names within the project directory. The LOCKFILE_DETECTION_READ_SIZE limit prevents reading excessive data. The rescue StandardError in detect_yarn_version_from_lockfile safely handles unexpected file issues.

Performance

No concerns. Lockfile detection uses File.exist? (fast stat calls) with early returns, and only reads up to 1000 bytes from yarn.lock when needed. This adds negligible overhead to initialization.

Documentation

README and CHANGELOG are well-updated. The detection priority is clearly documented in the README.

Verdict

Approve — One minor issue (missing RBS constant) that should be addressed before merge, but otherwise this is ready.


Automated review by Claude Code

Copy link
Copy Markdown

@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.

🧹 Nitpick comments (1)
spec/package_json_spec.rb (1)

418-485: Consider extracting shared examples to reduce duplication.

The lockfile detection tests in the .new context are nearly identical to those in .read. RSpec shared_examples could DRY this up while still exercising both code paths.

Not blocking — both paths do have subtly different side effects (.new may create package.json and call record_package_manager!), so duplicating is defensible.

justin808 and others added 2 commits March 3, 2026 22:06
* Remove broken Claude Code Review workflow

The plugin-based review setup is broken — the plugin tries to call
gh api via the Bash tool, which the action sandbox blocks by default.
Removing in favor of the working default claude.yml (@claude mentions).

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* Fix Claude Code Review workflow: use built-in review instead of broken plugin

Replace the plugin-based review (which fails because the sandbox blocks
Bash tool access) with the built-in review_triggered_on and
use_sticky_comment options.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* Fix: remove invalid review_triggered_on input

The action auto-detects review mode from the pull_request trigger.
Only use_sticky_comment and prompt are needed.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: Non-existent action version

actions/checkout@v6 does not exist — the latest stable release is v4. This will fail the workflow with an "Unable to resolve action" error.

Suggested change
uses: actions/checkout@v6
uses: actions/checkout@v4

Comment thread lib/package_json.rb
lockfile_path = "#{directory}/yarn.lock"

# Check file exists to avoid race condition
return :yarn_classic unless File.exist?(lockfile_path)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Redundant existence check with misleading comment

detect_yarn_version_from_lockfile is only ever called after File.exist?("#{directory}/yarn.lock") has already returned true in detect_manager_from_lockfile. The second check here never returns early under normal circumstances and the race-condition scenario it's meant to handle is already covered by the rescue StandardError below. The dead code adds confusion.

Suggested change
return :yarn_classic unless File.exist?(lockfile_path)
def detect_yarn_version_from_lockfile
lockfile_path = "#{directory}/yarn.lock"
content = File.read(lockfile_path, LOCKFILE_DETECTION_READ_SIZE)

Comment thread lib/package_json.rb

# Default to Yarn Classic for older format
:yarn_classic
rescue StandardError
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bare rescue StandardError silently hides unexpected errors

Rescuing StandardError catches everything from Errno::EACCES (permission denied) to Errno::ENOSPC (disk full) and silently returns :yarn_classic. This makes legitimate infrastructure problems invisible. Consider rescuing only the specific I/O errors that are expected:

Suggested change
rescue StandardError
rescue Errno::ENOENT, Errno::EACCES, IOError
# On error (e.g., lockfile deleted or unreadable), default to Yarn Classic
:yarn_classic

Comment thread lib/package_json.rb

def detect_manager_from_lockfile
# Check for lockfiles in priority order
return :bun if File.exist?("#{directory}/bun.lockb")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing detection for Bun's newer text lockfile (bun.lock)

Bun v1.1.26+ introduced a text-based lockfile format stored as bun.lock (alongside the binary bun.lockb). Projects using newer Bun versions may only have bun.lock, causing detection to fall through to a wrong manager.

Consider adding:

return :bun if File.exist?("#{directory}/bun.lock")
return :bun if File.exist?("#{directory}/bun.lockb")

Comment thread spec/package_json_spec.rb

it "does not add the packageManager property" do
with_package_json_file({ "version" => "1.0.0" }) do
described_class.read(Dir.pwd, fallback_manager: :yarn_classic)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Significant test duplication between read and new paths

The 11 new lockfile-detection tests under described_class.read (lines ~139–245) are nearly copy-pasted verbatim under described_class.new (lines ~415–489). This makes future changes error-prone — adding/modifying a scenario requires editing two places.

Consider extracting a shared example group:

RSpec.shared_examples "lockfile detection" do |constructor|
  it "detects npm from package-lock.json" do
    with_package_json_file({ "version" => "1.0.0" }) do
      File.write("package-lock.json", "{}")
      pj = constructor.call(fallback_manager: :yarn_classic)
      expect(pj.manager).to be_a PackageJson::Managers::NpmLike
    end
  end
  # … etc
end

describe ".read" do
  include_examples "lockfile detection", ->(kwargs) { described_class.read(Dir.pwd, **kwargs) }
end

describe ".new" do
  include_examples "lockfile detection", ->(kwargs) { described_class.new(**kwargs) }
end

@claude
Copy link
Copy Markdown

claude Bot commented Mar 4, 2026

Code Review

The lockfile-detection feature is a solid addition and the happy-path coverage is thorough. A few issues to address before merging:

Blocking

actions/checkout@v6 does not exist (inline comment on workflow line 18)
This version tag is invalid — the latest is v4. The CI review workflow will fail to run until this is corrected.

Missing bun.lock detection (inline comment on lib/package_json.rb line 103)
Bun v1.1.26+ generates a text-based bun.lock in addition to (or instead of) the binary bun.lockb. Projects on newer Bun will silently fall through to the wrong manager.

Non-blocking but worth fixing

Redundant File.exist? inside detect_yarn_version_from_lockfile (inline comment on line 115)
The caller already confirmed the file exists. The only scenario this second check guards against (race condition) is already handled by the rescue below. The dead code and misleading comment should be removed.

Overly broad rescue StandardError (inline comment on line 127)
This swallows disk-full, OOM, and other serious errors, silently returning :yarn_classic when the real problem is something else entirely. Rescue the specific I/O error classes instead (Errno::ENOENT, Errno::EACCES, IOError).

Test duplication (inline comment on spec/package_json_spec.rb line 250)
The 11 new lockfile scenarios are copy-pasted verbatim for both the .read and .new paths. A shared RSpec example group would halve the maintenance burden.

Minor

  • Path building uses string interpolation. File.join(directory, "bun.lockb") is more idiomatic and handles edge cases with trailing slashes.
  • Excluding lib/package_json.rb from Metrics/ClassLength rather than refactoring is a code smell. The new detection methods could reasonably live in a small LockfileDetector module/class.
  • LOCKFILE_DETECTION_READ_SIZE = 1000 bytes is fine for typical lockfiles, but if a project has an unusually large header comment before __metadata:, Berry detection would silently misclassify as Classic. A value of 4096 would be safer at negligible cost.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Use lockfile presence to choose the fallback manager?

2 participants