diff --git a/swap-shakacode-deps-implementation-plan.md b/swap-shakacode-deps-implementation-plan.md new file mode 100644 index 0000000..834afeb --- /dev/null +++ b/swap-shakacode-deps-implementation-plan.md @@ -0,0 +1,184 @@ +# swap-shakacode-deps Implementation Plan + +## Executive Summary + +Create a globally installable Ruby gem `swap-shakacode-deps` that provides the dependency swapping functionality currently in `react_on_rails_demo_common`, making it available for use in any repository. + +## Architecture Overview + +### Package Type: Ruby Gem +- **Rationale**: Ruby ecosystem provides better file manipulation, process management, and cross-platform compatibility +- **Global Installation**: `gem install swap-shakacode-deps` +- **Command**: `swap-shakacode-deps [options]` + +## Core Features (Maintaining Full Parity) + +1. **Local Path Swapping** + - `--shakapacker PATH` + - `--react-on-rails PATH` + - `--cypress-on-rails PATH` + +2. **GitHub Repository Swapping** + - `--github user/repo#branch` + - `--github user/repo@tag` + +3. **Configuration File Support** + - `.swap-deps.yml` in project root + - `--apply` to use config file + +4. **Backup & Restore** + - Automatic backup creation + - `--restore` to revert changes + +5. **Build Management** + - `--build` / `--skip-build` + - `--watch` for auto-rebuild + +6. **Watch Process Management** + - `--list-watch` + - `--kill-watch` + +7. **Cache Management** + - `--show-cache` + - `--clean-cache [GEM]` + +8. **Status Reporting** + - `--status` to show current swaps + +9. **Dry Run & Verbose Modes** + - `--dry-run` + - `--verbose` + +## File Structure + +``` +swap-shakacode-deps/ +├── bin/ +│ └── swap-shakacode-deps # Executable +├── lib/ +│ ├── swap_shakacode_deps.rb # Main entry +│ ├── swap_shakacode_deps/ +│ │ ├── version.rb +│ │ ├── cli.rb # CLI parser +│ │ ├── swapper.rb # Core swapping logic +│ │ ├── gem_swapper.rb # Gemfile manipulation +│ │ ├── npm_swapper.rb # package.json manipulation +│ │ ├── github_handler.rb # GitHub repo management +│ │ ├── cache_manager.rb # Cache operations +│ │ ├── watch_manager.rb # Watch process management +│ │ ├── backup_manager.rb # Backup/restore logic +│ │ └── config_loader.rb # YAML config handling +├── spec/ # Tests +├── README.md +├── CHANGELOG.md +├── LICENSE +├── Gemfile +├── Rakefile +└── swap-shakacode-deps.gemspec +``` + +## Key Implementation Details + +### 1. Context Detection +The gem will detect project type by looking for: +- `Gemfile` (Ruby project) +- `package.json` (Node project) +- `.swap-deps.yml` (Configuration file) + +### 2. Multi-Project Support +Unlike the current implementation that works with `demos/` directories, the global tool will: +- Work in the current directory by default +- Support `--path` option to specify target directory +- Support `--recursive` to process subdirectories + +### 3. Improved Error Handling +- Clear error messages for missing dependencies +- Validation before making changes +- Rollback on partial failures + +### 4. Platform Compatibility +- macOS (primary) +- Linux +- Windows (WSL) + +## Migration Strategy + +### Phase 1: Gem Development +1. Extract core logic from `demo_scripts` +2. Remove demo-specific assumptions +3. Generalize for any project structure + +### Phase 2: Integration +1. Create gem with full feature parity +2. Test with various project types +3. Publish to RubyGems + +### Phase 3: Update react_on_rails_demo_common +1. Add gem as dependency +2. Create wrapper script that delegates to gem +3. Maintain backward compatibility + +## Installation & Usage + +### Installation +```bash +# Global installation +gem install swap-shakacode-deps + +# Or add to Gemfile for project-specific use +gem 'swap-shakacode-deps' +``` + +### Basic Usage +```bash +# Swap to local shakapacker +swap-shakacode-deps --shakapacker ~/dev/shakapacker + +# Use GitHub branch +swap-shakacode-deps --github shakacode/react_on_rails#feature-x + +# Apply from config +swap-shakacode-deps --apply + +# Restore originals +swap-shakacode-deps --restore +``` + +## Configuration File Format + +```yaml +# .swap-deps.yml +gems: + shakapacker: ~/dev/shakapacker + react_on_rails: ~/dev/react_on_rails + +github: + shakapacker: + repo: shakacode/shakapacker + branch: main +``` + +## Benefits Over Current Implementation + +1. **Global Availability**: Use in any project, not just react_on_rails_demo_common +2. **Simplified Maintenance**: Single source of truth for the tool +3. **Better Testing**: Isolated gem with its own test suite +4. **Version Management**: Semantic versioning for the tool +5. **Documentation**: Dedicated docs for the tool +6. **Community Contribution**: Easier for others to contribute + +## Timeline Estimate + +- **Week 1**: Core gem structure and logic extraction +- **Week 2**: Feature implementation and testing +- **Week 3**: Documentation and publishing +- **Week 4**: Integration with react_on_rails_demo_common + +## Success Criteria + +1. All current swap-deps features work globally +2. No breaking changes for existing users +3. Clear upgrade path +4. Comprehensive documentation +5. Published to RubyGems +6. Works with any Shakacode project diff --git a/swap-shakacode-deps/.gitignore b/swap-shakacode-deps/.gitignore new file mode 100644 index 0000000..85b8e34 --- /dev/null +++ b/swap-shakacode-deps/.gitignore @@ -0,0 +1,70 @@ +*.gem +*.rbc +/.config +/coverage/ +/InstalledFiles +/pkg/ +/spec/reports/ +/spec/examples.txt +/test/tmp/ +/test/version_tmp/ +/tmp/ + +# Used by dotenv library to load environment variables +# .env + +# Ignore Byebug command history file +.byebug_history + +## Specific to RubyMotion: +.dat* +.repl_history +build/ +*.bridgesupport +build-iPhoneOS/ +build-iPhoneSimulator/ + +## Specific to RubyMotion (use of CocoaPods): +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# vendor/Pods/ + +## Documentation cache and generated files: +/.yardoc/ +/_yardoc/ +/doc/ +/rdoc/ + +## Environment normalization: +/.bundle/ +/vendor/bundle +/lib/bundler/man/ + +# for a library or gem, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# Gemfile.lock +# .ruby-version +# .ruby-gemset + +# unless supporting rvm < 1.11.0 or doing something fancy, ignore this: +.rvmrc + +# Used by RuboCop. Remote config files pulled in from inherit_from directive. +# .rubocop-https?--* + +# RSpec artifacts +/spec/support/fixtures/ +/spec/tmp/ + +# Ignore IDE files +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# OS files +.DS_Store +Thumbs.db \ No newline at end of file diff --git a/swap-shakacode-deps/.rubocop.yml b/swap-shakacode-deps/.rubocop.yml new file mode 100644 index 0000000..d489379 --- /dev/null +++ b/swap-shakacode-deps/.rubocop.yml @@ -0,0 +1,30 @@ +require: + - rubocop-rspec + +AllCops: + TargetRubyVersion: 2.7 + NewCops: enable + Exclude: + - 'bin/console' + - 'vendor/**/*' + - 'tmp/**/*' + +Style/Documentation: + Enabled: false + +Metrics/MethodLength: + Max: 20 + +Metrics/ClassLength: + Max: 150 + +Metrics/BlockLength: + Exclude: + - 'spec/**/*' + - '*.gemspec' + +RSpec/ExampleLength: + Max: 15 + +RSpec/MultipleExpectations: + Max: 5 diff --git a/swap-shakacode-deps/.swap-deps.yml.example b/swap-shakacode-deps/.swap-deps.yml.example new file mode 100644 index 0000000..259a2ee --- /dev/null +++ b/swap-shakacode-deps/.swap-deps.yml.example @@ -0,0 +1,36 @@ +# Example configuration file for swap-shakacode-deps +# Copy this file to .swap-deps.yml and customize for your environment +# The .swap-deps.yml file should be git-ignored for local development + +# Local gem paths +# These paths will be used when running: swap-shakacode-deps --apply +gems: + # Path to your local shakapacker repository + shakapacker: ~/dev/shakapacker + + # Path to your local react_on_rails repository + react_on_rails: ~/dev/react_on_rails + + # Path to your local cypress-on-rails repository (if used) + # cypress-on-rails: ~/dev/cypress-on-rails + +# GitHub repositories +# Use these for testing specific branches or tags +# Uncomment and modify as needed +# github: +# shakapacker: +# repo: shakacode/shakapacker +# branch: main # or use a specific branch like 'feature-xyz' +# +# react_on_rails: +# repo: shakacode/react_on_rails +# branch: main # or use a tag with ref_type: tag +# +# # Example using a tag instead of branch +# # cypress-on-rails: +# # repo: shakacode/cypress-on-rails +# # branch: v1.0.0 +# # ref_type: tag + +# Note: You can mix local paths and GitHub repos +# Local paths take precedence when both are specified \ No newline at end of file diff --git a/swap-shakacode-deps/CHANGELOG.md b/swap-shakacode-deps/CHANGELOG.md new file mode 100644 index 0000000..3228988 --- /dev/null +++ b/swap-shakacode-deps/CHANGELOG.md @@ -0,0 +1,31 @@ +# Changelog + +All notable changes to swap-shakacode-deps will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added +- Initial release of swap-shakacode-deps +- Support for swapping shakapacker, react_on_rails, and cypress-on-rails gems +- Local path swapping with `--shakapacker`, `--react-on-rails`, `--cypress-on-rails` options +- GitHub repository support with branches and tags via `--github` option +- Configuration file support via `.swap-deps.yml` and `--apply` option +- Backup and restore functionality with `--restore` option +- NPM package building with `--build` and `--skip-build` options +- Watch mode for auto-rebuilding with `--watch` option +- Watch process management with `--list-watch` and `--kill-watch` options +- Cache management with `--show-cache` and `--clean-cache` options +- Status reporting with `--status` option +- Dry-run mode with `--dry-run` option +- Verbose output with `--verbose` option +- Support for processing specific directories with `--path` option +- Recursive directory processing with `--recursive` option +- Comprehensive error handling and validation +- Automatic backup file creation +- File locking for atomic operations +- Cross-platform compatibility (macOS, Linux, Windows via WSL) + +[Unreleased]: https://github.com/shakacode/swap-shakacode-deps diff --git a/swap-shakacode-deps/Gemfile b/swap-shakacode-deps/Gemfile new file mode 100644 index 0000000..177f1e2 --- /dev/null +++ b/swap-shakacode-deps/Gemfile @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +# Specify gem dependencies in swap-shakacode-deps.gemspec +gemspec \ No newline at end of file diff --git a/swap-shakacode-deps/Gemfile.lock b/swap-shakacode-deps/Gemfile.lock new file mode 100644 index 0000000..57fb885 --- /dev/null +++ b/swap-shakacode-deps/Gemfile.lock @@ -0,0 +1,80 @@ +PATH + remote: . + specs: + swap-shakacode-deps (0.1.0) + json (~> 2.0) + +GEM + remote: https://rubygems.org/ + specs: + ast (2.4.3) + diff-lcs (1.6.2) + json (2.15.0) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + parallel (1.27.0) + parser (3.3.9.0) + ast (~> 2.4.1) + racc + prism (1.5.1) + racc (1.8.1) + rainbow (3.1.1) + rake (13.3.0) + regexp_parser (2.11.3) + rspec (3.13.1) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-core (3.13.5) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.5) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.5) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-support (3.13.6) + rubocop (1.81.1) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.47.1, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.47.1) + parser (>= 3.3.7.2) + prism (~> 1.4) + rubocop-capybara (2.20.0) + rubocop (~> 1.41) + rubocop-factory_bot (2.25.1) + rubocop (~> 1.41) + rubocop-rspec (2.29.2) + rubocop (~> 1.40) + rubocop-capybara (~> 2.17) + rubocop-factory_bot (~> 2.22) + rubocop-rspec_rails (~> 2.28) + rubocop-rspec_rails (2.28.3) + rubocop (~> 1.40) + ruby-progressbar (1.13.0) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.1.0) + +PLATFORMS + arm64-darwin-24 + ruby + +DEPENDENCIES + bundler (~> 2.0) + rake (~> 13.0) + rspec (~> 3.0) + rubocop (~> 1.50) + rubocop-rspec (~> 2.22) + swap-shakacode-deps! + +BUNDLED WITH + 2.6.5 diff --git a/swap-shakacode-deps/LICENSE b/swap-shakacode-deps/LICENSE new file mode 100644 index 0000000..802f88c --- /dev/null +++ b/swap-shakacode-deps/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 ShakaCode + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/swap-shakacode-deps/README.md b/swap-shakacode-deps/README.md new file mode 100644 index 0000000..8e63b03 --- /dev/null +++ b/swap-shakacode-deps/README.md @@ -0,0 +1,286 @@ +# swap-shakacode-deps + +A powerful command-line tool for swapping Shakacode gem dependencies between production versions and local development paths or GitHub branches. Perfect for developing and testing changes across multiple Shakacode libraries simultaneously. + +## Features + +- 🔄 **Swap Dependencies**: Switch between production gems and local development versions +- 🐙 **GitHub Support**: Use branches or tags directly from GitHub repositories +- 📦 **NPM Package Support**: Automatically handles npm packages within Ruby gems +- 🔨 **Auto-Build**: Build npm packages automatically with optional watch mode +- 💾 **Backup & Restore**: Safely backup and restore original dependencies +- ⚙️ **Configuration Files**: Use `.swap-deps.yml` for repeatable setups +- 🔍 **Status Tracking**: View currently swapped dependencies +- 🧹 **Cache Management**: Manage cached GitHub repositories + +## Installation + +### Global Installation (Recommended) + +```bash +gem install swap-shakacode-deps +``` + +### Project-Specific Installation + +Add to your `Gemfile`: + +```ruby +gem 'swap-shakacode-deps', group: :development +``` + +Then run: + +```bash +bundle install +``` + +## Quick Start + +### Swap to Local Development Version + +```bash +# Swap react_on_rails to local development version +swap-shakacode-deps --react-on-rails ~/dev/react_on_rails + +# Swap multiple gems at once +swap-shakacode-deps --shakapacker ~/dev/shakapacker \ + --react-on-rails ~/dev/react_on_rails +``` + +### Use GitHub Branches or Tags + +```bash +# Use a specific branch +swap-shakacode-deps --github shakacode/shakapacker#feature-branch + +# Use a specific tag +swap-shakacode-deps --github shakacode/react_on_rails@v14.0.0 + +# Mix local and GitHub sources +swap-shakacode-deps --shakapacker ~/dev/shakapacker \ + --github shakacode/react_on_rails#main +``` + +### Restore Original Dependencies + +```bash +swap-shakacode-deps --restore +``` + +## Supported Gems + +- **shakapacker**: The Shakacode fork of Webpacker +- **react_on_rails**: Integration of React with Rails +- **cypress-on-rails**: Cypress testing integration for Rails + +## Configuration File + +Create a `.swap-deps.yml` file in your project root for repeatable configurations: + +```yaml +# .swap-deps.yml +gems: + shakapacker: ~/dev/shakapacker + react_on_rails: ~/dev/react_on_rails + cypress-on-rails: ~/dev/cypress-on-rails + +github: + shakapacker: + repo: shakacode/shakapacker + branch: main + react_on_rails: + repo: shakacode/react_on_rails + branch: feature-x +``` + +Then apply the configuration: + +```bash +swap-shakacode-deps --apply +``` + +## Command-Line Options + +### Gem Selection + +| Option | Description | +|--------|-------------| +| `--shakapacker PATH` | Path to local shakapacker repository | +| `--react-on-rails PATH` | Path to local react_on_rails repository | +| `--cypress-on-rails PATH` | Path to local cypress-on-rails repository | +| `--github REPO[#BRANCH\|@TAG]` | Use GitHub repository with optional branch or tag | + +### Configuration + +| Option | Description | +|--------|-------------| +| `--apply` | Apply dependencies from `.swap-deps.yml` | +| `--restore` | Restore original dependencies from backups | +| `--path DIR` | Target directory (default: current directory) | +| `--recursive` | Process all subdirectories with Gemfiles | + +### Build Options + +| Option | Description | +|--------|-------------| +| `--build` | Build npm packages (default behavior) | +| `--skip-build` | Skip building npm packages | +| `--watch` | Run npm packages in watch mode for auto-rebuild | + +### Watch Process Management + +| Option | Description | +|--------|-------------| +| `--list-watch` | List all tracked watch processes | +| `--kill-watch` | Stop all tracked watch processes | + +### Cache Management + +| Option | Description | +|--------|-------------| +| `--show-cache` | Display cache information and size | +| `--clean-cache [GEM]` | Clean cached repositories (all or specific gem) | + +### Status and Debugging + +| Option | Description | +|--------|-------------| +| `--status` | Show current swapped dependencies status | +| `--dry-run` | Preview changes without modifying files | +| `--verbose` | Show detailed output | +| `--help` | Display help message | + +## Examples + +### Development Workflow + +1. **Start development with local gems:** + ```bash + swap-shakacode-deps --shakapacker ~/dev/shakapacker --watch + ``` + This swaps to your local shakapacker and starts watch mode for auto-rebuilding. + +2. **Check status:** + ```bash + swap-shakacode-deps --status + ``` + +3. **Make changes in your local gem repository** + The watch mode automatically rebuilds when you save changes. + +4. **Restore when done:** + ```bash + swap-shakacode-deps --restore + ``` + +### Testing GitHub Branches + +```bash +# Test a PR branch +swap-shakacode-deps --github shakacode/react_on_rails#pr-1234 + +# Run your tests +bundle exec rspec + +# Restore when done +swap-shakacode-deps --restore +``` + +### Working with Multiple Projects + +```bash +# Process a specific directory +swap-shakacode-deps --path ~/projects/my-app --react-on-rails ~/dev/react_on_rails + +# Process all Rails apps in a directory +swap-shakacode-deps --path ~/projects --recursive --apply +``` + +### Managing Watch Processes + +```bash +# Start watch mode +swap-shakacode-deps --shakapacker ~/dev/shakapacker --watch + +# List running watch processes +swap-shakacode-deps --list-watch + +# Stop all watch processes +swap-shakacode-deps --kill-watch +``` + +### Cache Management + +```bash +# Show cache information +swap-shakacode-deps --show-cache + +# Clean all cached repositories +swap-shakacode-deps --clean-cache + +# Clean specific gem cache +swap-shakacode-deps --clean-cache shakapacker +``` + +## How It Works + +1. **Backup**: Creates `.backup` files for `Gemfile` and `package.json` +2. **Modify Gemfile**: Updates gem declarations to use `path:` or `github:` options +3. **Modify package.json**: Updates npm dependencies to use `file:` protocol for local paths +4. **Install**: Runs `bundle install` and `npm install` to update lock files +5. **Build**: Optionally builds npm packages in the local gems +6. **Watch**: Optionally starts watch processes for auto-rebuilding + +## Safety Features + +- **Automatic Backups**: Always creates backups before modifying files +- **Validation**: Validates paths and repository names before making changes +- **Atomic Operations**: Uses file locking to prevent corruption +- **Rollback**: Can restore from backups if something goes wrong +- **Dry Run**: Preview changes without modifying files + +## Cache Location + +GitHub repositories are cached in `~/.cache/swap-shakacode-deps/` to speed up subsequent swaps. + +## Troubleshooting + +### Permission Denied + +If you get permission errors, ensure you have write access to: +- Your project's `Gemfile` and `package.json` +- The cache directory `~/.cache/swap-shakacode-deps/` + +### Build Failures + +If npm builds fail: +1. Check that you have Node.js and npm installed +2. Run `npm install` in the local gem's directory +3. Check the gem's README for specific build requirements + +### Watch Processes Not Stopping + +If watch processes don't stop cleanly: +```bash +# Force kill all watch processes +swap-shakacode-deps --kill-watch + +# If that doesn't work, find and kill manually +ps aux | grep "npm.*watch" +kill +``` + +## Contributing + +Bug reports and pull requests are welcome on GitHub at https://github.com/shakacode/swap-shakacode-deps. + +## License + +The gem is available as open source under the terms of the [MIT License](LICENSE). + +## About ShakaCode + +This tool is maintained by [ShakaCode](https://www.shakacode.com), the team behind [React on Rails](https://github.com/shakacode/react_on_rails), [Shakapacker](https://github.com/shakacode/shakapacker), and other open-source projects. + +For more tools and resources, visit [shakacode.com](https://www.shakacode.com). diff --git a/swap-shakacode-deps/Rakefile b/swap-shakacode-deps/Rakefile new file mode 100644 index 0000000..e17708d --- /dev/null +++ b/swap-shakacode-deps/Rakefile @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'bundler/gem_tasks' +require 'rspec/core/rake_task' +require 'rubocop/rake_task' + +RSpec::Core::RakeTask.new(:spec) +RuboCop::RakeTask.new + +desc 'Run all checks (specs and linters)' +task check: %i[spec rubocop] + +task default: :check \ No newline at end of file diff --git a/swap-shakacode-deps/USAGE_EXAMPLES.md b/swap-shakacode-deps/USAGE_EXAMPLES.md new file mode 100644 index 0000000..77aa4c0 --- /dev/null +++ b/swap-shakacode-deps/USAGE_EXAMPLES.md @@ -0,0 +1,173 @@ +# swap-shakacode-deps Usage Examples + +## Installation & Basic Usage + +### 1. Global Installation +```bash +# Install globally +gem install swap-shakacode-deps + +# Verify installation +swap-shakacode-deps --help +``` + +### 2. Simple Local Swap +```bash +# In any project using react_on_rails +cd ~/projects/my-rails-app + +# Swap to local react_on_rails +swap-shakacode-deps --react-on-rails ~/dev/react_on_rails + +# Your Gemfile now has: +# gem 'react_on_rails', path: '~/dev/react_on_rails' + +# package.json now has: +# "react-on-rails": "file:~/dev/react_on_rails/node_package" +``` + +### 3. Restore Original Dependencies +```bash +# Restore original versions +swap-shakacode-deps --restore + +# Gemfile and package.json are restored from backups +``` + +## Advanced Scenarios + +### Working Across Multiple Projects + +```bash +# Create a config file in your home directory +cat > ~/.swap-deps.yml << EOF +gems: + shakapacker: ~/dev/shakapacker + react_on_rails: ~/dev/react_on_rails +EOF + +# Apply to any project +cd ~/projects/app1 +swap-shakacode-deps --apply + +cd ~/projects/app2 +swap-shakacode-deps --apply +``` + +### Testing a Pull Request + +```bash +# Test a specific PR branch +swap-shakacode-deps --github shakacode/shakapacker#pr-123-feature + +# Run your tests +bundle exec rspec + +# Restore when done +swap-shakacode-deps --restore +``` + +### Development with Auto-Rebuild + +```bash +# Start watch mode for automatic rebuilding +swap-shakacode-deps --shakapacker ~/dev/shakapacker --watch + +# In another terminal, make changes to shakapacker +# The npm package rebuilds automatically + +# Check watch processes +swap-shakacode-deps --list-watch + +# Stop watch processes when done +swap-shakacode-deps --kill-watch +``` + +### Processing Multiple Projects + +```bash +# Process all Rails apps in a directory +swap-shakacode-deps --path ~/projects --recursive --react-on-rails ~/dev/react_on_rails + +# This finds all Gemfiles in subdirectories and swaps dependencies +``` + +## Common Workflows + +### 1. Daily Development +```bash +# Morning: swap to local versions +swap-shakacode-deps --apply --watch + +# Work on your changes... +# Watch mode keeps packages in sync + +# Evening: restore and push +swap-shakacode-deps --kill-watch +swap-shakacode-deps --restore +git add . +git commit -m "Feature complete" +git push +``` + +### 2. Debugging Production Issues +```bash +# Use exact production versions +swap-shakacode-deps --github shakacode/shakapacker@v8.0.0 \ + --github shakacode/react_on_rails@v13.4.0 + +# Debug with production versions... + +# Restore +swap-shakacode-deps --restore +``` + +### 3. Cross-Gem Development +```bash +# Working on a feature that spans multiple gems +swap-shakacode-deps --shakapacker ~/dev/shakapacker \ + --react-on-rails ~/dev/react_on_rails \ + --watch + +# Make changes in both gems +# Test integration in your app +# Everything rebuilds automatically +``` + +## Comparison with Current bin/swap-deps + +### Current (Project-Specific) +```bash +# Only works in react_on_rails_demo_common +cd ~/react_on_rails_demo_common +bin/swap-deps --react-on-rails ~/dev/react_on_rails +``` + +### New (Global Tool) +```bash +# Works in ANY project +cd ~/any-project-with-gemfile +swap-shakacode-deps --react-on-rails ~/dev/react_on_rails +``` + +## Key Advantages + +1. **Global Availability**: Use in any project, not tied to demos +2. **Project Agnostic**: Works with any Rails/Node project structure +3. **Portable Config**: Share `.swap-deps.yml` across projects +4. **Clean Cache**: Centralized cache management +5. **Version Control**: Gem versioning for the tool itself + +## Migration from bin/swap-deps + +For existing `react_on_rails_demo_common` users: + +```bash +# Old way (still works) +bin/swap-deps --react-on-rails ~/dev/react_on_rails --demo basic-v16 + +# New way (after installing gem) +swap-shakacode-deps --react-on-rails ~/dev/react_on_rails + +# The new tool works everywhere, not just in demos! +``` diff --git a/swap-shakacode-deps/bin/swap-shakacode-deps b/swap-shakacode-deps/bin/swap-shakacode-deps new file mode 100755 index 0000000..76e36b9 --- /dev/null +++ b/swap-shakacode-deps/bin/swap-shakacode-deps @@ -0,0 +1,7 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require_relative '../lib/swap_shakacode_deps' + +# Main execution +SwapShakacodeDeps::CLI.new.run! \ No newline at end of file diff --git a/swap-shakacode-deps/lib/swap_shakacode_deps.rb b/swap-shakacode-deps/lib/swap_shakacode_deps.rb new file mode 100644 index 0000000..a3f4327 --- /dev/null +++ b/swap-shakacode-deps/lib/swap_shakacode_deps.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require_relative 'swap_shakacode_deps/version' +require_relative 'swap_shakacode_deps/error' +require_relative 'swap_shakacode_deps/github_spec_parser' +require_relative 'swap_shakacode_deps/backup_manager' +require_relative 'swap_shakacode_deps/cache_manager' +require_relative 'swap_shakacode_deps/watch_manager' +require_relative 'swap_shakacode_deps/gem_swapper' +require_relative 'swap_shakacode_deps/npm_swapper' +require_relative 'swap_shakacode_deps/config_loader' +require_relative 'swap_shakacode_deps/swapper' +require_relative 'swap_shakacode_deps/cli' + +# Main module for swap-shakacode-deps gem +module SwapShakacodeDeps + # Supported Shakacode gems + SUPPORTED_GEMS = %w[shakapacker react_on_rails cypress-on-rails].freeze + + # NPM package paths within each gem + NPM_PACKAGE_PATHS = { + 'shakapacker' => '.', + 'react_on_rails' => 'node_package', + 'cypress-on-rails' => nil # Ruby-only gem + }.freeze +end diff --git a/swap-shakacode-deps/lib/swap_shakacode_deps/backup_manager.rb b/swap-shakacode-deps/lib/swap_shakacode_deps/backup_manager.rb new file mode 100644 index 0000000..0634e41 --- /dev/null +++ b/swap-shakacode-deps/lib/swap_shakacode_deps/backup_manager.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module SwapShakacodeDeps + # Manages backup and restore operations for Gemfiles and package.json files + class BackupManager + # TODO: Extract implementation from demo_scripts/gem_swapper.rb backup methods + + BACKUP_SUFFIX = '.backup' + + def initialize(dry_run: false, verbose: false) + @dry_run = dry_run + @verbose = verbose + end + + # Creates a backup of the specified file + def backup_file(file_path) + raise NotImplementedError, 'File backup will be implemented in the next iteration' + end + + # Restores a file from its backup + def restore_file(file_path) + raise NotImplementedError, 'File restore will be implemented in the next iteration' + end + + # Checks if a backup exists for the specified file + def backup_exists?(file_path) + File.exist?(file_path + BACKUP_SUFFIX) + end + + # Lists all backup files in a directory + def list_backups(directory) + Dir.glob(File.join(directory, "*#{BACKUP_SUFFIX}")) + end + end +end diff --git a/swap-shakacode-deps/lib/swap_shakacode_deps/cache_manager.rb b/swap-shakacode-deps/lib/swap_shakacode_deps/cache_manager.rb new file mode 100644 index 0000000..bb195aa --- /dev/null +++ b/swap-shakacode-deps/lib/swap_shakacode_deps/cache_manager.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module SwapShakacodeDeps + # Manages cached GitHub repositories for faster subsequent swaps + class CacheManager + # TODO: Extract implementation from demo_scripts/gem_swapper.rb cache methods + + CACHE_DIR = File.expand_path('~/.cache/swap-shakacode-deps') + + def initialize(dry_run: false, verbose: false, **_options) + @dry_run = dry_run + @verbose = verbose + end + + # Shows cache information including size and cached repositories + def show_info + puts 'ℹ️ Cache management will be implemented in the next iteration' + puts " Cache location: #{CACHE_DIR}" + end + + # Cleans cached repositories + def clean(gem_name: nil) + if gem_name + puts "ℹ️ Cleaning cache for #{gem_name} will be implemented in the next iteration" + else + puts 'ℹ️ Cleaning all cache will be implemented in the next iteration' + end + end + + # Returns path for cached GitHub repository + def github_cache_path(_gem_name, repo_info) + repo_slug = repo_info[:repo].tr('/', '-') + branch_slug = repo_info[:branch].tr('/', '-') + File.join(CACHE_DIR, "#{repo_slug}-#{branch_slug}") + end + + # Checks if cache directory exists + def cache_exists? + File.directory?(CACHE_DIR) + end + end +end diff --git a/swap-shakacode-deps/lib/swap_shakacode_deps/cli.rb b/swap-shakacode-deps/lib/swap_shakacode_deps/cli.rb new file mode 100644 index 0000000..7148a5b --- /dev/null +++ b/swap-shakacode-deps/lib/swap_shakacode_deps/cli.rb @@ -0,0 +1,326 @@ +# frozen_string_literal: true + +require 'optparse' + +module SwapShakacodeDeps + # rubocop:disable Metrics/ClassLength + class CLI + CONFIG_FILE = '.swap-deps.yml' + + attr_reader :gem_paths, :github_repos, :options + + def initialize + @gem_paths = {} + @github_repos = {} + @options = { + dry_run: false, + verbose: false, + restore: false, + apply_config: false, + skip_build: false, + watch_mode: false, + target_path: nil, + recursive: false, + list_watch: false, + kill_watch: false, + show_cache: false, + clean_cache: false, + clean_cache_gem: nil, + show_status: false + } + end + + # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity + def run! + parse_options! + + if @options[:show_status] + show_status_info + elsif @options[:show_cache] + show_cache_info + elsif @options[:clean_cache] || @options[:clean_cache_gem] + clean_cache_handler + elsif @options[:list_watch] + list_watch_processes + elsif @options[:kill_watch] + kill_watch_processes + elsif @options[:restore] + restore_dependencies + elsif @options[:apply_config] + apply_from_config + elsif gem_paths.empty? && github_repos.empty? + puts 'Error: No dependencies specified. Use --shakapacker, --react-on-rails, --cypress-on-rails, or --github' + puts 'Or use --apply to load from .swap-deps.yml' + puts 'Run with --help for more information' + exit 1 + else + swap_dependencies + end + rescue SwapShakacodeDeps::Error => e + warn "Error: #{e.message}" + exit 1 + rescue StandardError => e + warn "Unexpected error: #{e.message}" + warn e.backtrace.join("\n") if @options[:verbose] + exit 1 + end + # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity + + private + + # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/BlockLength + def parse_options! + parser = OptionParser.new do |opts| + opts.banner = 'Usage: swap-shakacode-deps [options]' + opts.separator '' + opts.separator 'Swap Shakacode gem dependencies between production and local/GitHub versions' + opts.separator '' + opts.separator 'Gem options (specify one or more):' + + opts.on('--shakapacker PATH', 'Path to local shakapacker repository') do |path| + @gem_paths['shakapacker'] = path + end + + opts.on('--react-on-rails PATH', 'Path to local react_on_rails repository') do |path| + @gem_paths['react_on_rails'] = path + end + + opts.on('--cypress-on-rails PATH', 'Path to local cypress-on-rails repository') do |path| + @gem_paths['cypress-on-rails'] = path + end + + opts.separator '' + opts.separator 'GitHub options:' + + opts.on('--github REPO[#BRANCH|@TAG]', + 'GitHub repository (e.g., user/repo, user/repo#branch, or user/repo@tag)') do |value| + parse_github_option(value) + end + + opts.separator '' + opts.separator 'Configuration options:' + + opts.on('--apply', 'Apply dependency paths from .swap-deps.yml config file') do + @options[:apply_config] = true + end + + opts.on('--restore', 'Restore original dependency versions from backups') do + @options[:restore] = true + end + + opts.separator '' + opts.separator 'Target directory:' + + opts.on('--path DIR', 'Target directory to process (default: current directory)') do |dir| + @options[:target_path] = dir + end + + opts.on('--recursive', 'Process all subdirectories with Gemfiles') do + @options[:recursive] = true + end + + opts.separator '' + opts.separator 'Build options:' + + opts.on('--build', 'Build local npm packages (default unless --skip-build)') do + @options[:skip_build] = false + end + + opts.on('--skip-build', 'Skip building local npm packages') do + @options[:skip_build] = true + end + + opts.on('--watch', 'Run npm packages in watch mode for auto-rebuild') do + @options[:watch_mode] = true + end + + opts.separator '' + opts.separator 'Watch process management:' + + opts.on('--list-watch', 'List tracked watch processes') do + @options[:list_watch] = true + end + + opts.on('--kill-watch', 'Stop all tracked watch processes') do + @options[:kill_watch] = true + end + + opts.separator '' + opts.separator 'Status and cache management:' + + opts.on('--status', 'Show current swapped dependencies status') do + @options[:show_status] = true + end + + opts.on('--show-cache', 'Show cache location, size, and cached repositories') do + @options[:show_cache] = true + end + + opts.on('--clean-cache [GEM]', 'Remove cached repositories (all or specific gem)') do |gem| + if gem + @options[:clean_cache_gem] = gem + else + @options[:clean_cache] = true + end + end + + opts.separator '' + opts.separator 'General options:' + + opts.on('--dry-run', 'Show what would be done without making changes') do + @options[:dry_run] = true + end + + opts.on('-v', '--verbose', 'Show detailed output') do + @options[:verbose] = true + end + + opts.on('-h', '--help', 'Show this help message') do + puts opts + puts '' + puts 'Examples:' + puts ' # Swap react_on_rails to local version' + puts ' swap-shakacode-deps --react-on-rails ~/dev/react_on_rails' + puts '' + puts ' # Swap multiple dependencies' + puts ' swap-shakacode-deps --shakapacker ~/dev/shakapacker \\' + puts ' --react-on-rails ~/dev/react_on_rails' + puts '' + puts ' # Use a GitHub repository with a branch' + puts ' swap-shakacode-deps --github shakacode/shakapacker#fix-hmr' + puts '' + puts ' # Use a release tag' + puts ' swap-shakacode-deps --github shakacode/shakapacker@v9.0.0' + puts '' + puts ' # Mix local paths and GitHub repos' + puts ' swap-shakacode-deps --shakapacker ~/dev/shakapacker \\' + puts ' --github shakacode/react_on_rails#feature-x' + puts '' + puts ' # Process a specific directory' + puts ' swap-shakacode-deps --path ~/projects/my-app --react-on-rails ~/dev/react_on_rails' + puts '' + puts ' # Process all projects recursively' + puts ' swap-shakacode-deps --path ~/projects --recursive --apply' + puts '' + puts ' # Use config file' + puts ' swap-shakacode-deps --apply' + puts '' + puts ' # Restore original versions' + puts ' swap-shakacode-deps --restore' + puts '' + puts ' # Preview without making changes' + puts ' swap-shakacode-deps --dry-run --react-on-rails ~/dev/react_on_rails' + puts '' + puts ' # Use watch mode for auto-rebuild' + puts ' swap-shakacode-deps --watch --react-on-rails ~/dev/react_on_rails' + puts '' + puts 'Configuration file:' + puts " Create #{CONFIG_FILE} with your dependency paths." + puts ' See README.md for configuration file format.' + exit 0 + end + end + + parser.parse! + end + # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/BlockLength + + def parse_github_option(value) + if value.include?('@') + repo, ref = value.split('@', 2) + ref_type = :tag + elsif value.include?('#') + repo, ref = value.split('#', 2) + ref_type = :branch + else + repo = value + ref = 'main' + ref_type = :branch + end + gem_name = infer_gem_from_repo(repo) + @github_repos[gem_name] = { repo: repo, branch: ref, ref_type: ref_type } + end + + def infer_gem_from_repo(repo) + gem_name = repo.split('/').last.downcase + + case gem_name + when 'shakapacker' + 'shakapacker' + when 'react_on_rails', 'react-on-rails' + 'react_on_rails' + when 'cypress-on-rails', 'cypress_on_rails' + 'cypress-on-rails' + else + raise ValidationError, "Cannot infer gem name from repo: #{repo}. " \ + 'Please use --shakapacker, --react-on-rails, or --cypress-on-rails flags explicitly.' + end + end + + def swap_dependencies + swapper = create_swapper + swapper.swap! + end + + def restore_dependencies + swapper = create_swapper + swapper.restore! + end + + def apply_from_config + config_file = find_config_file + unless config_file + raise ConfigError, "Config file not found: #{CONFIG_FILE}\n" \ + "Create #{CONFIG_FILE} with your dependency configuration." + end + + swapper = create_swapper + swapper.load_config(config_file) + swapper.swap! + end + + def show_status_info + swapper = create_swapper + swapper.show_status + end + + def show_cache_info + cache_manager = CacheManager.new(**@options) + cache_manager.show_info + end + + def clean_cache_handler + cache_manager = CacheManager.new(**@options) + cache_manager.clean(gem_name: @options[:clean_cache_gem]) + end + + def list_watch_processes + watch_manager = WatchManager.new(**@options) + watch_manager.list_processes + end + + def kill_watch_processes + watch_manager = WatchManager.new(**@options) + watch_manager.kill_processes + end + + def create_swapper + Swapper.new( + gem_paths: @gem_paths, + github_repos: @github_repos, + **@options + ) + end + + def find_config_file + if @options[:target_path] + config_path = File.join(@options[:target_path], CONFIG_FILE) + return config_path if File.exist?(config_path) + elsif File.exist?(CONFIG_FILE) + return CONFIG_FILE + end + nil + end + end + # rubocop:enable Metrics/ClassLength +end diff --git a/swap-shakacode-deps/lib/swap_shakacode_deps/config_loader.rb b/swap-shakacode-deps/lib/swap_shakacode_deps/config_loader.rb new file mode 100644 index 0000000..8dd863b --- /dev/null +++ b/swap-shakacode-deps/lib/swap_shakacode_deps/config_loader.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require 'yaml' + +module SwapShakacodeDeps + # Loads and validates configuration from .swap-deps.yml files + class ConfigLoader + # TODO: Extract implementation from demo_scripts/gem_swapper.rb load_config method + + def initialize(verbose: false) + @verbose = verbose + end + + # Loads configuration from a YAML file + def load(config_file) + raise ConfigError, "Configuration file not found: #{config_file}" unless File.exist?(config_file) + + config = YAML.safe_load_file(config_file) + validate_config(config) + config + rescue Psych::SyntaxError => e + raise ConfigError, "Invalid YAML syntax in #{config_file}: #{e.message}" + end + + private + + def validate_config(config) + raise ConfigError, 'Configuration must be a hash' unless config.is_a?(Hash) + + # Validate gems section if present + raise ConfigError, 'gems section must be a hash' if config['gems'] && !config['gems'].is_a?(Hash) + + # Validate github section if present + return unless config['github'] && !config['github'].is_a?(Hash) + + raise ConfigError, 'github section must be a hash' + end + end +end diff --git a/swap-shakacode-deps/lib/swap_shakacode_deps/error.rb b/swap-shakacode-deps/lib/swap_shakacode_deps/error.rb new file mode 100644 index 0000000..5cb1783 --- /dev/null +++ b/swap-shakacode-deps/lib/swap_shakacode_deps/error.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module SwapShakacodeDeps + class Error < StandardError; end + class ValidationError < Error; end + class ConfigError < Error; end + class BackupError < Error; end + class CacheError < Error; end +end diff --git a/swap-shakacode-deps/lib/swap_shakacode_deps/gem_swapper.rb b/swap-shakacode-deps/lib/swap_shakacode_deps/gem_swapper.rb new file mode 100644 index 0000000..e25407f --- /dev/null +++ b/swap-shakacode-deps/lib/swap_shakacode_deps/gem_swapper.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module SwapShakacodeDeps + # Handles swapping of gem dependencies in Gemfile + class GemSwapper + # TODO: Extract implementation from demo_scripts/gem_swapper.rb + + def initialize(dry_run: false, verbose: false) + @dry_run = dry_run + @verbose = verbose + end + + # Swaps a gem to use a local path in Gemfile + def swap_to_path(gemfile_content, gem_name, local_path) + raise NotImplementedError, 'Gemfile path swapping will be implemented in the next iteration' + end + + # Swaps a gem to use a GitHub repository in Gemfile + def swap_to_github(gemfile_content, gem_name, github_info) + raise NotImplementedError, 'Gemfile GitHub swapping will be implemented in the next iteration' + end + + # Detects swapped gems in a Gemfile + def detect_swapped_gems(gemfile_path) + return [] unless File.exist?(gemfile_path) + + puts 'ℹ️ Gem detection will be implemented in the next iteration' + [] + end + + # Runs bundle install after swapping gems + def run_bundle_install(path) + return if @dry_run + + puts ' Running bundle install...' + Dir.chdir(path) do + system('bundle', 'install', '--quiet') + end + end + end +end diff --git a/swap-shakacode-deps/lib/swap_shakacode_deps/github_spec_parser.rb b/swap-shakacode-deps/lib/swap_shakacode_deps/github_spec_parser.rb new file mode 100644 index 0000000..ea37abb --- /dev/null +++ b/swap-shakacode-deps/lib/swap_shakacode_deps/github_spec_parser.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module SwapShakacodeDeps + # Parses and validates GitHub repository specifications + module GitHubSpecParser + # TODO: Extract implementation from demo_scripts/github_spec_parser.rb + + # Parses GitHub spec: org/repo, org/repo#branch, or org/repo@tag + def parse_github_spec(github_spec) + raise NotImplementedError, 'GitHub spec parsing will be implemented in the next iteration' + end + + # Validates GitHub repository format (org/repo) + def validate_github_repo(repo) + raise NotImplementedError, 'GitHub repo validation will be implemented in the next iteration' + end + + # Validates GitHub branch name according to Git ref naming rules + def validate_github_branch(branch) + raise NotImplementedError, 'GitHub branch validation will be implemented in the next iteration' + end + end +end diff --git a/swap-shakacode-deps/lib/swap_shakacode_deps/npm_swapper.rb b/swap-shakacode-deps/lib/swap_shakacode_deps/npm_swapper.rb new file mode 100644 index 0000000..d3e3356 --- /dev/null +++ b/swap-shakacode-deps/lib/swap_shakacode_deps/npm_swapper.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module SwapShakacodeDeps + # Handles swapping of npm package dependencies in package.json + class NpmSwapper + # TODO: Extract implementation from demo_scripts/gem_swapper.rb npm methods + + def initialize(dry_run: false, verbose: false) + @dry_run = dry_run + @verbose = verbose + end + + # Swaps npm packages to use local file paths in package.json + def swap_to_local(package_json_path, packages) + raise NotImplementedError, 'NPM package swapping will be implemented in the next iteration' + end + + # Detects swapped npm packages in package.json + def detect_swapped_packages(package_json_path) + return [] unless File.exist?(package_json_path) + + puts 'ℹ️ NPM package detection will be implemented in the next iteration' + [] + end + + # Runs npm install after swapping packages + def run_npm_install(path) + return if @dry_run + + puts ' Running npm install...' + Dir.chdir(path) do + system('npm', 'install', '--silent') + end + end + + # Builds npm packages + def build_npm_package(gem_name, npm_path) + return if @dry_run + + puts " Building #{gem_name}..." + Dir.chdir(npm_path) do + system('npm', 'run', 'build') + end + end + end +end diff --git a/swap-shakacode-deps/lib/swap_shakacode_deps/swapper.rb b/swap-shakacode-deps/lib/swap_shakacode_deps/swapper.rb new file mode 100644 index 0000000..f64ce0b --- /dev/null +++ b/swap-shakacode-deps/lib/swap_shakacode_deps/swapper.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +module SwapShakacodeDeps + # Main orchestrator class for swapping dependencies + class Swapper + # TODO: Extract and refactor implementation from demo_scripts/gem_swapper.rb DependencySwapper class + + # rubocop:disable Metrics/AbcSize + def initialize(gem_paths: {}, github_repos: {}, **options) + @gem_paths = gem_paths + @github_repos = github_repos + @options = options + @dry_run = options[:dry_run] + @verbose = options[:verbose] + @target_path = options[:target_path] || Dir.pwd + @recursive = options[:recursive] + + @backup_manager = BackupManager.new(**options) + @cache_manager = CacheManager.new(**options) + @watch_manager = WatchManager.new(**options) + @gem_swapper = GemSwapper.new(**options) + @npm_swapper = NpmSwapper.new(**options) + @config_loader = ConfigLoader.new(**options) + end + # rubocop:enable Metrics/AbcSize + + # Main swap operation + # rubocop:disable Metrics/AbcSize + def swap! + puts '🚧 Dependency swapping functionality will be implemented in the next iteration' + puts '' + puts 'Current configuration:' + puts " Target path: #{@target_path}" + puts " Recursive: #{@recursive}" + puts " Dry run: #{@dry_run}" + puts " Verbose: #{@verbose}" + puts '' + puts 'Gem paths to swap:' + @gem_paths.each do |gem, path| + puts " #{gem}: #{path}" + end + puts '' + puts 'GitHub repos to swap:' + @github_repos.each do |gem, info| + puts " #{gem}: #{info[:repo]}##{info[:branch]}" + end + end + # rubocop:enable Metrics/AbcSize + + # Restore operation + def restore! + puts '🚧 Dependency restoration functionality will be implemented in the next iteration' + end + + # Load configuration from file + # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity + def load_config(config_file) + config = @config_loader.load(config_file) + + # Load path-based gems + @gem_paths.merge!(config['gems']) if config['gems'].is_a?(Hash) + + # Load GitHub-based gems + if config['github'].is_a?(Hash) + config['github'].each do |gem_name, info| + @github_repos[gem_name] = { + repo: info['repo'] || info[:repo], + branch: info['branch'] || info[:branch] || 'main', + ref_type: (info['ref_type'] || info[:ref_type] || :branch).to_sym + } + end + end + + puts "📋 Loaded configuration from #{config_file}" + end + # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity + + # Show status of swapped dependencies + def show_status + puts '🚧 Status display functionality will be implemented in the next iteration' + end + + private + + def find_projects + if @recursive + # Find all directories with Gemfiles recursively + Dir.glob(File.join(@target_path, '**/Gemfile')).map { |f| File.dirname(f) } + else + # Just process the target directory + gemfile = File.join(@target_path, 'Gemfile') + File.exist?(gemfile) ? [@target_path] : [] + end + end + end +end diff --git a/swap-shakacode-deps/lib/swap_shakacode_deps/version.rb b/swap-shakacode-deps/lib/swap_shakacode_deps/version.rb new file mode 100644 index 0000000..1f038cf --- /dev/null +++ b/swap-shakacode-deps/lib/swap_shakacode_deps/version.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +module SwapShakacodeDeps + VERSION = '0.1.0' +end diff --git a/swap-shakacode-deps/lib/swap_shakacode_deps/watch_manager.rb b/swap-shakacode-deps/lib/swap_shakacode_deps/watch_manager.rb new file mode 100644 index 0000000..bf6fa44 --- /dev/null +++ b/swap-shakacode-deps/lib/swap_shakacode_deps/watch_manager.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module SwapShakacodeDeps + # Manages watch processes for automatic rebuilding of npm packages + class WatchManager + # TODO: Extract implementation from demo_scripts/gem_swapper.rb watch methods + + CACHE_DIR = File.expand_path('~/.cache/swap-shakacode-deps') + WATCH_PIDS_FILE = File.join(CACHE_DIR, 'watch_pids.json') + WATCH_LOG_DIR = File.join(CACHE_DIR, 'watch_logs') + + def initialize(dry_run: false, verbose: false, **_options) + @dry_run = dry_run + @verbose = verbose + end + + # Lists all tracked watch processes + def list_processes + puts 'ℹ️ Watch process listing will be implemented in the next iteration' + end + + # Stops all tracked watch processes + def kill_processes + puts 'ℹ️ Watch process termination will be implemented in the next iteration' + end + + # Starts a watch process for the specified gem + def spawn_watch_process(gem_name, npm_path) + raise NotImplementedError, 'Watch process spawning will be implemented in the next iteration' + end + + # Checks if a process is running + def process_running?(pid) + Process.kill(0, pid) + true + rescue Errno::ESRCH + false + rescue Errno::EPERM + true # Process exists but we don't have permission + end + end +end diff --git a/swap-shakacode-deps/swap-shakacode-deps.gemspec b/swap-shakacode-deps/swap-shakacode-deps.gemspec new file mode 100644 index 0000000..b7e6e94 --- /dev/null +++ b/swap-shakacode-deps/swap-shakacode-deps.gemspec @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require_relative 'lib/swap_shakacode_deps/version' + +Gem::Specification.new do |spec| + spec.name = 'swap-shakacode-deps' + spec.version = SwapShakacodeDeps::VERSION + spec.authors = ['ShakaCode'] + spec.email = ['contact@shakacode.com'] + + spec.summary = 'Swap Shakacode gem dependencies between local and production versions' + spec.description = <<~DESC + A command-line tool for swapping Shakacode gem dependencies (shakapacker, react_on_rails, + cypress-on-rails) between production versions and local development paths or GitHub branches. + Supports automatic backup/restore, npm package building, and watch mode for development. + DESC + spec.homepage = 'https://github.com/shakacode/swap-shakacode-deps' + spec.license = 'MIT' + spec.required_ruby_version = '>= 2.7.0' + + spec.metadata['homepage_uri'] = spec.homepage + spec.metadata['source_code_uri'] = 'https://github.com/shakacode/swap-shakacode-deps' + spec.metadata['changelog_uri'] = 'https://github.com/shakacode/swap-shakacode-deps/blob/main/CHANGELOG.md' + + # Specify which files should be added to the gem when it is released + spec.files = Dir.glob('{bin,lib}/**/*', File::FNM_DOTMATCH) + + %w[README.md LICENSE CHANGELOG.md] + spec.bindir = 'bin' + spec.executables = ['swap-shakacode-deps'] + spec.require_paths = ['lib'] + + # Runtime dependencies + spec.add_dependency 'json', '~> 2.0' + + # Development dependencies + spec.add_development_dependency 'bundler', '~> 2.0' + spec.add_development_dependency 'rake', '~> 13.0' + spec.add_development_dependency 'rspec', '~> 3.0' + spec.add_development_dependency 'rubocop', '~> 1.50' + spec.add_development_dependency 'rubocop-rspec', '~> 2.22' + + spec.metadata['rubygems_mfa_required'] = 'true' +end \ No newline at end of file