gem_kit-release 0.1.0 → 0.2.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 +4 -4
- data/LICENSE +201 -0
- data/README.md +191 -0
- data/lib/gem_kit/release/changelog.rb +6 -2
- data/lib/gem_kit/release/cli.rb +228 -76
- data/lib/gem_kit/release/{cli → commands}/bump.rb +3 -16
- data/lib/gem_kit/release/{cli → commands}/changelog.rb +3 -15
- data/lib/gem_kit/release/{cli → commands}/command.rb +18 -14
- data/lib/gem_kit/release/{cli → commands}/deprecations.rb +4 -12
- data/lib/gem_kit/release/{cli → commands}/release.rb +4 -11
- data/lib/gem_kit/release/{cli → commands}/tag.rb +26 -12
- data/lib/gem_kit/release/gate.rb +7 -7
- data/lib/gem_kit/release/generators/setup.rb +251 -0
- data/lib/gem_kit/release/project.rb +126 -9
- data/lib/gem_kit/release/templates/DEPRECATIONS.md.erb +19 -20
- data/lib/gem_kit/release/templates/RELEASE.md.erb +20 -20
- data/lib/gem_kit/release/version.rb +1 -1
- data/lib/gem_kit/release.rb +23 -11
- data/lib/rubygems/commands/kit_command.rb +1 -1
- metadata +50 -19
- data/lib/gem_kit/release/cli/setup.rb +0 -136
- data/lib/gem_kit/release/deprecate.rb +0 -285
data/lib/gem_kit/release/cli.rb
CHANGED
|
@@ -1,66 +1,180 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "
|
|
3
|
+
require "thor"
|
|
4
4
|
|
|
5
|
-
require_relative "
|
|
6
|
-
require_relative "
|
|
7
|
-
require_relative "
|
|
8
|
-
require_relative "
|
|
9
|
-
require_relative "
|
|
10
|
-
require_relative "
|
|
11
|
-
require_relative "
|
|
5
|
+
require_relative "../../gem_kit"
|
|
6
|
+
require_relative "version"
|
|
7
|
+
require_relative "changelog"
|
|
8
|
+
require_relative "project"
|
|
9
|
+
require_relative "version_file"
|
|
10
|
+
require_relative "gate"
|
|
11
|
+
require_relative "commands/command"
|
|
12
|
+
require_relative "commands/bump"
|
|
13
|
+
require_relative "commands/changelog"
|
|
14
|
+
require_relative "commands/deprecations"
|
|
15
|
+
require_relative "commands/release"
|
|
16
|
+
require_relative "commands/tag"
|
|
17
|
+
require_relative "generators/setup"
|
|
12
18
|
|
|
13
19
|
module GemKit
|
|
14
20
|
module Release
|
|
15
|
-
# The command
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
21
|
+
# The command line behind `gem kit`, on Thor.
|
|
22
|
+
#
|
|
23
|
+
# Thor gives us the parts a hand-rolled parser always ends up needing
|
|
24
|
+
# badly: per-command help built from the `desc`/`method_option`
|
|
25
|
+
# declarations, a subcommand listing, and — the reason for choosing it over
|
|
26
|
+
# a plain option parser — Thor::Group and Thor::Actions, the same generator
|
|
27
|
+
# machinery Rails' `rails generate` is built on. `gem kit setup` is a
|
|
28
|
+
# generator, so it gets create/identical/conflict reporting and the
|
|
29
|
+
# overwrite prompt without writing any of it.
|
|
19
30
|
#
|
|
20
31
|
# gem kit the listing
|
|
21
|
-
# gem kit bump
|
|
32
|
+
# gem kit help bump that command's options
|
|
22
33
|
#
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|
34
|
+
# Each command here is a thin front for a plain object under CLI::; the
|
|
35
|
+
# work itself is in Gate, VersionFile, Changelog and Deprecate.
|
|
36
|
+
class CLI < Thor
|
|
37
|
+
class_option :gem, type: :string,
|
|
38
|
+
desc: "Which gem, in a repository holding more than one gemspec"
|
|
39
|
+
|
|
40
|
+
# Thor asks; without it every invocation warns.
|
|
41
|
+
def self.exit_on_failure? = true
|
|
42
|
+
|
|
43
|
+
# `gem kit` with no arguments lists the commands rather than erroring.
|
|
44
|
+
def self.banner(command, _namespace = nil, _subcommand = false)
|
|
45
|
+
"gem kit #{command.usage}"
|
|
51
46
|
end
|
|
52
47
|
|
|
53
|
-
#
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
48
|
+
# Short forms for the three typed most often.
|
|
49
|
+
map "log" => :changelog,
|
|
50
|
+
"deps" => :deprecations,
|
|
51
|
+
"init" => :setup
|
|
52
|
+
|
|
53
|
+
register(Generators::Setup, "setup", "setup",
|
|
54
|
+
"Copy DEPRECATIONS.md and RELEASE.md into this project")
|
|
55
|
+
|
|
56
|
+
desc "bump [SEGMENT]", "Move the gem version, refusing to bump onto a deprecation deadline"
|
|
57
|
+
long_desc <<~TXT
|
|
58
|
+
Rewrites the gem's version file — by rendering its .erb template if there is
|
|
59
|
+
one, otherwise by substituting the version literal in place.
|
|
60
|
+
|
|
61
|
+
Refuses to bump onto a deprecation deadline. A deprecation names the version
|
|
62
|
+
the old name stops existing in; bumping to that version while the old name is
|
|
63
|
+
still in the tree breaks that promise. Remove the deprecated code first, or
|
|
64
|
+
pass --force, which says what it is overriding.
|
|
65
|
+
TXT
|
|
66
|
+
method_option :force, type: :boolean, default: false, aliases: "-f",
|
|
67
|
+
desc: "Bump even when a deprecation comes due"
|
|
68
|
+
def bump(segment = "patch")
|
|
69
|
+
Commands::Bump.new(options).call(segment)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
desc "changelog [VERSION]", "Lint CHANGELOG.md, or have an AI CLI write this version's entry"
|
|
73
|
+
long_desc <<~TXT
|
|
74
|
+
Checks CHANGELOG.md against Keep a Changelog: the title, that every heading is
|
|
75
|
+
`## [Unreleased]` or `## [1.2.3] - YYYY-MM-DD`, that versions are valid, dated,
|
|
76
|
+
unique and ordered newest-first, and that ### sections are one of Added,
|
|
77
|
+
Changed, Deprecated, Removed, Fixed, Security — none of them empty.
|
|
78
|
+
|
|
79
|
+
Given a version, it also asks whether that version has a non-empty section of
|
|
80
|
+
its own sitting at the top of the released list.
|
|
81
|
+
|
|
82
|
+
With --write, hands the job to the CLI named by config.changelog_writer
|
|
83
|
+
(default: claude). Review what it writes.
|
|
84
|
+
TXT
|
|
85
|
+
method_option :write, type: :boolean, default: false, aliases: "-w",
|
|
86
|
+
desc: "Ask the configured AI CLI to write this version's entry"
|
|
87
|
+
def changelog(version = nil)
|
|
88
|
+
Commands::Changelog.new(options).call(version)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
desc "deprecations [VERSION]", "List the deprecations this gem has not yet honoured"
|
|
92
|
+
long_desc <<~TXT
|
|
93
|
+
A deprecation names its replacement and the version the old name stops
|
|
94
|
+
existing in. This lists the promises still outstanding, with the source
|
|
95
|
+
location of each declaration.
|
|
96
|
+
|
|
97
|
+
Given a version, it lists only what comes due there and fails if anything
|
|
98
|
+
does, which is what makes it usable as a gate in CI.
|
|
99
|
+
TXT
|
|
100
|
+
def deprecations(version = nil)
|
|
101
|
+
Commands::Deprecations.new(options).call(version)
|
|
63
102
|
end
|
|
103
|
+
|
|
104
|
+
desc "release", "Gate, build and push this gem"
|
|
105
|
+
long_desc <<~TXT
|
|
106
|
+
Two gates, both before `gem build` runs:
|
|
107
|
+
|
|
108
|
+
the changelog must have a non-empty, correctly formatted section for this
|
|
109
|
+
version, sitting at the top of the released list; and nothing promised to
|
|
110
|
+
disappear in this version may still be in the tree.
|
|
111
|
+
|
|
112
|
+
Then builds the gemspec and pushes it. Requires RubyGems push credentials.
|
|
113
|
+
--dry-run runs the gates and stops, which is what belongs in CI.
|
|
114
|
+
TXT
|
|
115
|
+
method_option :dry_run, type: :boolean, default: false, aliases: "-n",
|
|
116
|
+
desc: "Run the gates and stop"
|
|
117
|
+
method_option :tag, type: :boolean, default: false, aliases: "-t",
|
|
118
|
+
desc: "Tag the release after pushing"
|
|
119
|
+
def release
|
|
120
|
+
Commands::Release.new(options).call
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
desc "tag", "Tag the current version in git"
|
|
124
|
+
long_desc <<~TXT
|
|
125
|
+
Creates the annotated tag <prefix><version> — v4.1.0 by default — for the
|
|
126
|
+
version in the gem's version file, refusing if that tag already exists.
|
|
127
|
+
|
|
128
|
+
The tag is what the next changelog is written against, so a missing one makes
|
|
129
|
+
the following release harder to describe.
|
|
130
|
+
TXT
|
|
131
|
+
method_option :push, type: :boolean, default: false, aliases: "-p",
|
|
132
|
+
desc: "Push the tag to origin"
|
|
133
|
+
method_option :prefix, type: :string,
|
|
134
|
+
desc: "Tag name prefix (default: v, or <gem>-v in a multi-gem repository)"
|
|
135
|
+
def tag
|
|
136
|
+
Commands::Tag.new(options).call
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# The extension seam. A plugin gem adds commands to `gem kit` by reopening
|
|
141
|
+
# the CLI through this, which is a documented entry point rather than a
|
|
142
|
+
# class_eval someone reverse-engineered:
|
|
143
|
+
#
|
|
144
|
+
# # lib/gem_kit/plugin.rb, in a gem of your own
|
|
145
|
+
# require "gem_kit/release/cli"
|
|
146
|
+
#
|
|
147
|
+
# GemKit::Release.plugin do
|
|
148
|
+
# desc "lint", "Check this gem for the things gems get wrong"
|
|
149
|
+
# def lint = GemKit::Plugin::Lint.new(options).call
|
|
150
|
+
# end
|
|
151
|
+
#
|
|
152
|
+
# The block is evaluated on the Thor class, so everything Thor offers is
|
|
153
|
+
# in scope: `desc`, `long_desc`, `method_option`, `map`, and `register` for
|
|
154
|
+
# a Thor::Group generator. Commands added this way are indistinguishable
|
|
155
|
+
# from the built-in ones — they appear in `gem kit`, take `--gem`, and get
|
|
156
|
+
# a help page.
|
|
157
|
+
#
|
|
158
|
+
# Loading is the plugin's own business: ship a lib/rubygems_plugin.rb that
|
|
159
|
+
# requires your file, and RubyGems will load it on every `gem` invocation,
|
|
160
|
+
# the same way this gem gets loaded.
|
|
161
|
+
def self.plugin(&block)
|
|
162
|
+
CLI.class_eval(&block)
|
|
163
|
+
CLI
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Run one invocation. Returns an exit status rather than exiting, so the
|
|
167
|
+
# `gem kit` bridge can hand it to terminate_interaction and the specs can
|
|
168
|
+
# assert on it.
|
|
169
|
+
def self.run(arguments, out: $stdout, err: $stderr)
|
|
170
|
+
CLI.start(arguments)
|
|
171
|
+
0
|
|
172
|
+
rescue Failure => failure
|
|
173
|
+
err.puts(failure.message)
|
|
174
|
+
1
|
|
175
|
+
rescue SystemExit => exit_exception
|
|
176
|
+
# Thor exits on an unknown command or a missing required argument.
|
|
177
|
+
exit_exception.status
|
|
64
178
|
end
|
|
65
179
|
end
|
|
66
180
|
end
|
|
@@ -71,51 +185,89 @@ describe "gem_kit/release/cli" do
|
|
|
71
185
|
require_relative "../../../spec/support/gem_kit_release_spec"
|
|
72
186
|
extend GemKitReleaseSpec
|
|
73
187
|
|
|
74
|
-
|
|
75
|
-
# stderr and the status is non-zero, the way `git` alone behaves.
|
|
76
|
-
it "lists every subcommand with its description" do
|
|
188
|
+
it "lists every command with its description" do
|
|
77
189
|
with_gem do |dir|
|
|
78
|
-
|
|
190
|
+
_status, out, _err = invoke([], dir)
|
|
79
191
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
err.should.match(/#{name}/)
|
|
83
|
-
end
|
|
84
|
-
err.should.match(/Move the gem version/)
|
|
192
|
+
%w[setup bump changelog deprecations release tag].each { |name| out.should.match(/#{name}/) }
|
|
193
|
+
out.should.match(/Move the gem version/)
|
|
85
194
|
end
|
|
86
195
|
end
|
|
87
196
|
|
|
88
|
-
it "names the commands `gem kit <
|
|
197
|
+
it "names the commands `gem kit <command>`, not the running program" do
|
|
89
198
|
with_gem do |dir|
|
|
90
|
-
_status,
|
|
91
|
-
|
|
92
|
-
err.should.match(/gem kit bump \[SEGMENT\]/)
|
|
93
|
-
err.should.not.match(/^\s+-e /)
|
|
94
|
-
end
|
|
95
|
-
end
|
|
199
|
+
_status, out, _err = invoke([], dir)
|
|
96
200
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
before = $PROGRAM_NAME
|
|
100
|
-
invoke([], dir)
|
|
101
|
-
$PROGRAM_NAME.should == before
|
|
201
|
+
out.should.match(/gem kit bump/)
|
|
202
|
+
out.should.not.match(/^\s+rspec /)
|
|
102
203
|
end
|
|
103
204
|
end
|
|
104
205
|
|
|
105
|
-
it "prints a
|
|
206
|
+
it "prints a command's options and long description for help" do
|
|
106
207
|
with_gem do |dir|
|
|
107
|
-
_status, out, _err = invoke(["
|
|
208
|
+
_status, out, _err = invoke(["help", "bump"], dir)
|
|
108
209
|
|
|
109
210
|
out.should.match(/gem kit bump/)
|
|
110
211
|
out.should.match(/SEGMENT/)
|
|
111
212
|
out.should.match(/--force/)
|
|
112
|
-
out.should.match(/
|
|
213
|
+
out.should.match(/deprecation deadline/)
|
|
113
214
|
end
|
|
114
215
|
end
|
|
115
216
|
|
|
116
|
-
it "fails on an unknown
|
|
217
|
+
it "fails on an unknown command" do
|
|
117
218
|
with_gem do |dir|
|
|
118
219
|
invoke(["nonsense"], dir).first.should.not == 0
|
|
119
220
|
end
|
|
120
221
|
end
|
|
222
|
+
|
|
223
|
+
# What a plugin gem does. The command has to be indistinguishable from a
|
|
224
|
+
# built-in one, or the seam is not worth documenting.
|
|
225
|
+
it "takes a command from a plugin" do
|
|
226
|
+
GemKit::Release.plugin do
|
|
227
|
+
desc "hello NAME", "Say hello from a plugin"
|
|
228
|
+
def hello(name) = $stdout.puts("hello #{name} (#{options[:gem] || "no --gem"})")
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
begin
|
|
232
|
+
with_gem do |dir|
|
|
233
|
+
status, out, _err = invoke(["hello", "world"], dir)
|
|
234
|
+
status.should == 0
|
|
235
|
+
out.should.match(/hello world/)
|
|
236
|
+
|
|
237
|
+
# The class options reach it too.
|
|
238
|
+
_status, out, _err = invoke(["hello", "world", "--gem", "demo"], dir)
|
|
239
|
+
out.should.match(/hello world \(demo\)/)
|
|
240
|
+
|
|
241
|
+
# And it is listed and documented like the rest.
|
|
242
|
+
_status, listing, _err = invoke([], dir)
|
|
243
|
+
listing.should.match(/gem kit hello NAME/)
|
|
244
|
+
listing.should.match(/Say hello from a plugin/)
|
|
245
|
+
end
|
|
246
|
+
ensure
|
|
247
|
+
GemKit::Release::CLI.remove_command("hello")
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
it "hands the plugin the whole Thor DSL, generators included" do
|
|
252
|
+
generator = Class.new(Thor::Group) do
|
|
253
|
+
include Thor::Actions
|
|
254
|
+
def self.banner = "gem kit scaffold"
|
|
255
|
+
def report = $stdout.puts("scaffolded")
|
|
256
|
+
end
|
|
257
|
+
Object.const_set(:PluginScaffoldGenerator, generator) unless defined?(PluginScaffoldGenerator)
|
|
258
|
+
|
|
259
|
+
GemKit::Release.plugin do
|
|
260
|
+
register(PluginScaffoldGenerator, "scaffold", "scaffold", "Scaffold something")
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
begin
|
|
264
|
+
with_gem do |dir|
|
|
265
|
+
status, out, _err = invoke(["scaffold"], dir)
|
|
266
|
+
status.should == 0
|
|
267
|
+
out.should.match(/scaffolded/)
|
|
268
|
+
end
|
|
269
|
+
ensure
|
|
270
|
+
GemKit::Release::CLI.remove_command("scaffold")
|
|
271
|
+
end
|
|
272
|
+
end
|
|
121
273
|
end
|
|
@@ -4,29 +4,16 @@ require_relative "command"
|
|
|
4
4
|
|
|
5
5
|
module GemKit
|
|
6
6
|
module Release
|
|
7
|
-
module
|
|
7
|
+
module Commands
|
|
8
8
|
# The deadline check lives here rather than at release time because the
|
|
9
9
|
# bump is the last moment anyone can be stopped: once the version file
|
|
10
10
|
# says 5.0.0, every promise to disappear "in 5.0" is already broken.
|
|
11
11
|
class Bump < Command
|
|
12
|
-
desc "Move the gem version, refusing to bump onto a deprecation deadline"
|
|
13
12
|
|
|
14
|
-
argument :segment,
|
|
15
|
-
type: :string, required: false, default: "patch",
|
|
16
|
-
values: %w[major minor patch],
|
|
17
|
-
desc: "Which segment of the version to move"
|
|
18
13
|
|
|
19
|
-
option :force,
|
|
20
|
-
type: :boolean, default: false,
|
|
21
|
-
desc: "Bump even when a deprecation comes due"
|
|
22
14
|
|
|
23
|
-
example [
|
|
24
|
-
"minor # 4.1.0 -> 4.2.0",
|
|
25
|
-
"major # refused while a 5.0 deprecation is still in the tree",
|
|
26
|
-
"major --force # bump anyway, saying what it overrode",
|
|
27
|
-
]
|
|
28
15
|
|
|
29
|
-
def call(segment
|
|
16
|
+
def call(segment = "patch")
|
|
30
17
|
current = project.version.to_s
|
|
31
18
|
|
|
32
19
|
begin
|
|
@@ -65,7 +52,7 @@ end
|
|
|
65
52
|
|
|
66
53
|
__END__
|
|
67
54
|
|
|
68
|
-
describe "gem_kit/release/
|
|
55
|
+
describe "gem_kit/release/commands/bump" do
|
|
69
56
|
require_relative "../../../../spec/support/gem_kit_release_spec"
|
|
70
57
|
extend GemKitReleaseSpec
|
|
71
58
|
|
|
@@ -4,27 +4,15 @@ require_relative "command"
|
|
|
4
4
|
|
|
5
5
|
module GemKit
|
|
6
6
|
module Release
|
|
7
|
-
module
|
|
7
|
+
module Commands
|
|
8
8
|
# Linting is the default because it is the safe, read-only half. Writing
|
|
9
9
|
# edits a file and costs money, so it asks for the flag.
|
|
10
10
|
class Changelog < Command
|
|
11
|
-
desc "Lint CHANGELOG.md, or have an AI CLI write this version's entry"
|
|
12
11
|
|
|
13
|
-
argument :version,
|
|
14
|
-
type: :string, required: false,
|
|
15
|
-
desc: "Check that this version is ready to release (default: format only)"
|
|
16
12
|
|
|
17
|
-
option :write,
|
|
18
|
-
type: :boolean, default: false,
|
|
19
|
-
desc: "Ask the configured AI CLI to write this version's entry"
|
|
20
13
|
|
|
21
|
-
example [
|
|
22
|
-
" # lint the format",
|
|
23
|
-
"4.2.0 # ...and check 4.2.0 is ready to release",
|
|
24
|
-
"--write # hand the entry to the configured AI CLI",
|
|
25
|
-
]
|
|
26
14
|
|
|
27
|
-
def call(version
|
|
15
|
+
def call(version = nil)
|
|
28
16
|
return write_entry if options[:write]
|
|
29
17
|
|
|
30
18
|
problems = version ? gate.release_problems(version) : gate.changelog_problems
|
|
@@ -84,7 +72,7 @@ end
|
|
|
84
72
|
|
|
85
73
|
__END__
|
|
86
74
|
|
|
87
|
-
describe "gem_kit/release/
|
|
75
|
+
describe "gem_kit/release/commands/changelog" do
|
|
88
76
|
require_relative "../../../../spec/support/gem_kit_release_spec"
|
|
89
77
|
extend GemKitReleaseSpec
|
|
90
78
|
|
|
@@ -1,30 +1,34 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "dry/cli"
|
|
4
|
-
|
|
5
3
|
require_relative "../../release"
|
|
6
4
|
|
|
7
5
|
module GemKit
|
|
8
6
|
module Release
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
# turn it into a status.
|
|
13
|
-
class Failure < StandardError; end
|
|
7
|
+
# Raised instead of exiting, so the caller decides what a failure means:
|
|
8
|
+
# the Thor CLI turns it into a non-zero status, and the specs assert on it.
|
|
9
|
+
class Failure < StandardError; end
|
|
14
10
|
|
|
15
|
-
|
|
16
|
-
#
|
|
17
|
-
# question, report
|
|
18
|
-
# Deprecate
|
|
19
|
-
|
|
11
|
+
module Commands
|
|
12
|
+
# Shared base for the commands behind `gem kit`. Each is thin — ask a
|
|
13
|
+
# library object a question, report — and knows nothing about Thor. The
|
|
14
|
+
# work stays in Gate, VersionFile, Changelog and Deprecate; the parsing,
|
|
15
|
+
# the help pages and the generator machinery stay in Thor.
|
|
16
|
+
class Command
|
|
20
17
|
RED = "\e[0;31m"
|
|
21
18
|
RESET = "\e[0m"
|
|
22
19
|
|
|
20
|
+
attr_reader :options
|
|
21
|
+
|
|
22
|
+
def initialize(options = {})
|
|
23
|
+
@options = options
|
|
24
|
+
end
|
|
25
|
+
|
|
23
26
|
# The gem in the working directory. Everything is inferred from its
|
|
24
27
|
# .gemspec, so there is nothing to configure in the common case.
|
|
28
|
+
# `--gem` picks one out of a repository holding several.
|
|
25
29
|
def project
|
|
26
|
-
@project ||= Project.detect
|
|
27
|
-
rescue Project::NotFound => error
|
|
30
|
+
@project ||= Project.detect(Dir.pwd, name: options[:gem])
|
|
31
|
+
rescue Project::NotFound, Project::Ambiguous => error
|
|
28
32
|
fail_with(error.message)
|
|
29
33
|
end
|
|
30
34
|
|
|
@@ -4,23 +4,15 @@ require_relative "command"
|
|
|
4
4
|
|
|
5
5
|
module GemKit
|
|
6
6
|
module Release
|
|
7
|
-
module
|
|
7
|
+
module Commands
|
|
8
8
|
# Without a version: everything outstanding. With one: only what comes
|
|
9
9
|
# due there, failing if anything does — which is what makes it usable as
|
|
10
10
|
# a CI gate.
|
|
11
11
|
class Deprecations < Command
|
|
12
|
-
desc "List the deprecations this gem has not yet honoured"
|
|
13
12
|
|
|
14
|
-
argument :version,
|
|
15
|
-
type: :string, required: false,
|
|
16
|
-
desc: "List only what comes due at this version, and fail if any does"
|
|
17
13
|
|
|
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
14
|
|
|
23
|
-
def call(version
|
|
15
|
+
def call(version = nil)
|
|
24
16
|
project.load!
|
|
25
17
|
registry = Deprecate.registry
|
|
26
18
|
|
|
@@ -34,7 +26,7 @@ module GemKit
|
|
|
34
26
|
return render(registry)
|
|
35
27
|
end
|
|
36
28
|
|
|
37
|
-
due = Deprecate.pending(version)
|
|
29
|
+
due = GemKit::Deprecate.pending(version)
|
|
38
30
|
if due.empty?
|
|
39
31
|
say("No deprecations come due at #{version}. (#{registry.size} outstanding overall.)")
|
|
40
32
|
return
|
|
@@ -49,7 +41,7 @@ end
|
|
|
49
41
|
|
|
50
42
|
__END__
|
|
51
43
|
|
|
52
|
-
describe "gem_kit/release/
|
|
44
|
+
describe "gem_kit/release/commands/deprecations" do
|
|
53
45
|
require_relative "../../../../spec/support/gem_kit_release_spec"
|
|
54
46
|
extend GemKitReleaseSpec
|
|
55
47
|
|
|
@@ -4,21 +4,14 @@ require_relative "command"
|
|
|
4
4
|
|
|
5
5
|
module GemKit
|
|
6
6
|
module Release
|
|
7
|
-
module
|
|
7
|
+
module Commands
|
|
8
8
|
# Gate, build, push. The gates run before anything is built, because a
|
|
9
9
|
# half-done release is worse than a refused one.
|
|
10
10
|
class Release < Command
|
|
11
|
-
desc "Gate, build and push this gem"
|
|
12
11
|
|
|
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
12
|
|
|
16
|
-
example [
|
|
17
|
-
"--dry-run # run the gates; what belongs in CI",
|
|
18
|
-
"--tag # release, then tag it",
|
|
19
|
-
]
|
|
20
13
|
|
|
21
|
-
def call
|
|
14
|
+
def call
|
|
22
15
|
version = project.version.to_s
|
|
23
16
|
problems = gate.release_problems(version)
|
|
24
17
|
|
|
@@ -42,7 +35,7 @@ module GemKit
|
|
|
42
35
|
|
|
43
36
|
say("Released #{project.name} #{version}")
|
|
44
37
|
|
|
45
|
-
Tag.new.call if options[:tag]
|
|
38
|
+
Tag.new(options).call if options[:tag]
|
|
46
39
|
end
|
|
47
40
|
end
|
|
48
41
|
end
|
|
@@ -51,7 +44,7 @@ end
|
|
|
51
44
|
|
|
52
45
|
__END__
|
|
53
46
|
|
|
54
|
-
describe "gem_kit/release/
|
|
47
|
+
describe "gem_kit/release/commands/release" do
|
|
55
48
|
require_relative "../../../../spec/support/gem_kit_release_spec"
|
|
56
49
|
extend GemKitReleaseSpec
|
|
57
50
|
|
|
@@ -4,24 +4,16 @@ require_relative "command"
|
|
|
4
4
|
|
|
5
5
|
module GemKit
|
|
6
6
|
module Release
|
|
7
|
-
module
|
|
7
|
+
module Commands
|
|
8
8
|
# The tag matters beyond bookkeeping: it is what the next changelog is
|
|
9
9
|
# written against, so a missing one makes the following release harder to
|
|
10
10
|
# describe.
|
|
11
11
|
class Tag < Command
|
|
12
|
-
desc "Tag the current version in git"
|
|
13
12
|
|
|
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
13
|
|
|
17
|
-
example [
|
|
18
|
-
" # tag v4.1.0",
|
|
19
|
-
"--push # ...and push it",
|
|
20
|
-
"--prefix release- # tag release-4.1.0",
|
|
21
|
-
]
|
|
22
14
|
|
|
23
|
-
def call
|
|
24
|
-
tag = "#{options
|
|
15
|
+
def call
|
|
16
|
+
tag = "#{options[:prefix] || project.tag_prefix}#{project.version}"
|
|
25
17
|
|
|
26
18
|
Dir.chdir(project.root) do
|
|
27
19
|
fail_with("not a git repository") unless system("git rev-parse --git-dir >/dev/null 2>&1")
|
|
@@ -45,7 +37,7 @@ end
|
|
|
45
37
|
|
|
46
38
|
__END__
|
|
47
39
|
|
|
48
|
-
describe "gem_kit/release/
|
|
40
|
+
describe "gem_kit/release/commands/tag" do
|
|
49
41
|
require_relative "../../../../spec/support/gem_kit_release_spec"
|
|
50
42
|
extend GemKitReleaseSpec
|
|
51
43
|
|
|
@@ -67,6 +59,28 @@ describe "gem_kit/release/cli/tag" do
|
|
|
67
59
|
end
|
|
68
60
|
end
|
|
69
61
|
|
|
62
|
+
# Two gems at the same version in one repository would otherwise fight over
|
|
63
|
+
# a single `v0.1.0`.
|
|
64
|
+
it "namespaces the tag by gem when the repository holds several" do
|
|
65
|
+
with_gem do |dir|
|
|
66
|
+
as_repo.call(dir)
|
|
67
|
+
File.write(File.join(dir, "other.gemspec"), <<~RUBY)
|
|
68
|
+
Gem::Specification.new do |spec|
|
|
69
|
+
spec.name = "other"
|
|
70
|
+
spec.version = "1.2.3"
|
|
71
|
+
spec.authors = ["x"]
|
|
72
|
+
spec.summary = "x"
|
|
73
|
+
spec.files = []
|
|
74
|
+
end
|
|
75
|
+
RUBY
|
|
76
|
+
|
|
77
|
+
invoke(["tag", "--gem", "demo"], dir).first.should == 0
|
|
78
|
+
invoke(["tag", "--gem", "other"], dir).first.should == 0
|
|
79
|
+
|
|
80
|
+
`git -C #{dir} tag -l`.split.sort.should == ["demo-v1.2.3", "other-v1.2.3"]
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
70
84
|
it "honours a custom prefix" do
|
|
71
85
|
with_gem do |dir|
|
|
72
86
|
as_repo.call(dir)
|