gem_kit-release 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/gem_kit/release/changelog.rb +313 -0
- data/lib/gem_kit/release/cli/bump.rb +133 -0
- data/lib/gem_kit/release/cli/changelog.rb +161 -0
- data/lib/gem_kit/release/cli/command.rb +68 -0
- data/lib/gem_kit/release/cli/deprecations.rb +102 -0
- data/lib/gem_kit/release/cli/release.rb +98 -0
- data/lib/gem_kit/release/cli/setup.rb +136 -0
- data/lib/gem_kit/release/cli/tag.rb +97 -0
- data/lib/gem_kit/release/cli.rb +121 -0
- data/lib/gem_kit/release/deprecate.rb +285 -0
- data/lib/gem_kit/release/gate.rb +178 -0
- data/lib/gem_kit/release/project.rb +188 -0
- data/lib/gem_kit/release/templates/DEPRECATIONS.md.erb +173 -0
- data/lib/gem_kit/release/templates/RELEASE.md.erb +159 -0
- data/lib/gem_kit/release/version.rb +7 -0
- data/lib/gem_kit/release/version_file.rb +133 -0
- data/lib/gem_kit/release.rb +77 -0
- data/lib/rubygems/commands/kit_command.rb +111 -0
- data/lib/rubygems_plugin.rb +11 -0
- metadata +77 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "command"
|
|
4
|
+
|
|
5
|
+
module GemKit
|
|
6
|
+
module Release
|
|
7
|
+
module CLI
|
|
8
|
+
# Without a version: everything outstanding. With one: only what comes
|
|
9
|
+
# due there, failing if anything does — which is what makes it usable as
|
|
10
|
+
# a CI gate.
|
|
11
|
+
class Deprecations < Command
|
|
12
|
+
desc "List the deprecations this gem has not yet honoured"
|
|
13
|
+
|
|
14
|
+
argument :version,
|
|
15
|
+
type: :string, required: false,
|
|
16
|
+
desc: "List only what comes due at this version, and fail if any does"
|
|
17
|
+
|
|
18
|
+
example [
|
|
19
|
+
" # everything outstanding, by removal version",
|
|
20
|
+
"5.0.0 # only what comes due at 5.0.0; fails if any does",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
def call(version: nil, **)
|
|
24
|
+
project.load!
|
|
25
|
+
registry = Deprecate.registry
|
|
26
|
+
|
|
27
|
+
if registry.empty?
|
|
28
|
+
say("No deprecations declared.")
|
|
29
|
+
return
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
if version.nil?
|
|
33
|
+
say("#{registry.size} outstanding deprecation(s) (current version #{project.version}):")
|
|
34
|
+
return render(registry)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
due = Deprecate.pending(version)
|
|
38
|
+
if due.empty?
|
|
39
|
+
say("No deprecations come due at #{version}. (#{registry.size} outstanding overall.)")
|
|
40
|
+
return
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
refuse("#{due.size} deprecation(s) come due at #{version} and must be removed first:", lines(due))
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
__END__
|
|
51
|
+
|
|
52
|
+
describe "gem_kit/release/cli/deprecations" do
|
|
53
|
+
require_relative "../../../../spec/support/gem_kit_release_spec"
|
|
54
|
+
extend GemKitReleaseSpec
|
|
55
|
+
|
|
56
|
+
it "says so when nothing is declared" do
|
|
57
|
+
with_gem do |dir|
|
|
58
|
+
status, out, _err = invoke(["deprecations"], dir)
|
|
59
|
+
status.should == 0
|
|
60
|
+
out.should.match(/No deprecations declared/)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it "lists everything outstanding with its deadline" do
|
|
65
|
+
with_gem do |dir|
|
|
66
|
+
status, out, _err = invoke(["deprecations"], dir,
|
|
67
|
+
deprecations: [["Old", "New", "2.0"], ["Older", "Newer", "3.0"]])
|
|
68
|
+
|
|
69
|
+
status.should == 0
|
|
70
|
+
out.should.match(/2 outstanding deprecation\(s\) \(current version 1\.2\.3\)/)
|
|
71
|
+
out.should.match(/2\.0\s+Old -> New/)
|
|
72
|
+
out.should.match(/3\.0\s+Older -> Newer/)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it "fails for a version with deprecations due, naming only those" do
|
|
77
|
+
with_gem do |dir|
|
|
78
|
+
status, _out, err = invoke(["deprecations", "2.0.0"], dir,
|
|
79
|
+
deprecations: [["Old", "New", "2.0"], ["Later", "Newer", "3.0"]])
|
|
80
|
+
|
|
81
|
+
status.should == 1
|
|
82
|
+
err.should.match(/1 deprecation\(s\) come due at 2\.0\.0/)
|
|
83
|
+
err.should.match(/Old -> New/)
|
|
84
|
+
err.should.not.match(/Later/)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it "passes for a version with nothing due, but says what remains" do
|
|
89
|
+
with_gem do |dir|
|
|
90
|
+
status, out, _err = invoke(["deprecations", "1.5.0"], dir, deprecations: [["Old", "New", "2.0"]])
|
|
91
|
+
|
|
92
|
+
status.should == 0
|
|
93
|
+
out.should.match(/No deprecations come due at 1\.5\.0\. \(1 outstanding overall\.\)/)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it "is reachable by its alias" do
|
|
98
|
+
with_gem do |dir|
|
|
99
|
+
invoke(["deps"], dir).first.should == 0
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "command"
|
|
4
|
+
|
|
5
|
+
module GemKit
|
|
6
|
+
module Release
|
|
7
|
+
module CLI
|
|
8
|
+
# Gate, build, push. The gates run before anything is built, because a
|
|
9
|
+
# half-done release is worse than a refused one.
|
|
10
|
+
class Release < Command
|
|
11
|
+
desc "Gate, build and push this gem"
|
|
12
|
+
|
|
13
|
+
option :dry_run, type: :boolean, default: false, desc: "Run the gates and stop"
|
|
14
|
+
option :tag, type: :boolean, default: false, desc: "Tag the release after pushing"
|
|
15
|
+
|
|
16
|
+
example [
|
|
17
|
+
"--dry-run # run the gates; what belongs in CI",
|
|
18
|
+
"--tag # release, then tag it",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
def call(**options)
|
|
22
|
+
version = project.version.to_s
|
|
23
|
+
problems = gate.release_problems(version)
|
|
24
|
+
|
|
25
|
+
refuse("Refusing to release #{project.name} #{version}:", problems) unless problems.empty?
|
|
26
|
+
|
|
27
|
+
if options[:dry_run]
|
|
28
|
+
say("#{project.name} #{version} is ready to release.")
|
|
29
|
+
return
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
gemspec = File.basename(project.gemspec_path)
|
|
33
|
+
package = "#{project.name}-#{version}.gem"
|
|
34
|
+
|
|
35
|
+
Dir.chdir(project.root) do
|
|
36
|
+
say("Building #{gemspec}…")
|
|
37
|
+
fail_with("gem build failed") unless system("gem", "build", gemspec)
|
|
38
|
+
|
|
39
|
+
say("Pushing #{package}…")
|
|
40
|
+
fail_with("gem push failed") unless system("gem", "push", package)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
say("Released #{project.name} #{version}")
|
|
44
|
+
|
|
45
|
+
Tag.new.call if options[:tag]
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
__END__
|
|
53
|
+
|
|
54
|
+
describe "gem_kit/release/cli/release" do
|
|
55
|
+
require_relative "../../../../spec/support/gem_kit_release_spec"
|
|
56
|
+
extend GemKitReleaseSpec
|
|
57
|
+
|
|
58
|
+
it "--dry-run passes when the changelog documents the version" do
|
|
59
|
+
with_gem do |dir|
|
|
60
|
+
status, out, _err = invoke(["release", "--dry-run"], dir)
|
|
61
|
+
status.should == 0
|
|
62
|
+
out.should.match(/demo 1\.2\.3 is ready to release/)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "refuses without a changelog entry, before building anything" do
|
|
67
|
+
with_gem(changelog: :none) do |dir|
|
|
68
|
+
status, _out, err = invoke(["release"], dir)
|
|
69
|
+
|
|
70
|
+
status.should == 1
|
|
71
|
+
err.should.match(/Refusing to release demo 1\.2\.3/)
|
|
72
|
+
err.should.match(/does not exist/)
|
|
73
|
+
Dir[File.join(dir, "*.gem")].should.be.empty
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it "refuses when a deprecation is due in this version, before building anything" do
|
|
78
|
+
with_gem do |dir|
|
|
79
|
+
status, _out, err = invoke(["release"], dir, deprecations: [["Old", "New", "1.0"]])
|
|
80
|
+
|
|
81
|
+
status.should == 1
|
|
82
|
+
err.should.match(/deprecation due in 1\.2\.3: .*Old -> New/)
|
|
83
|
+
Dir[File.join(dir, "*.gem")].should.be.empty
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it "does not gate on a deprecation that is not yet due" do
|
|
88
|
+
with_gem do |dir|
|
|
89
|
+
invoke(["release", "--dry-run"], dir, deprecations: [["Old", "New", "9.0"]]).first.should == 0
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it "reports a directory with no gemspec" do
|
|
94
|
+
Dir.mktmpdir do |dir|
|
|
95
|
+
invoke(["release", "--dry-run"], dir).first.should == 1
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "erb"
|
|
4
|
+
|
|
5
|
+
require_relative "command"
|
|
6
|
+
|
|
7
|
+
module GemKit
|
|
8
|
+
module Release
|
|
9
|
+
module CLI
|
|
10
|
+
# Copies the two documents that describe this toolchain into the project,
|
|
11
|
+
# rendered for its name and version.
|
|
12
|
+
#
|
|
13
|
+
# They are copies rather than links because a policy nobody can read in
|
|
14
|
+
# their own repo is a policy nobody follows, and because the examples are
|
|
15
|
+
# worth more when they use the project's real version numbers.
|
|
16
|
+
class Setup < Command
|
|
17
|
+
TEMPLATES = File.expand_path("../templates", __dir__)
|
|
18
|
+
|
|
19
|
+
DOCUMENTS = %w[DEPRECATIONS.md RELEASE.md].freeze
|
|
20
|
+
|
|
21
|
+
desc "Copy DEPRECATIONS.md and RELEASE.md into this project"
|
|
22
|
+
|
|
23
|
+
option :force, type: :boolean, default: false, desc: "Overwrite documents that already exist"
|
|
24
|
+
|
|
25
|
+
example [
|
|
26
|
+
" # write the documents that are missing",
|
|
27
|
+
"--force # refresh them all, e.g. after a major version",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
def call(**options)
|
|
31
|
+
written, skipped = [], []
|
|
32
|
+
|
|
33
|
+
DOCUMENTS.each do |document|
|
|
34
|
+
destination = File.join(project.root, document)
|
|
35
|
+
|
|
36
|
+
if File.exist?(destination) && !options[:force]
|
|
37
|
+
skipped << document
|
|
38
|
+
next
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
File.write(destination, render_document(document))
|
|
42
|
+
written << document
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
written.each { |document| say("Wrote #{document}") }
|
|
46
|
+
|
|
47
|
+
unless skipped.empty?
|
|
48
|
+
say("Left alone (already present): #{skipped.join(", ")}")
|
|
49
|
+
say("Pass --force to overwrite.")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
say("Link them from your README and AGENTS.md so they are found.") unless written.empty?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
# Values the templates may use. Kept small and obvious — a template
|
|
58
|
+
# needing more than this is documenting the tool, not the project.
|
|
59
|
+
def render_document(document)
|
|
60
|
+
name = project.name
|
|
61
|
+
version = project.version.to_s
|
|
62
|
+
next_major = project.next_major_version
|
|
63
|
+
changelog = File.basename(project.changelog_path)
|
|
64
|
+
version_file = relative(project.version_file)
|
|
65
|
+
test = project.test_command
|
|
66
|
+
today = Time.now.strftime("%Y-%m-%d")
|
|
67
|
+
|
|
68
|
+
template = File.read(File.join(TEMPLATES, "#{document}.erb"))
|
|
69
|
+
ERB.new(template, trim_mode: "-").result(binding)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
__END__
|
|
77
|
+
|
|
78
|
+
describe "gem_kit/release/cli/setup" do
|
|
79
|
+
require_relative "../../../../spec/support/gem_kit_release_spec"
|
|
80
|
+
extend GemKitReleaseSpec
|
|
81
|
+
|
|
82
|
+
it "writes both documents into the project root" do
|
|
83
|
+
with_gem do |dir|
|
|
84
|
+
status, out, _err = invoke(["setup"], dir)
|
|
85
|
+
|
|
86
|
+
status.should == 0
|
|
87
|
+
out.should.match(/Wrote DEPRECATIONS\.md/)
|
|
88
|
+
out.should.match(/Wrote RELEASE\.md/)
|
|
89
|
+
File.exist?(File.join(dir, "DEPRECATIONS.md")).should.be.true
|
|
90
|
+
File.exist?(File.join(dir, "RELEASE.md")).should.be.true
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it "renders them for this project's name and versions" do
|
|
95
|
+
with_gem(version: "4.1.0") do |dir|
|
|
96
|
+
invoke(["setup"], dir)
|
|
97
|
+
|
|
98
|
+
deprecations = File.read(File.join(dir, "DEPRECATIONS.md"))
|
|
99
|
+
deprecations.should.match(/A deprecation in demo is a \*\*dated promise\*\*/)
|
|
100
|
+
deprecations.should.match(/4\.1\.0, so the usual deadline is 5/)
|
|
101
|
+
deprecations.should.not.match(/<%=/)
|
|
102
|
+
|
|
103
|
+
release = File.read(File.join(dir, "RELEASE.md"))
|
|
104
|
+
release.should.match(/demo/)
|
|
105
|
+
release.should.not.match(/<%=/)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it "leaves existing documents alone and says so" do
|
|
110
|
+
with_gem do |dir|
|
|
111
|
+
File.write(File.join(dir, "RELEASE.md"), "mine\n")
|
|
112
|
+
|
|
113
|
+
status, out, _err = invoke(["setup"], dir)
|
|
114
|
+
|
|
115
|
+
status.should == 0
|
|
116
|
+
out.should.match(/Left alone \(already present\): RELEASE\.md/)
|
|
117
|
+
File.read(File.join(dir, "RELEASE.md")).should == "mine\n"
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "--force overwrites them" do
|
|
122
|
+
with_gem do |dir|
|
|
123
|
+
File.write(File.join(dir, "RELEASE.md"), "mine\n")
|
|
124
|
+
|
|
125
|
+
invoke(["setup", "--force"], dir).first.should == 0
|
|
126
|
+
File.read(File.join(dir, "RELEASE.md")).should.not == "mine\n"
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it "is reachable by its alias" do
|
|
131
|
+
with_gem do |dir|
|
|
132
|
+
invoke(["init"], dir).first.should == 0
|
|
133
|
+
File.exist?(File.join(dir, "RELEASE.md")).should.be.true
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "command"
|
|
4
|
+
|
|
5
|
+
module GemKit
|
|
6
|
+
module Release
|
|
7
|
+
module CLI
|
|
8
|
+
# The tag matters beyond bookkeeping: it is what the next changelog is
|
|
9
|
+
# written against, so a missing one makes the following release harder to
|
|
10
|
+
# describe.
|
|
11
|
+
class Tag < Command
|
|
12
|
+
desc "Tag the current version in git"
|
|
13
|
+
|
|
14
|
+
option :push, type: :boolean, default: false, desc: "Push the tag to origin"
|
|
15
|
+
option :prefix, type: :string, default: "v", desc: "Tag name prefix"
|
|
16
|
+
|
|
17
|
+
example [
|
|
18
|
+
" # tag v4.1.0",
|
|
19
|
+
"--push # ...and push it",
|
|
20
|
+
"--prefix release- # tag release-4.1.0",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
def call(**options)
|
|
24
|
+
tag = "#{options.fetch(:prefix, "v")}#{project.version}"
|
|
25
|
+
|
|
26
|
+
Dir.chdir(project.root) do
|
|
27
|
+
fail_with("not a git repository") unless system("git rev-parse --git-dir >/dev/null 2>&1")
|
|
28
|
+
fail_with("tag #{tag} already exists") unless `git tag -l #{tag}`.strip.empty?
|
|
29
|
+
fail_with("could not create tag #{tag}") unless system("git", "tag", "-a", tag, "-m", tag)
|
|
30
|
+
|
|
31
|
+
say("Tagged #{tag}")
|
|
32
|
+
|
|
33
|
+
if options[:push]
|
|
34
|
+
fail_with("could not push #{tag}") unless system("git", "push", "origin", tag)
|
|
35
|
+
say("Pushed #{tag}")
|
|
36
|
+
else
|
|
37
|
+
say("Push it with: git push origin #{tag}")
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
__END__
|
|
47
|
+
|
|
48
|
+
describe "gem_kit/release/cli/tag" do
|
|
49
|
+
require_relative "../../../../spec/support/gem_kit_release_spec"
|
|
50
|
+
extend GemKitReleaseSpec
|
|
51
|
+
|
|
52
|
+
# git needs an identity and at least one commit before it will tag.
|
|
53
|
+
as_repo = lambda do |dir|
|
|
54
|
+
system("git init -q #{dir}")
|
|
55
|
+
system("git -C #{dir} -c user.name=t -c user.email=t@t commit -q --allow-empty -m init")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "tags the current version and says how to push it" do
|
|
59
|
+
with_gem do |dir|
|
|
60
|
+
as_repo.call(dir)
|
|
61
|
+
|
|
62
|
+
status, out, _err = invoke(["tag"], dir)
|
|
63
|
+
status.should == 0
|
|
64
|
+
out.should.match(/Tagged v1\.2\.3/)
|
|
65
|
+
out.should.match(/git push origin v1\.2\.3/)
|
|
66
|
+
`git -C #{dir} tag -l`.strip.should == "v1.2.3"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it "honours a custom prefix" do
|
|
71
|
+
with_gem do |dir|
|
|
72
|
+
as_repo.call(dir)
|
|
73
|
+
|
|
74
|
+
invoke(["tag", "--prefix", "release-"], dir).first.should == 0
|
|
75
|
+
`git -C #{dir} tag -l`.strip.should == "release-1.2.3"
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "refuses to retag an existing version" do
|
|
80
|
+
with_gem do |dir|
|
|
81
|
+
as_repo.call(dir)
|
|
82
|
+
invoke(["tag"], dir)
|
|
83
|
+
|
|
84
|
+
status, _out, err = invoke(["tag"], dir)
|
|
85
|
+
status.should == 1
|
|
86
|
+
err.should.match(/tag v1\.2\.3 already exists/)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it "reports a directory that is not a git repository" do
|
|
91
|
+
with_gem do |dir|
|
|
92
|
+
status, _out, err = invoke(["tag"], dir)
|
|
93
|
+
status.should == 1
|
|
94
|
+
err.should.match(/not a git repository/)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/cli"
|
|
4
|
+
|
|
5
|
+
require_relative "cli/command"
|
|
6
|
+
require_relative "cli/bump"
|
|
7
|
+
require_relative "cli/changelog"
|
|
8
|
+
require_relative "cli/deprecations"
|
|
9
|
+
require_relative "cli/release"
|
|
10
|
+
require_relative "cli/setup"
|
|
11
|
+
require_relative "cli/tag"
|
|
12
|
+
|
|
13
|
+
module GemKit
|
|
14
|
+
module Release
|
|
15
|
+
# The command registry. dry-cli gives us the parts a hand-rolled parser
|
|
16
|
+
# always ends up needing badly: per-command help pages built from the
|
|
17
|
+
# `argument`/`option`/`example` declarations, value validation, and a
|
|
18
|
+
# listing of the subcommands.
|
|
19
|
+
#
|
|
20
|
+
# gem kit the listing
|
|
21
|
+
# gem kit bump --help that command's arguments, options, examples
|
|
22
|
+
#
|
|
23
|
+
module CLI
|
|
24
|
+
extend Dry::CLI::Registry
|
|
25
|
+
|
|
26
|
+
register "setup", Setup, aliases: ["init"]
|
|
27
|
+
register "bump", Bump
|
|
28
|
+
register "changelog", Changelog, aliases: ["log"]
|
|
29
|
+
register "deprecations", Deprecations, aliases: ["deps"]
|
|
30
|
+
register "release", Release
|
|
31
|
+
register "tag", Tag
|
|
32
|
+
|
|
33
|
+
# How the commands are reached, which is not something dry-cli can work
|
|
34
|
+
# out: it takes the program name from $PROGRAM_NAME, and ours is `gem`.
|
|
35
|
+
PROGRAM_NAME = "gem kit"
|
|
36
|
+
|
|
37
|
+
# Run one invocation. Returns an exit status rather than exiting, so the
|
|
38
|
+
# `gem kit` bridge can hand it to terminate_interaction and the specs can
|
|
39
|
+
# assert on it.
|
|
40
|
+
def self.run(arguments, out: $stdout, err: $stderr)
|
|
41
|
+
with_program_name do
|
|
42
|
+
Dry::CLI.new(self).call(arguments: arguments, out: out, err: err)
|
|
43
|
+
end
|
|
44
|
+
0
|
|
45
|
+
rescue Failure => failure
|
|
46
|
+
err.puts(failure.message)
|
|
47
|
+
1
|
|
48
|
+
rescue SystemExit => exit_exception
|
|
49
|
+
# dry-cli exits on an unknown command or a bad argument value.
|
|
50
|
+
exit_exception.status
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# dry-cli renders usage as `File.basename($PROGRAM_NAME) <command>`, so
|
|
54
|
+
# every help page would otherwise say `gem bump` — or, under the test
|
|
55
|
+
# runner, `-e`. Basename of "gem kit" is "gem kit", which is exactly the
|
|
56
|
+
# string we want.
|
|
57
|
+
def self.with_program_name
|
|
58
|
+
original = $PROGRAM_NAME
|
|
59
|
+
$PROGRAM_NAME = PROGRAM_NAME
|
|
60
|
+
yield
|
|
61
|
+
ensure
|
|
62
|
+
$PROGRAM_NAME = original
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
__END__
|
|
69
|
+
|
|
70
|
+
describe "gem_kit/release/cli" do
|
|
71
|
+
require_relative "../../../spec/support/gem_kit_release_spec"
|
|
72
|
+
extend GemKitReleaseSpec
|
|
73
|
+
|
|
74
|
+
# With no subcommand dry-cli treats it as a usage error: the listing goes to
|
|
75
|
+
# stderr and the status is non-zero, the way `git` alone behaves.
|
|
76
|
+
it "lists every subcommand with its description" do
|
|
77
|
+
with_gem do |dir|
|
|
78
|
+
status, _out, err = invoke([], dir)
|
|
79
|
+
|
|
80
|
+
status.should.not == 0
|
|
81
|
+
%w[setup bump changelog deprecations release tag].each do |name|
|
|
82
|
+
err.should.match(/#{name}/)
|
|
83
|
+
end
|
|
84
|
+
err.should.match(/Move the gem version/)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it "names the commands `gem kit <subcommand>`, not the running program" do
|
|
89
|
+
with_gem do |dir|
|
|
90
|
+
_status, _out, err = invoke([], dir)
|
|
91
|
+
|
|
92
|
+
err.should.match(/gem kit bump \[SEGMENT\]/)
|
|
93
|
+
err.should.not.match(/^\s+-e /)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it "restores $PROGRAM_NAME afterwards" do
|
|
98
|
+
with_gem do |dir|
|
|
99
|
+
before = $PROGRAM_NAME
|
|
100
|
+
invoke([], dir)
|
|
101
|
+
$PROGRAM_NAME.should == before
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it "prints a subcommand's arguments, options and examples for --help" do
|
|
106
|
+
with_gem do |dir|
|
|
107
|
+
_status, out, _err = invoke(["bump", "--help"], dir)
|
|
108
|
+
|
|
109
|
+
out.should.match(/gem kit bump/)
|
|
110
|
+
out.should.match(/SEGMENT/)
|
|
111
|
+
out.should.match(/--force/)
|
|
112
|
+
out.should.match(/Examples:/)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it "fails on an unknown subcommand" do
|
|
117
|
+
with_gem do |dir|
|
|
118
|
+
invoke(["nonsense"], dir).first.should.not == 0
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|