Skip to content

Commit 6461003

Browse files
justin808claude
andcommitted
Add GitHub shorthand syntax and auto-detection to swap-deps
- Support shorthand syntax for gem-specific flags: - `--shakapacker '#main'` expands to shakacode/shakapacker#main - `--shakapacker '@v9.0.0'` expands to shakacode/[email protected] - `--shakapacker 'shakacode/shakapacker#branch'` for full spec - Auto-detect default branch when none specified: - `--shakapacker shakacode/shakapacker` detects main/master - Uses `git ls-remote --symref` for zero-cost detection - Add zsh escaping documentation: - Document that # and @ require quotes in zsh - Update help text with examples - Map gem names to default repos: - shakapacker -> shakacode/shakapacker - react_on_rails -> shakacode/react_on_rails - cypress-on-rails -> shakacode/cypress-on-rails Fixes issue where zsh interprets # as comment marker. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 913e180 commit 6461003

File tree

2 files changed

+112
-20
lines changed

2 files changed

+112
-20
lines changed

lib/demo_scripts/gem_swapper.rb

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# frozen_string_literal: true
22

3+
require 'English'
34
require 'yaml'
45
require 'json'
56
require 'pathname'
@@ -599,14 +600,14 @@ def validate_github_repos(repos)
599600
repo, ref, ref_type = parse_github_spec(value)
600601
{
601602
repo: repo,
602-
branch: ref || 'main',
603+
branch: ref,
603604
ref_type: ref_type || :branch
604605
}
605606
elsif value.is_a?(Hash)
606607
# Hash format with repo and optional branch
607608
{
608609
repo: value['repo'] || value[:repo],
609-
branch: value['branch'] || value[:branch] || 'main',
610+
branch: value['branch'] || value[:branch],
610611
ref_type: (value['ref_type'] || value[:ref_type] || :branch).to_sym
611612
}
612613
else
@@ -615,13 +616,33 @@ def validate_github_repos(repos)
615616

616617
# Use shared validation methods
617618
validate_github_repo(result[:repo])
619+
620+
# Auto-detect default branch if not specified
621+
result[:branch] ||= detect_default_branch(result[:repo])
622+
618623
validate_github_branch(result[:branch]) if result[:branch]
619624

620625
result
621626
end
622627
end
623628
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
624629

630+
def detect_default_branch(repo)
631+
puts " 🔍 Detecting default branch for #{repo}..."
632+
633+
# Use git ls-remote to find the default branch without cloning
634+
output = `git ls-remote --symref https://github.com/#{repo}.git HEAD 2>/dev/null`
635+
636+
if $CHILD_STATUS.success? && output =~ %r{ref: refs/heads/(\S+)\s+HEAD}
637+
branch = ::Regexp.last_match(1)
638+
puts " Detected: #{branch}"
639+
branch
640+
else
641+
puts " Could not detect, defaulting to 'main'"
642+
'main'
643+
end
644+
end
645+
625646
def validate_local_paths!
626647
gem_paths.each do |gem_name, path|
627648
next if File.directory?(path)

lib/demo_scripts/swap_deps_cli.rb

Lines changed: 89 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,69 @@ def run!
7474
end
7575
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
7676

77+
# Map gem names to their default GitHub repositories
78+
GEM_REPOS = {
79+
'shakapacker' => 'shakacode/shakapacker',
80+
'react_on_rails' => 'shakacode/react_on_rails',
81+
'cypress-on-rails' => 'shakacode/cypress-on-rails'
82+
}.freeze
83+
7784
private
7885

86+
def process_gem_value(gem_name, value)
87+
# Check if it's a GitHub spec (starts with #, @, or contains /)
88+
if value.start_with?('#', '@') || value.include?('/')
89+
# It's a GitHub spec
90+
repo, ref, ref_type = parse_github_spec(gem_name, value)
91+
@github_repos[gem_name] = { repo: repo, branch: ref, ref_type: ref_type }
92+
else
93+
# It's a local path
94+
@gem_paths[gem_name] = value
95+
end
96+
end
97+
98+
# rubocop:disable Metrics/MethodLength
99+
def parse_github_spec(gem_name, spec)
100+
# Handle shorthand formats:
101+
# - #branch -> shakacode/gem#branch
102+
# - @tag -> shakacode/gem@tag
103+
# - user/repo#branch -> user/repo#branch
104+
# - user/repo@tag -> user/repo@tag
105+
# - user/repo -> user/repo (will auto-detect default branch)
106+
107+
if spec.start_with?('#')
108+
# Shorthand: #branch
109+
repo = GEM_REPOS[gem_name]
110+
raise Error, "No default repo for gem: #{gem_name}" unless repo
111+
112+
ref = spec[1..]
113+
ref_type = :branch
114+
elsif spec.start_with?('@')
115+
# Shorthand: @tag
116+
repo = GEM_REPOS[gem_name]
117+
raise Error, "No default repo for gem: #{gem_name}" unless repo
118+
119+
ref = spec[1..]
120+
ref_type = :tag
121+
elsif spec.include?('@')
122+
# Full: user/repo@tag
123+
repo, ref = spec.split('@', 2)
124+
ref_type = :tag
125+
elsif spec.include?('#')
126+
# Full: user/repo#branch
127+
repo, ref = spec.split('#', 2)
128+
ref_type = :branch
129+
else
130+
# Just repo name: user/repo
131+
repo = spec
132+
ref = nil # Will auto-detect default branch
133+
ref_type = :branch
134+
end
135+
136+
[repo, ref, ref_type]
137+
end
138+
# rubocop:enable Metrics/MethodLength
139+
79140
def detect_context!
80141
# Check if we're in a demo directory by looking for Gemfile and presence of ../../.swap-deps.yml
81142
if File.exist?('Gemfile') && File.exist?('../../.swap-deps.yml')
@@ -107,23 +168,26 @@ def parse_options!
107168
opts.separator ''
108169
opts.separator 'Gem options (specify one or more):'
109170

110-
opts.on('--shakapacker PATH', 'Path to local shakapacker repository') do |path|
111-
@gem_paths['shakapacker'] = path
171+
opts.on('--shakapacker VALUE', 'Local path or GitHub spec (e.g., #branch, @tag, user/repo#branch)') do |value|
172+
process_gem_value('shakapacker', value)
112173
end
113174

114-
opts.on('--react-on-rails PATH', 'Path to local react_on_rails repository') do |path|
115-
@gem_paths['react_on_rails'] = path
175+
opts.on('--react-on-rails VALUE',
176+
'Local path or GitHub spec (e.g., #branch, @tag, user/repo#branch)') do |value|
177+
process_gem_value('react_on_rails', value)
116178
end
117179

118-
opts.on('--cypress-on-rails PATH', 'Path to local cypress-on-rails repository') do |path|
119-
@gem_paths['cypress-on-rails'] = path
180+
opts.on('--cypress-on-rails VALUE',
181+
'Local path or GitHub spec (e.g., #branch, @tag, user/repo#branch)') do |value|
182+
process_gem_value('cypress-on-rails', value)
120183
end
121184

122185
opts.separator ''
123186
opts.separator 'GitHub options:'
124187

125188
opts.on('--github REPO[#BRANCH|@TAG]',
126-
'GitHub repository (e.g., user/repo, user/repo#branch, or user/repo@tag)') do |value|
189+
'GitHub repository (e.g., user/repo, user/repo#branch, or user/repo@tag)',
190+
'Note: In zsh, quote values with # or @ (e.g., \'user/repo#main\')') do |value|
127191
if value.include?('@')
128192
repo, ref = value.split('@', 2)
129193
ref_type = :tag
@@ -132,7 +196,7 @@ def parse_options!
132196
ref_type = :branch
133197
else
134198
repo = value
135-
ref = 'main'
199+
ref = nil # Auto-detect default branch
136200
ref_type = :branch
137201
end
138202
gem_name = infer_gem_from_repo(repo)
@@ -228,25 +292,32 @@ def parse_options!
228292
puts ' bin/swap-deps --shakapacker ~/dev/shakapacker \\'
229293
puts ' --react-on-rails ~/dev/react_on_rails'
230294
puts ''
295+
puts ' # Use shorthand for GitHub branches (quotes required in zsh!)'
296+
puts ' bin/swap-deps --shakapacker \'#main\''
297+
puts ' bin/swap-deps --react-on-rails \'#feature-x\''
298+
puts ''
299+
puts ' # Use shorthand for GitHub tags'
300+
puts ' bin/swap-deps --shakapacker \'@v9.0.0\''
301+
puts ''
302+
puts ' # Use full GitHub repo spec'
303+
puts ' bin/swap-deps --shakapacker \'shakacode/shakapacker#fix-hmr\''
304+
puts ' bin/swap-deps --shakapacker \'otheruser/shakapacker#custom-branch\''
305+
puts ''
306+
puts ' # Auto-detect default branch (no # needed, no quotes needed)'
307+
puts ' bin/swap-deps --shakapacker shakacode/shakapacker'
308+
puts ''
231309
puts ' # Apply to specific demo only'
232310
puts ' bin/swap-deps --demo basic-v16-rspack --react-on-rails ~/dev/react_on_rails'
233311
puts ''
234312
puts ' # Apply to demos-scratch directory'
235313
puts ' bin/swap-deps --demos-dir demos-scratch --react-on-rails ~/dev/react_on_rails'
236314
puts ''
237-
puts ' # Use a GitHub repository with a branch (# for branches)'
238-
puts ' bin/swap-deps --github shakacode/shakapacker#fix-hmr'
239-
puts ''
240-
puts ' # Use a release tag (@ for tags)'
241-
puts ' bin/swap-deps --github shakacode/[email protected]'
242-
puts ''
243-
puts ' # Mix branches and tags'
244-
puts ' bin/swap-deps --github shakacode/shakapacker#v8-stable \\'
245-
puts ' --github shakacode/[email protected]'
315+
puts ' # Use --github flag (alternative to gem-specific flags)'
316+
puts ' bin/swap-deps --github \'shakacode/shakapacker#fix-hmr\''
246317
puts ''
247318
puts ' # Mix local paths and GitHub repos'
248319
puts ' bin/swap-deps --shakapacker ~/dev/shakapacker \\'
249-
puts ' --github shakacode/react_on_rails#feature-x'
320+
puts ' --react-on-rails \'#feature-x\''
250321
puts ''
251322
puts ' # Use config file'
252323
puts ' bin/swap-deps --apply'

0 commit comments

Comments
 (0)