Improve swap-deps backup handling for multiple swaps - #48
Conversation
When users swap dependencies multiple times without restoring (e.g., swapping from path A to path B), the tool now: - Preserves the original backup (not the intermediate state) - Provides clearer messages about backup status - Detects and warns about inconsistent states This prevents users from losing their original dependency versions when performing multiple swaps in succession. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
|
Note Other AI code review bot(s) detectedCodeRabbit 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. WalkthroughAdds state-aware backup handling to lib/demo_scripts/gem_swapper.rb: backup_file now detects if Gemfile or package.json are already swapped, preserves existing backups when appropriate, warns and raises on inconsistent states, logs backup creation in non-dry runs, and wraps the expanded method with RuboCop complexity disables. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Dev as Developer
participant GS as GemSwapper
participant BF as backup_file
participant FS as Filesystem
Dev->>GS: Run swap operation
GS->>BF: backup_file(target_path, dry_run)
BF->>FS: Check for existing backup file
alt Backup exists
BF->>FS: Inspect current file for swapped markers
alt Already swapped
BF-->>GS: Keep existing backup, log info
else Appears unswapped
BF-->>GS: Log warning + print restore instructions
BF-->>Dev: Raise Error and halt
end
else No backup
BF->>FS: Create backup (unless dry-run)
BF-->>GS: Log backup created
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
⏰ 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)
🔇 Additional comments (2)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
lib/demo_scripts/gem_swapper.rb (1)
739-767: LGTM! Well-designed backup preservation logic with excellent error handling.The implementation correctly addresses the PR objectives:
- Preserves original backup when re-swapping to different paths ✓
- Detects inconsistent state and provides clear remediation steps ✓
- User messaging is helpful and actionable ✓
Minor enhancement: Consider anchoring the regex pattern to avoid matching commented lines.
The gem detection pattern on line 745 could potentially match commented gem lines (e.g.,
# gem 'shakapacker', path: '...'), leading to false positives. For consistency withswap_gem_in_gemfile(line 626) which anchors patterns with^, consider:- gem_pattern = /gem\s+["'](?:shakapacker|react_on_rails|cypress-on-rails)["'],.*(?:path:|github:)/ + gem_pattern = /^\s*gem\s+["'](?:shakapacker|react_on_rails|cypress-on-rails)["'],.*(?:path:|github:)/This anchors to the start of line and matches leading whitespace, preventing false matches in comments. However, this is a low-risk issue since:
- The specific pattern is unlikely to appear in comments
- The worst-case impact is unnecessarily preserving a backup (safe failure mode)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
lib/demo_scripts/gem_swapper.rb(1 hunks)
⏰ 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: claude-review
🔇 Additional comments (2)
lib/demo_scripts/gem_swapper.rb (2)
735-736: MethodLength disable is appropriate.The method complexity increased to handle the new backup preservation logic, which is necessary for the feature. The rubocop disable is justified.
769-774: Good addition of user feedback logging.The log message on line 773 provides clear feedback when a backup is created, improving transparency about what the tool is doing.
|
@claude review this pr |
There was a problem hiding this comment.
Pull Request Overview
Improves dependency swap backup handling to properly manage multiple swaps without restoration, preserving original dependency versions and providing clearer status messages.
- Detects when files are already swapped to prevent redundant backups
- Preserves existing backups and warns about inconsistent states
- Adds confirmation messages for backup creation
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| is_gemfile = file_path.end_with?('Gemfile') | ||
|
|
||
| # Check if file has already been swapped | ||
| gem_pattern = /gem\s+["'](?:shakapacker|react_on_rails|cypress-on-rails)["'],.*(?:path:|github:)/ |
There was a problem hiding this comment.
The hardcoded gem names in the regex make this method tightly coupled to specific gems. Consider extracting these gem names to a constant or configuration to improve maintainability.
| gem_pattern = /gem\s+["'](?:shakapacker|react_on_rails|cypress-on-rails)["'],.*(?:path:|github:)/ | |
| gem_names = NPM_PACKAGE_PATHS.keys.map { |name| Regexp.escape(name) }.join('|') | |
| gem_pattern = Regexp.new("gem\\s+[\"'](?:#{gem_names})[\"'],.*(?:path:|github:)") |
| already_swapped = if is_gemfile | ||
| # Check for path: or github: in Gemfile | ||
| content.match?(gem_pattern) | ||
| else | ||
| # Check for file: in package.json | ||
| content.include?('"file:') | ||
| end |
There was a problem hiding this comment.
The swap detection logic uses different patterns for different file types. Consider extracting this into separate methods (e.g., gemfile_swapped? and package_json_swapped?) to improve readability and testability.
| # Check for file: in package.json | ||
| content.include?('"file:') |
There was a problem hiding this comment.
The package.json swap detection is too broad - it will match any occurrence of '"file:' in the content, potentially including comments or unrelated JSON fields. Consider using a more specific pattern that matches dependency declarations.
| # Check for file: in package.json | |
| content.include?('"file:') | |
| # Check for file: in package.json dependency fields | |
| begin | |
| package = JSON.parse(content) | |
| dep_fields = %w[dependencies devDependencies peerDependencies optionalDependencies bundledDependencies] | |
| already_swapped = dep_fields.any? do |field| | |
| deps = package[field] | |
| next false unless deps.is_a?(Hash) | |
| deps.values.any? { |v| v.is_a?(String) && v.start_with?('file:') } | |
| end | |
| rescue JSON::ParserError | |
| already_swapped = false | |
| end |
|
Claude finished @justin808's task —— View job Code Review: Improve swap-deps backup handling for multiple swapsTasks
SummaryThis PR successfully addresses the problem of confusing backup state when swapping dependencies multiple times. The implementation is well-thought-out with good error handling and user messaging. I found a few issues to address: Issues Found1. Critical: Regex pattern doesn't anchor to line start (lib/demo_scripts/gem_swapper.rb:745)The gem detection pattern can match commented-out gem lines, leading to false positives: gem_pattern = /gem\s+["'](?:shakapacker|react_on_rails|cypress-on-rails)["'],.*(?:path:|github:)/This will match: # gem 'shakapacker', path: '/tmp/test' # ← Commented out but still matchesRecommendation: Anchor to line start and allow for leading whitespace: gem_pattern = /^\s*gem\s+["'](?:shakapacker|react_on_rails|cypress-on-rails)["'],.*(?:path:|github:)/This matches the pattern used in 2. Bug: package.json detection is too broad (lib/demo_scripts/gem_swapper.rb:751)The package.json swap detection will match content.include?('"file:')This could match:
Recommendation: Parse the JSON and check dependency fields specifically: already_swapped = if is_gemfile
content.match?(gem_pattern)
else
# Parse JSON and check dependency fields
begin
data = JSON.parse(content)
dep_types = %w[dependencies devDependencies peerDependencies]
dep_types.any? do |type|
deps = data[type]
deps.is_a?(Hash) && deps.values.any? { |v| v.is_a?(String) && v.start_with?('file:') }
end
rescue JSON::ParserError
false
end
endThis is more robust and matches the approach used in 3. Minor: Hardcoded gem names reduce maintainability (lib/demo_scripts/gem_swapper.rb:745)The regex hardcodes gem names instead of using the gem_pattern = /gem\s+["'](?:shakapacker|react_on_rails|cypress-on-rails)["'],.*(?:path:|github:)/Recommendation: Generate from gem_names = NPM_PACKAGE_PATHS.keys.map { |name| Regexp.escape(name) }.join('|')
gem_pattern = /^\s*gem\s+["'](?:#{gem_names})["'],.*(?:path:|github:)/Positive Aspects✅ Excellent error handling: The inconsistent state detection (lines 759-766) is well-implemented with clear guidance for users ✅ Good user messaging: Clear distinction between "using existing backup" (line 756) vs. warning about inconsistent state ✅ Preserves original backups: Correctly solves the core problem of preserving original dependencies through multiple swaps ✅ Added confirmation logging: The new message on line 773 improves transparency Test Coverage NoteThe PR description mentions manual testing, but consider adding automated tests for:
RecommendationFix issues #1 and #2 (the regex patterns) before merging. Issue #3 is a nice-to-have for maintainability. |
Address three issues identified in code review: 1. Fix regex pattern to anchor to line start (^\s*) to avoid matching commented-out gem lines 2. Fix package.json detection to properly parse JSON and check dependency fields instead of simple string search 3. Use NPM_PACKAGE_PATHS.keys to generate gem names dynamically instead of hardcoding them 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Code Review - PR #48: Improve swap-deps backup handling for multiple swapsSummaryThis PR improves the backup file handling logic to better support multiple sequential swaps without restoring. Overall, the implementation is solid and addresses a real usability issue. Strengths
Code Quality and Best PracticesGood Practices:
Areas for Improvement: Method Complexity (lib/demo_scripts/gem_swapper.rb:743-794) Potential Issues
The check for file: in package.json is too broad - it matches ANY file: dependency, not just managed ones. If projects have other file: deps, it incorrectly detects as swapped. Fix: Check specific package names (shakapacker, react-on-rails, cypress-on-rails). SecurityNo new concerns - Safe file operations, no user input in commands, proper use of File.basename PerformanceMinimal impact - File read only when backup exists Test Coverage MISSINGThe new logic (lines 747-784) lacks unit tests. The comment saying backup_file is simple is now outdated. Recommended tests:
RecommendationsPriority: High
Priority: Medium Priority: Low ConclusionSolid improvement addressing a real issue. Request changes for:
Once addressed, ready to merge! Review by Claude Code |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
lib/demo_scripts/gem_swapper.rb (1)
745-762: Consider extracting swap detection into separate methods.The swap detection logic for Gemfile and package.json could be extracted into dedicated methods (
gemfile_swapped?andpackage_json_swapped?) to improve readability and testability. This would also make it easier to add more sophisticated detection logic in the future.Based on learnings.
Example refactoring:
def gemfile_swapped?(content) gem_names = NPM_PACKAGE_PATHS.keys.map { |name| Regexp.escape(name) }.join('|') gem_pattern = /^\s*gem\s+["'](?:#{gem_names})["'],.*(?:path:|github:)/ content.match?(gem_pattern) end def package_json_swapped?(content) data = JSON.parse(content) dep_types = %w[dependencies devDependencies peerDependencies] managed_npm_names = NPM_PACKAGE_PATHS.select { |_, path| !path.nil? } .keys .map { |name| name.tr('_', '-') } dep_types.any? do |type| deps = data[type] next false unless deps.is_a?(Hash) deps.any? { |pkg, ver| managed_npm_names.include?(pkg) && ver.is_a?(String) && ver.start_with?('file:') } end rescue JSON::ParserError false endThen in
backup_file:already_swapped = if is_gemfile gemfile_swapped?(content) else package_json_swapped?(content) end
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
lib/demo_scripts/gem_swapper.rb(1 hunks)
⏰ 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: claude-review
🔇 Additional comments (4)
lib/demo_scripts/gem_swapper.rb (4)
735-736: LGTM: Complexity metrics disabled appropriately.The RuboCop disables are justified given the added state-detection logic. The method handles multiple scenarios (backup exists + swapped vs unswapped) which increases complexity necessarily.
Also applies to: 786-786
745-749: LGTM: Gemfile swap detection logic is correct.The pattern correctly detects if the file has been modified by checking whether ANY supported gem uses
path:orgithub:. This approach is appropriate because:
- If any supported gem is swapped, the file has been modified from its original state
- The backup should preserve the true original (before any swaps)
- This enables re-swapping to different paths without losing the original
764-776: LGTM: Error handling for inconsistent state is appropriate.The approach of raising an error when a backup exists but the file appears unswapped is a reasonable safety check. The error message provides clear guidance to the user on how to resolve the inconsistency.
783-783: LGTM: Improved user feedback.Adding a log message when creating the backup improves transparency and helps users understand what's happening during the swap operation.
The previous implementation checked for ANY file: dependency in package.json, which incorrectly detected the file as swapped when unrelated file: dependencies existed. Now checks only managed packages (shakapacker, react-on-rails, cypress-on-rails) by converting gem names to npm package names and checking if those specific packages use file: protocol. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Code Review - PR #48: Improve swap-deps backup handling for multiple swapsSummaryThis PR improves the backup file handling logic in the gem_swapper.rb to better handle scenarios where users run swap-deps multiple times with different paths without restoring. The implementation is solid overall with good error handling and clear user messaging. ✅ Code Quality & Best PracticesStrengths:
Minor Suggestions:
🐛 Potential Bugs & Issues1. Regex escaping performance (lib/demo_scripts/gem_swapper.rb:753): 2. JSON parsing edge case (lib/demo_scripts/gem_swapper.rb:774-776): 3. Race condition possibility: ⚡ Performance Considerations
🔒 Security ConcernsNo security issues identified. The code properly:
🧪 Test CoverageStatus: The PR description mentions All existing tests pass and RuboCop passes, but I notice:
📋 Recommendations SummaryHigh Priority:
Medium Priority: Low Priority: ConclusionThis is a well-implemented improvement that addresses a real user pain point. The logic is sound, error handling is appropriate, and user messaging is excellent. With the addition of comprehensive tests for the new scenarios, this will be ready to merge. The main gap is test coverage for the new conditional logic paths. Once tests are added to verify both the preserve existing backup and inconsistent state error scenarios, this PR will be solid. Great work on improving the developer experience! 🎉 Review generated with 🤖 Claude Code |
Summary
Problem
When users run swap-deps multiple times with different paths (e.g., swap to path A, then swap to path B without restoring), the tool was confusing about backup state.
Solution
Test plan
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes