Class: Gemchain::Cascade
- Inherits:
-
Object
- Object
- Gemchain::Cascade
- Defined in:
- lib/gemchain/cascade.rb
Defined Under Namespace
Classes: Result
Instance Method Summary collapse
-
#check ⇒ Object
Returns a full ecosystem check.
-
#guard(gem_name) ⇒ Object
Pre-release guard: warns about gems that depend on
gem_name. -
#initialize(workspace, released_check: nil) ⇒ Cascade
constructor
A new instance of Cascade.
-
#link_content(workspace: nil) ⇒ Object
Generates Gemfile content with local path references for all workspace gems.
-
#released_on_rubygems?(name, version) ⇒ Boolean
True when
nameis published atversionon RubyGems. -
#update(gem_name, new_version, dry_run: false, test_only: false) ⇒ Object
Performs the full cascade update: 1.
Constructor Details
#initialize(workspace, released_check: nil) ⇒ Cascade
Returns a new instance of Cascade.
7 8 9 10 |
# File 'lib/gemchain/cascade.rb', line 7 def initialize(workspace, released_check: nil) @workspace = workspace @released_check = released_check || method(:released_on_rubygems?) end |
Instance Method Details
#check ⇒ Object
Returns a full ecosystem check.
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/gemchain/cascade.rb', line 31 def check gems_info = @workspace.gems.map do |gem| deps = @workspace.dependents_of(gem.name) { name: gem.name, version: gem.version.to_s, path: gem.path, dependencies: gem.dependencies, dependents: deps.map(&:name), dependent_count: deps.size } end {gems: gems_info} end |
#guard(gem_name) ⇒ Object
Pre-release guard: warns about gems that depend on gem_name.
Returns a hash with dependency info, or nil if gem not found.
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
# File 'lib/gemchain/cascade.rb', line 14 def guard(gem_name) gem = @workspace.find(gem_name) return nil unless gem direct = @workspace.dependents_of(gem_name) transitive = @workspace.transitive_dependents(gem_name) { gem: gem, direct_dependents: direct.size, dependents: direct, transitive_dependents: transitive, all_downstream: transitive.map { |n| @workspace.find(n) }.compact } end |
#link_content(workspace: nil) ⇒ Object
Generates Gemfile content with local path references for all workspace gems.
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/gemchain/cascade.rb', line 93 def link_content(workspace: nil) workspace ||= Dir.pwd lines = [] lines << "# Auto-generated by gemchain — local development path references" lines << "# Replace in your Gemfile to test against local copies" lines << "" lines << "# Add this block to your Gemfile:" lines << "ask_rb_root = File.expand_path(\"..\", __dir__)" if workspace lines << "" gem_names = @workspace.gems.map(&:name).sort lines << "%w[#{gem_names.join(' ')}].each do |gem_name|" lines << " gem gem_name, path: File.join(ask_rb_root, gem_name)" lines << "end" lines << "" lines.join("\n") end |
#released_on_rubygems?(name, version) ⇒ Boolean
True when name is published at version on RubyGems. Bounded by a
short timeout; when rubygems is unreachable, assume not published so
the full mode (bump → test → release) is preferred — bumping an
already-published version is a no-op, and the executor's release step
additionally skips re-releases of published versions.
117 118 119 120 121 122 123 124 |
# File 'lib/gemchain/cascade.rb', line 117 def released_on_rubygems?(name, version) require "json" require "net/http" body = Timeout.timeout(5) { Net::HTTP.get(URI("https://rubygems.org/api/v1/gems/#{name}.json")) } JSON.parse(body)["version"] == version rescue StandardError false end |
#update(gem_name, new_version, dry_run: false, test_only: false) ⇒ Object
Performs the full cascade update:
- If the source gem isn't already at
new_versionAND published on RubyGems: bump, test, release it (a version.rb match alone is not enough — a failed run can leave it bumped but unpublished) - For each dependent (topological order): update constraint, test, bump, release
With test_only: true, only the dependents' :test steps are emitted — verify compatibility without editing or releasing anything.
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/gemchain/cascade.rb', line 55 def update(gem_name, new_version, dry_run: false, test_only: false) gem = @workspace.find(gem_name) return Result.new(success: false, message: "Gem '#{gem_name}' not found in workspace") unless gem unless new_version.match?(/\A\d+\.\d+\.\d+\z/) return Result.new(success: false, message: "Invalid version: #{new_version.inspect} — expected X.Y.Z") end steps = [] order = @workspace.cascade_order(gem_name) if !test_only && (gem.version.to_s != new_version || !@released_check.call(gem_name, new_version)) # The source must be released before dependents' `bundle update` # can resolve the new version from RubyGems. steps << {action: :bump, gem: gem_name, version: new_version} steps << {action: :test, gem: gem_name} steps << {action: :release, gem: gem_name, version: new_version} end order.each do |dep_name| next if dep_name == gem_name dep_gem = @workspace.find(dep_name) next unless dep_gem next unless dep_gem.dependencies.key?(gem_name) if test_only steps << {action: :test, gem: dep_name, dependency: gem_name} else steps << {action: :update_dependency, gem: dep_name, dependency: gem_name, constraint: ">= #{new_version}"} steps << {action: :test, gem: dep_name, dependency: gem_name} steps << {action: :release, gem: dep_name} end end {steps: steps, dry_run: dry_run, test_only: test_only} end |