Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/gem_contribute/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ module CLI
GitHub project (e.g. `rubyevents/rubyevents`).
Lands on the default branch.
Flags: -e (editor), -a (AI tool).
open <gem> Open the gem's GitHub repo in your default browser.
The URL is also printed on stdout as a fallback.
fix <gem>/<issue#> Fork the gem's repo, clone the fork, branch from main.
Flags: -e (editor), -a (AI tool), --no-comment.
submit Push the current branch and open a pre-filled
Expand Down Expand Up @@ -63,6 +65,7 @@ def dispatch(command, argv, stdout:, stderr:)
"config" => ->(o, e) { Config.new(stdout: o, stderr: e) },
"auth" => ->(o, e) { Auth.new(stdout: o, stderr: e) },
"fork" => ->(o, e) { Fork.new(stdout: o, stderr: e, clone_root: GemContribute::Config.new.clone_root) },
"open" => ->(o, e) { Open.new(stdout: o, stderr: e) },
"fix" => lambda { |o, e|
config = GemContribute::Config.new
Fix.new(stdout: o, stderr: e, clone_root: config.clone_root, config: config)
Expand Down
44 changes: 44 additions & 0 deletions lib/gem_contribute/cli/open.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# frozen_string_literal: true

module GemContribute
module CLI
# `gem-contribute open <gem>` — print the gem's GitHub URL and try to open
# it in the default browser.
#
# Resolves `<gem>` the same way `issues` and `fix` do (RubyGems → Project,
# with the `gem-contribute` self short-circuit). The browser opener is the
# same platform-aware helper used by `auth login` and `submit`. As in those
# verbs, the opener is injected at construction so specs never shell out.
class Open
include PlatformTools
include Workflow

USAGE = "Usage: gem-contribute open <gem>"

def initialize(stdout: $stdout, stderr: $stderr, output: nil,
resolver: Resolver.new,
browser_opener: nil)
@output = output || Output::Standard.new(out: stdout, err: stderr)
@resolver = resolver
@browser_opener = browser_opener || method(:default_browser_opener)
end

def run(argv)
target = argv.shift
if target.nil?
@output.error(USAGE)
return 2
end

project = resolve_target(target, verb: "open")
return 1 if project.nil?

url = "https://github.com/#{project.owner}/#{project.repo}"
opened = @browser_opener.call(url)
@output.info(opened ? "Opened browser to:" : "Open this URL in your browser:")
@output.info(" #{url}")
0
end
end
end
end
76 changes: 76 additions & 0 deletions spec/gem_contribute/cli/open_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# frozen_string_literal: true

require "stringio"

RSpec.describe GemContribute::CLI::Open do
let(:stdout) { StringIO.new }
let(:stderr) { StringIO.new }
let(:resolver) { instance_double(GemContribute::Resolver) }
let(:opened_urls) { [] }
let(:browser_opener) do
lambda do |url|
opened_urls << url
true
end
end
let(:cli) do
described_class.new(stdout: stdout, stderr: stderr,
resolver: resolver, browser_opener: browser_opener)
end

let(:project) do
GemContribute::Project.new(
gem_name: "rubocop", host: "github.com",
owner: "rubocop", repo: "rubocop", metadata: {}
)
end

it "exits 2 with a usage message when no gem name is given" do
expect(cli.run([])).to eq(2)
expect(stderr.string).to include("Usage: gem-contribute open <gem>")
end

it "opens the resolved repo URL in the browser and prints it" do
allow(resolver).to receive(:resolve).and_return(project)

expect(cli.run(["rubocop"])).to eq(0)
expect(opened_urls).to eq(["https://github.com/rubocop/rubocop"])
out = stdout.string
expect(out).to include("Opened browser to:")
expect(out).to include("https://github.com/rubocop/rubocop")
end

it "still prints the URL when the browser opener returns false" do
allow(resolver).to receive(:resolve).and_return(project)
failing_opener = ->(_url) { false }
cli = described_class.new(stdout: stdout, stderr: stderr,
resolver: resolver, browser_opener: failing_opener)

expect(cli.run(["rubocop"])).to eq(0)
out = stdout.string
expect(out).to include("Open this URL in your browser:")
expect(out).to include("https://github.com/rubocop/rubocop")
end

it "exits 1 with a clear message when the gem resolves to a non-github.com host" do
other = GemContribute::Project.new(
gem_name: "internal", host: :unknown, owner: nil, repo: nil, metadata: {}
)
allow(resolver).to receive(:resolve).and_return(other)

expect(cli.run(["internal"])).to eq(1)
expect(stderr.string).to include("only github.com is supported")
expect(opened_urls).to be_empty
end

it "self-resolves `gem-contribute` without hitting the resolver" do
allow(resolver).to receive(:resolve)

expect(cli.run(["gem-contribute"])).to eq(0)
expect(resolver).not_to have_received(:resolve)
expect(opened_urls).to eq([
"https://github.com/#{GemContribute::SELF_PROJECT.owner}/" \
"#{GemContribute::SELF_PROJECT.repo}"
])
end
end
Loading