-
# frozen_string_literal: true
-
-
1
require 'thor'
-
1
require 'gempath'
-
-
1
module Gempath
-
1
class CLI < Thor
-
1
class << self
-
1
def exit_on_failure?
-
true
-
end
-
end
-
-
1
def help(*)
-
super
-
end
-
-
# Global options available to all commands
-
1
class_option :help, aliases: '-h', type: :boolean, desc: 'Display usage information'
-
1
class_option :filepath, aliases: '-f', type: :string,
-
desc: 'Path to Gemfile.lock (default: ./Gemfile.lock)',
-
default: 'Gemfile.lock'
-
-
1
desc 'analyze [OPTIONS]', 'Analyze dependencies in Gemfile.lock'
-
1
method_option :name, aliases: '-n', type: :string,
-
desc: 'Name of the gem to analyze'
-
1
long_desc <<~LONGDESC
-
`gempath analyze` analyzes dependencies and relationships in your Gemfile.lock.
-
-
When run without options, it shows information for all gems.
-
When given a gem name with --name, it shows detailed information just for that gem.
-
-
For each gem, it shows:\n
-
* Version being used\n
-
* All direct dependencies and their versions\n
-
* Source (rubygems.org, git, or path)\n
-
* Direct consumers (gems that depend on it)\n
-
* All dependency paths leading to this gem\n
-
Examples:
-
-
# Analyze all gems using Gemfile.lock in current directory:
-
-
gempath analyze
-
-
# Analyze aws-sdk-core using Gemfile.lock in current directory:
-
-
gempath analyze --name aws-sdk-core
-
-
# Analyze rails using a specific Gemfile.lock:
-
-
gempath analyze --name rails --filepath /path/to/Gemfile.lock
-
LONGDESC
-
1
def analyze(*)
-
# Thor will handle unknown arguments automatically
-
-
2
analyzer = Gempath::Analyzer.new(options[:filepath])
-
2
result = analyzer.analyze(options[:name])
-
2
puts JSON.pretty_generate(result)
-
rescue Gempath::Error => e
-
raise Thor::Error,
-
"#{e.message}\n\nTo specify a different Gemfile.lock location:\n gempath analyze -f /path/to/Gemfile.lock"
-
end
-
-
1
desc 'generate [OPTIONS]', 'Generate a minimal Gemfile for a gem and its dependencies'
-
1
method_option :name, aliases: '-n', type: :string, required: true,
-
desc: 'Name of the gem to generate Gemfile for'
-
1
method_option :filepath, aliases: '-f', type: :string, default: 'Gemfile.lock',
-
desc: 'Path to Gemfile.lock (default: ./Gemfile.lock)'
-
1
method_option :ruby_version, aliases: '-r', type: :string,
-
desc: 'Ruby version to use (default: current Ruby version)'
-
1
long_desc <<~LONGDESC
-
`gempath generate` creates a minimal working Gemfile for a gem and its dependencies.
-
-
This is useful for troubleshooting gem compatibility issues by creating an isolated
-
environment with just the gem and its dependencies.
-
-
The generated Gemfile will include:
-
* Ruby version (if specified)
-
* Source information (e.g. rubygems.org or custom gem server)
-
* The target gem and its version
-
* All direct dependencies and their versions
-
* Any git or path-based dependencies
-
-
Examples:
-
-
# Generate Gemfile for aws-sdk-core using current directory's Gemfile.lock:
-
-
gempath generate --name aws-sdk-core
-
-
# Generate Gemfile for rails with specific Ruby version:
-
-
gempath generate --name rails --ruby-version 3.2.0
-
-
# Generate Gemfile using dependencies from a specific Gemfile.lock:
-
-
gempath generate --name puppet --filepath /path/to/Gemfile.lock
-
LONGDESC
-
1
no_commands do
-
1
def group_gems_by_source(_name, _version, source_info)
-
59
then: 59
else: 0
case source_info&.dig('source', 'type')
-
when: 0
when 'git'
-
[:git, source_info['source']['remote']]
-
when: 1
when 'path'
-
1
[:path, source_info['source']['remote']]
-
when: 58
when 'rubygems'
-
58
source = source_info['source']['remotes'].first
-
58
then: 0
else: 58
source == 'https://rubygems.org' ? :default : [:source, source]
-
else: 0
else
-
:default
-
end
-
end
-
end
-
-
1
def generate(*)
-
6
analyzer = Gempath::Analyzer.new(options[:filepath])
-
6
result = analyzer.analyze(options[:name])
-
6
gem_info = result[options[:name]]
-
-
6
then: 2
else: 4
raise Thor::Error, "Gem '#{options[:name]}' not found in #{options[:filepath]}" if gem_info.nil?
-
-
# Start building the Gemfile content
-
4
gemfile_content = []
-
-
# Add Ruby version if specified
-
4
then: 1
else: 3
if options[:ruby_version]
-
1
gemfile_content << "ruby '#{options[:ruby_version]}'"
-
1
gemfile_content << ''
-
end
-
-
# Default source is always rubygems.org
-
4
gemfile_content << "source 'https://rubygems.org'"
-
4
gemfile_content << ''
-
-
# Group gems by their source
-
4
gems_by_source = {}
-
-
# Helper to add a gem to a source group
-
4
add_to_group = lambda { |name, version, source_info|
-
59
source_key = group_gems_by_source(name, version, source_info)
-
59
gems_by_source[source_key] ||= []
-
59
gems_by_source[source_key] << [name, version, source_info]
-
}
-
-
# Add main gem to its source group
-
4
add_to_group.call(gem_info['name'], gem_info['version'], gem_info)
-
-
# Add dependencies to their source groups
-
4
then: 4
else: 0
gem_info['dependencies']&.each do |dep_name, dep_version|
-
55
dep_result = analyzer.analyze(dep_name)
-
55
dep_info = dep_result[dep_name]
-
55
add_to_group.call(dep_name, dep_version, dep_info)
-
end
-
-
# Output gems grouped by source
-
4
gems_by_source.each do |source_key, gems|
-
7
else: 0
case source_key
-
when :default
-
when: 0
# Output default source gems directly
-
gems.each do |name, version, _|
-
gemfile_content << "gem '#{name}', '#{version}'"
-
end
-
else: 0
then: 0
gemfile_content << '' unless gems.empty?
-
when: 7
when Array
-
7
source_type, source_value = source_key
-
7
else: 0
case source_type
-
when: 6
when :source
-
6
gemfile_content << "source '#{source_value}' do"
-
6
gems.each do |name, version, _|
-
58
gemfile_content << " gem '#{name}', '#{version}'"
-
end
-
6
gemfile_content << 'end'
-
6
gemfile_content << ''
-
when: 0
when :git
-
gemfile_content << "git '#{source_value}' do"
-
gems.each do |name, _version, _|
-
gemfile_content << " gem '#{name}'"
-
end
-
gemfile_content << 'end'
-
gemfile_content << ''
-
when: 1
when :path
-
1
gems.each do |name, _, _|
-
1
gemfile_content << "gem '#{name}', path: '#{source_value}'"
-
end
-
1
gemfile_content << ''
-
end
-
end
-
end
-
-
4
puts gemfile_content.join("\n").strip
-
rescue Gempath::Error => e
-
raise Thor::Error,
-
"#{e.message}\n\nTo specify a different Gemfile.lock location:\n gempath generate -f /path/to/Gemfile.lock"
-
end
-
-
1
default_task :help
-
end
-
end