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/gate.rb
CHANGED
|
@@ -23,7 +23,7 @@ module GemKit
|
|
|
23
23
|
def deprecation_problems(version)
|
|
24
24
|
project.load!
|
|
25
25
|
|
|
26
|
-
Deprecate.pending(version).map do |entry|
|
|
26
|
+
GemKit::Deprecate.pending(version).map do |entry|
|
|
27
27
|
"#{entry.removed_in.to_s.ljust(8)} #{entry}#{entry.declared_at ? "\n#{" " * 9}#{entry.declared_at}" : ""}"
|
|
28
28
|
end
|
|
29
29
|
end
|
|
@@ -32,7 +32,7 @@ module GemKit
|
|
|
32
32
|
# way past, not worth blocking on.
|
|
33
33
|
def upcoming_deprecations(version)
|
|
34
34
|
project.load!
|
|
35
|
-
Deprecate.upcoming(version)
|
|
35
|
+
GemKit::Deprecate.upcoming(version)
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
# Changelog format, plus "is there an entry for this version?" when one
|
|
@@ -96,12 +96,12 @@ describe "gem_kit/release/gate" do
|
|
|
96
96
|
|
|
97
97
|
# Run a block with the deprecation registry isolated.
|
|
98
98
|
isolated = lambda do |&block|
|
|
99
|
-
saved = GemKit::
|
|
100
|
-
GemKit::
|
|
99
|
+
saved = GemKit::Deprecate.registry.dup
|
|
100
|
+
GemKit::Deprecate.registry.clear
|
|
101
101
|
begin
|
|
102
102
|
block.call
|
|
103
103
|
ensure
|
|
104
|
-
GemKit::
|
|
104
|
+
GemKit::Deprecate.registry.replace(saved)
|
|
105
105
|
end
|
|
106
106
|
end
|
|
107
107
|
|
|
@@ -134,7 +134,7 @@ describe "gem_kit/release/gate" do
|
|
|
134
134
|
File.write(path, clean_changelog)
|
|
135
135
|
|
|
136
136
|
isolated.call do
|
|
137
|
-
GemKit::
|
|
137
|
+
GemKit::Deprecate.register(name: "Old", replacement: "New", removed_in: "1.0")
|
|
138
138
|
gate = GemKit::Release::Gate.new(stub_project.call(path))
|
|
139
139
|
|
|
140
140
|
gate.release_problems("1.0.0").first.should.match(/deprecation due in 1\.0\.0: .*Old -> New/)
|
|
@@ -146,7 +146,7 @@ describe "gem_kit/release/gate" do
|
|
|
146
146
|
it "does not block a bump on a deadline that has not arrived" do
|
|
147
147
|
Dir.mktmpdir do |dir|
|
|
148
148
|
isolated.call do
|
|
149
|
-
GemKit::
|
|
149
|
+
GemKit::Deprecate.register(name: "Old", replacement: "New", removed_in: "2.0")
|
|
150
150
|
gate = GemKit::Release::Gate.new(stub_project.call(File.join(dir, "CHANGELOG.md")))
|
|
151
151
|
|
|
152
152
|
gate.bump_problems("1.1.0").should == []
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "thor"
|
|
4
|
+
require "thor/group"
|
|
5
|
+
|
|
6
|
+
require_relative "../commands/command"
|
|
7
|
+
|
|
8
|
+
module GemKit
|
|
9
|
+
module Release
|
|
10
|
+
module Generators
|
|
11
|
+
# `gem kit setup` — a Thor generator, which is what Rails' own generators
|
|
12
|
+
# are. Using one rather than hand-rolled File.write buys the whole
|
|
13
|
+
# conflict protocol for free: `create`/`identical`/`conflict` status
|
|
14
|
+
# lines, a prompt offering diff/overwrite/skip when a file exists and
|
|
15
|
+
# differs, and --force / --skip / --pretend to answer that prompt ahead
|
|
16
|
+
# of time.
|
|
17
|
+
#
|
|
18
|
+
# It writes the two documents that describe this toolchain, rendered for
|
|
19
|
+
# the project's own name and versions. They are copies rather than links
|
|
20
|
+
# because a policy nobody can read in their own repo is a policy nobody
|
|
21
|
+
# follows, and because the examples are worth more with real numbers in
|
|
22
|
+
# them.
|
|
23
|
+
class Setup < Thor::Group
|
|
24
|
+
include Thor::Actions
|
|
25
|
+
|
|
26
|
+
# --force, --skip, --pretend and --quiet: Thor::Actions implements the
|
|
27
|
+
# behaviour but leaves declaring the switches to the generator.
|
|
28
|
+
add_runtime_options!
|
|
29
|
+
|
|
30
|
+
class_option :gem, type: :string,
|
|
31
|
+
desc: "Which gem, in a repository holding more than one gemspec"
|
|
32
|
+
|
|
33
|
+
def self.source_root
|
|
34
|
+
File.expand_path("../templates", __dir__)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Written into the project, not the working directory: `gem kit setup`
|
|
38
|
+
# run from a subdirectory still belongs to the gem.
|
|
39
|
+
def self.banner = "gem kit setup"
|
|
40
|
+
|
|
41
|
+
# The policy is the repository's — one deprecation policy governs every
|
|
42
|
+
# gem in it — so DEPRECATIONS.md is written once, whichever gem you
|
|
43
|
+
# named.
|
|
44
|
+
def deprecations_document
|
|
45
|
+
template("DEPRECATIONS.md.erb", File.join(project.root, "DEPRECATIONS.md"))
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# The process is the gem's: every command in it names a version and a
|
|
49
|
+
# changelog belonging to one gem. So a repository with two gems gets
|
|
50
|
+
# RELEASE-<gem>.md apiece, matching the changelogs.
|
|
51
|
+
def release_document
|
|
52
|
+
template("RELEASE.md.erb", File.join(project.root, release_document_name))
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def where_to_link_them
|
|
56
|
+
say ""
|
|
57
|
+
say "Link them from your README and AGENTS.md so they are found."
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Everything below is machinery, not a step. Thor runs every public
|
|
61
|
+
# method of a Thor::Group in order, so helpers have to say so.
|
|
62
|
+
no_commands do
|
|
63
|
+
# Every command in a multi-gem repository needs to say which gem it
|
|
64
|
+
# means, so the examples show it.
|
|
65
|
+
# Pads a command so the trailing comments line up whatever the gem
|
|
66
|
+
# is called.
|
|
67
|
+
def step(command) = command.ljust(step_width)
|
|
68
|
+
|
|
69
|
+
def step_width
|
|
70
|
+
@step_width ||= [30, "gem kit changelog#{gem_flag} <VERSION>".length + 2].max
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def multi_gem = project.multi_gem?
|
|
74
|
+
def tag_prefix = project.tag_prefix
|
|
75
|
+
|
|
76
|
+
# DEPRECATIONS.md is written once for the repository, so everything
|
|
77
|
+
# it says has to hold for every gem in it — no version numbers, and
|
|
78
|
+
# no link to one gem's changelog.
|
|
79
|
+
def deadline_rule
|
|
80
|
+
if multi_gem
|
|
81
|
+
"The usual deadline is the next major of the gem the name lives in."
|
|
82
|
+
else
|
|
83
|
+
"The current version is #{version}, so the usual deadline is #{next_major}."
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def changelog_reference
|
|
88
|
+
multi_gem ? "that gem's changelog" : "`#{changelog}`"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def release_document_link
|
|
92
|
+
multi_gem ? "The RELEASE-<gem>.md beside it" : "[RELEASE.md](RELEASE.md)"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def gem_flag
|
|
96
|
+
project.multi_gem? ? " --gem #{project.name}" : ""
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def release_document_name
|
|
100
|
+
project.multi_gem? ? "RELEASE-#{project.name}.md" : "RELEASE.md"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# The values the templates render against. Kept small and obvious — a
|
|
104
|
+
# template needing more than this is documenting the tool, not the
|
|
105
|
+
# project.
|
|
106
|
+
# DEPRECATIONS.md is the repository's, so what it is *about* must not
|
|
107
|
+
# depend on which gem you named — otherwise running setup for the
|
|
108
|
+
# second gem rewrites the first gem's document.
|
|
109
|
+
def subject
|
|
110
|
+
project.multi_gem? ? File.basename(project.root) : project.name
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def name = project.name
|
|
114
|
+
def version = project.version.to_s
|
|
115
|
+
def next_major = project.next_major_version
|
|
116
|
+
def changelog = File.basename(project.changelog_path)
|
|
117
|
+
def test = project.test_command
|
|
118
|
+
def today = Time.now.strftime("%Y-%m-%d")
|
|
119
|
+
|
|
120
|
+
def version_file
|
|
121
|
+
project.version_file.sub("#{project.root}/", "")
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def project
|
|
125
|
+
@project ||= Project.detect(Dir.pwd, name: options[:gem])
|
|
126
|
+
rescue Project::NotFound, Project::Ambiguous => error
|
|
127
|
+
raise Failure, error.message
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
__END__
|
|
136
|
+
|
|
137
|
+
describe "gem_kit/release/generators/setup" do
|
|
138
|
+
require_relative "../../../../spec/support/gem_kit_release_spec"
|
|
139
|
+
extend GemKitReleaseSpec
|
|
140
|
+
|
|
141
|
+
it "writes both documents into the project root" do
|
|
142
|
+
with_gem do |dir|
|
|
143
|
+
status, out, _err = invoke(["setup"], dir)
|
|
144
|
+
|
|
145
|
+
status.should == 0
|
|
146
|
+
out.should.match(/create.*DEPRECATIONS\.md/)
|
|
147
|
+
out.should.match(/create.*RELEASE\.md/)
|
|
148
|
+
File.exist?(File.join(dir, "DEPRECATIONS.md")).should.be.true
|
|
149
|
+
File.exist?(File.join(dir, "RELEASE.md")).should.be.true
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
it "renders them for this project's name and versions" do
|
|
154
|
+
with_gem(version: "4.1.0") do |dir|
|
|
155
|
+
invoke(["setup"], dir)
|
|
156
|
+
|
|
157
|
+
deprecations = File.read(File.join(dir, "DEPRECATIONS.md"))
|
|
158
|
+
deprecations.should.match(/A deprecation in demo is a \*\*dated promise\*\*/)
|
|
159
|
+
deprecations.should.match(/4\.1\.0, so the usual deadline is 5\.0/)
|
|
160
|
+
deprecations.should.not.match(/<%=/)
|
|
161
|
+
|
|
162
|
+
release = File.read(File.join(dir, "RELEASE.md"))
|
|
163
|
+
release.should.match(/Releasing demo/)
|
|
164
|
+
release.should.not.match(/<%=/)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Thor::Actions reports an unchanged file as `identical` rather than
|
|
169
|
+
# rewriting it, which is the generator behaviour we switched to Thor for.
|
|
170
|
+
it "reports an unchanged document as identical rather than rewriting it" do
|
|
171
|
+
with_gem do |dir|
|
|
172
|
+
invoke(["setup"], dir)
|
|
173
|
+
before = File.mtime(File.join(dir, "RELEASE.md"))
|
|
174
|
+
|
|
175
|
+
_status, out, _err = invoke(["setup"], dir)
|
|
176
|
+
|
|
177
|
+
out.should.match(/identical.*RELEASE\.md/)
|
|
178
|
+
File.mtime(File.join(dir, "RELEASE.md")).should == before
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
it "--force overwrites a document that differs" do
|
|
183
|
+
with_gem do |dir|
|
|
184
|
+
File.write(File.join(dir, "RELEASE.md"), "mine\n")
|
|
185
|
+
|
|
186
|
+
status, out, _err = invoke(["setup", "--force"], dir)
|
|
187
|
+
|
|
188
|
+
status.should == 0
|
|
189
|
+
out.should.match(/force.*RELEASE\.md/)
|
|
190
|
+
File.read(File.join(dir, "RELEASE.md")).should.not == "mine\n"
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
it "--skip leaves a document that differs alone" do
|
|
195
|
+
with_gem do |dir|
|
|
196
|
+
File.write(File.join(dir, "RELEASE.md"), "mine\n")
|
|
197
|
+
|
|
198
|
+
invoke(["setup", "--skip"], dir).first.should == 0
|
|
199
|
+
File.read(File.join(dir, "RELEASE.md")).should == "mine\n"
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
it "--pretend writes nothing" do
|
|
204
|
+
with_gem do |dir|
|
|
205
|
+
invoke(["setup", "--pretend"], dir).first.should == 0
|
|
206
|
+
File.exist?(File.join(dir, "RELEASE.md")).should.be.false
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
it "is reachable by its alias" do
|
|
211
|
+
with_gem do |dir|
|
|
212
|
+
invoke(["init"], dir).first.should == 0
|
|
213
|
+
File.exist?(File.join(dir, "RELEASE.md")).should.be.true
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# In a repository with several gems the process document is per-gem, but the
|
|
218
|
+
# policy is the repository's — so running setup for the second gem must not
|
|
219
|
+
# rewrite what the first one wrote.
|
|
220
|
+
it "writes a release document per gem and one policy for the repository" do
|
|
221
|
+
with_gem(second_gem: true) do |dir|
|
|
222
|
+
_status, first, _err = invoke(["setup", "--gem", "demo"], dir)
|
|
223
|
+
_status, second, _err = invoke(["setup", "--gem", "other"], dir)
|
|
224
|
+
|
|
225
|
+
first.should.match(/create.*DEPRECATIONS\.md/)
|
|
226
|
+
first.should.match(/create.*RELEASE-demo\.md/)
|
|
227
|
+
|
|
228
|
+
second.should.match(/identical.*DEPRECATIONS\.md/)
|
|
229
|
+
second.should.match(/create.*RELEASE-other\.md/)
|
|
230
|
+
|
|
231
|
+
File.exist?(File.join(dir, "RELEASE.md")).should.be.false
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
it "spells the commands with --gem when the repository holds several" do
|
|
236
|
+
with_gem(second_gem: true) do |dir|
|
|
237
|
+
invoke(["setup", "--gem", "demo"], dir)
|
|
238
|
+
|
|
239
|
+
release = File.read(File.join(dir, "RELEASE-demo.md"))
|
|
240
|
+
release.should.match(/gem kit bump --gem demo/)
|
|
241
|
+
release.should.match(/CHANGELOG-demo\.md/)
|
|
242
|
+
release.should.match(/demo-v1\.2\.3/)
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
it "reports a directory with no gemspec" do
|
|
247
|
+
Dir.mktmpdir do |dir|
|
|
248
|
+
invoke(["setup"], dir).first.should == 1
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
|
@@ -25,15 +25,37 @@ module GemKit
|
|
|
25
25
|
class Project
|
|
26
26
|
class NotFound < StandardError; end
|
|
27
27
|
|
|
28
|
+
# More than one gemspec and nothing to choose between them. A monorepo
|
|
29
|
+
# has to say which gem it means.
|
|
30
|
+
class Ambiguous < StandardError; end
|
|
31
|
+
|
|
28
32
|
# Overridable settings. Each is nil until set, and nil means "infer".
|
|
29
33
|
Config = Struct.new(:changelog, :version_file, :require_path, :test_command,
|
|
30
34
|
:changelog_writer, keyword_init: true)
|
|
31
35
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
# `name` picks one gem out of a repository that holds several — matched
|
|
37
|
+
# against the gemspec's filename, so `--gem gem_kit` finds
|
|
38
|
+
# gem_kit.gemspec and not gem_kit-release.gemspec.
|
|
39
|
+
def self.detect(dir = Dir.pwd, name: nil, config: GemKit::Release.config)
|
|
40
|
+
gemspecs = Dir[File.join(dir, "*.gemspec")].sort
|
|
41
|
+
raise NotFound, "no .gemspec in #{dir}" if gemspecs.empty?
|
|
42
|
+
|
|
43
|
+
if name
|
|
44
|
+
match = gemspecs.find { |path| File.basename(path, ".gemspec") == name }
|
|
45
|
+
raise NotFound, "no #{name}.gemspec in #{dir}" if match.nil?
|
|
46
|
+
|
|
47
|
+
return new(match, config: config)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Silently picking the first would mean bumping the wrong gem's version
|
|
51
|
+
# and only finding out on release.
|
|
52
|
+
if gemspecs.size > 1
|
|
53
|
+
names = gemspecs.map { |path| File.basename(path, ".gemspec") }
|
|
54
|
+
raise Ambiguous, "#{gemspecs.size} gemspecs in #{dir} (#{names.join(", ")}); " \
|
|
55
|
+
"name one with --gem"
|
|
56
|
+
end
|
|
35
57
|
|
|
36
|
-
new(
|
|
58
|
+
new(gemspecs.first, config: config)
|
|
37
59
|
end
|
|
38
60
|
|
|
39
61
|
attr_reader :root, :gemspec_path, :config
|
|
@@ -55,13 +77,38 @@ module GemKit
|
|
|
55
77
|
|
|
56
78
|
def version = Gem::Version.new(spec.version.to_s)
|
|
57
79
|
|
|
58
|
-
# The next major version — the
|
|
80
|
+
# The next major version — the usual deadline for a deprecation. Written
|
|
81
|
+
# "5.0" rather than "5": it reads as a version in prose and in a
|
|
82
|
+
# declaration, and Gem::Version treats the two as equal anyway.
|
|
59
83
|
def next_major_version
|
|
60
|
-
|
|
84
|
+
"#{version.segments.first.to_i + 1}.0"
|
|
61
85
|
end
|
|
62
86
|
|
|
87
|
+
# Every gemspec beside this one, this one included. A repository with
|
|
88
|
+
# more than one releases each gem on its own schedule, and several things
|
|
89
|
+
# below have to know that.
|
|
90
|
+
def siblings
|
|
91
|
+
@siblings ||= Dir[File.join(root, "*.gemspec")].size
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def multi_gem? = siblings > 1
|
|
95
|
+
|
|
96
|
+
# One changelog per gem. A shared file cannot work: the release gate asks
|
|
97
|
+
# whether the version being cut is the *topmost* released section, and
|
|
98
|
+
# two gems interleaved in one file means the older one never is — so the
|
|
99
|
+
# second gem could never be released.
|
|
63
100
|
def changelog_path
|
|
64
|
-
File.expand_path(config.changelog ||
|
|
101
|
+
File.expand_path(config.changelog || default_changelog, root)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def default_changelog
|
|
105
|
+
multi_gem? ? "CHANGELOG-#{name}.md" : "CHANGELOG.md"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# `v1.2.3` says which version but not which gem, which is fine until a
|
|
109
|
+
# repository holds two of them at the same version.
|
|
110
|
+
def tag_prefix
|
|
111
|
+
multi_gem? ? "#{name}-v" : "v"
|
|
65
112
|
end
|
|
66
113
|
|
|
67
114
|
# lib/gem_kit/release/version.rb for "gem_kit-release", lib/brute/version.rb
|
|
@@ -158,13 +205,50 @@ describe "gem_kit/release/project" do
|
|
|
158
205
|
|
|
159
206
|
it "defaults the changelog to CHANGELOG.md in the project root" do
|
|
160
207
|
with_project.call do |dir|
|
|
161
|
-
GemKit::Release::Project.detect(dir)
|
|
208
|
+
project = GemKit::Release::Project.detect(dir)
|
|
209
|
+
project.multi_gem?.should.be.false
|
|
210
|
+
project.changelog_path.should == File.join(dir, "CHANGELOG.md")
|
|
211
|
+
project.tag_prefix.should == "v"
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# One changelog and one tag namespace per gem, because a repository with two
|
|
216
|
+
# gems releases them separately.
|
|
217
|
+
it "gives each gem its own changelog and tag prefix when there are several" do
|
|
218
|
+
with_project.call(name: "demo") do |dir|
|
|
219
|
+
File.write(File.join(dir, "other.gemspec"), <<~RUBY)
|
|
220
|
+
Gem::Specification.new do |spec|
|
|
221
|
+
spec.name = "other"
|
|
222
|
+
spec.version = "9.9.9"
|
|
223
|
+
spec.authors = ["x"]
|
|
224
|
+
spec.summary = "x"
|
|
225
|
+
spec.files = []
|
|
226
|
+
end
|
|
227
|
+
RUBY
|
|
228
|
+
|
|
229
|
+
project = GemKit::Release::Project.detect(dir, name: "demo")
|
|
230
|
+
project.multi_gem?.should.be.true
|
|
231
|
+
project.changelog_path.should == File.join(dir, "CHANGELOG-demo.md")
|
|
232
|
+
project.tag_prefix.should == "demo-v"
|
|
233
|
+
|
|
234
|
+
GemKit::Release::Project.detect(dir, name: "other").changelog_path
|
|
235
|
+
.should == File.join(dir, "CHANGELOG-other.md")
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
it "an explicit changelog still overrides, in either kind of repository" do
|
|
240
|
+
with_project.call(name: "demo") do |dir|
|
|
241
|
+
File.write(File.join(dir, "other.gemspec"), "Gem::Specification.new { |s| s.name = \"other\" }")
|
|
242
|
+
|
|
243
|
+
config = GemKit::Release::Project::Config.new(changelog: "HISTORY.md")
|
|
244
|
+
GemKit::Release::Project.detect(dir, name: "demo", config: config).changelog_path
|
|
245
|
+
.should == File.join(dir, "HISTORY.md")
|
|
162
246
|
end
|
|
163
247
|
end
|
|
164
248
|
|
|
165
249
|
it "bumps to the next major for the default deprecation deadline" do
|
|
166
250
|
with_project.call(version: "4.1.0") do |dir|
|
|
167
|
-
GemKit::Release::Project.detect(dir).next_major_version.should == "5"
|
|
251
|
+
GemKit::Release::Project.detect(dir).next_major_version.should == "5.0"
|
|
168
252
|
end
|
|
169
253
|
end
|
|
170
254
|
|
|
@@ -177,6 +261,39 @@ describe "gem_kit/release/project" do
|
|
|
177
261
|
end
|
|
178
262
|
end
|
|
179
263
|
|
|
264
|
+
it "picks one gem out of a repository holding several" do
|
|
265
|
+
with_project.call(name: "demo") do |dir|
|
|
266
|
+
File.write(File.join(dir, "other.gemspec"), <<~RUBY)
|
|
267
|
+
Gem::Specification.new do |spec|
|
|
268
|
+
spec.name = "other"
|
|
269
|
+
spec.version = "9.9.9"
|
|
270
|
+
spec.authors = ["x"]
|
|
271
|
+
spec.summary = "x"
|
|
272
|
+
spec.files = []
|
|
273
|
+
end
|
|
274
|
+
RUBY
|
|
275
|
+
|
|
276
|
+
GemKit::Release::Project.detect(dir, name: "demo").name.should == "demo"
|
|
277
|
+
GemKit::Release::Project.detect(dir, name: "other").version.should == Gem::Version.new("9.9.9")
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
it "refuses to guess between several gemspecs" do
|
|
282
|
+
with_project.call(name: "demo") do |dir|
|
|
283
|
+
File.write(File.join(dir, "other.gemspec"), "Gem::Specification.new { |s| s.name = \"other\" }")
|
|
284
|
+
|
|
285
|
+
lambda { GemKit::Release::Project.detect(dir) }
|
|
286
|
+
.should.raise(GemKit::Release::Project::Ambiguous)
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
it "reports a named gem that is not here" do
|
|
291
|
+
with_project.call(name: "demo") do |dir|
|
|
292
|
+
lambda { GemKit::Release::Project.detect(dir, name: "nope") }
|
|
293
|
+
.should.raise(GemKit::Release::Project::NotFound)
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
180
297
|
it "takes overrides from the config" do
|
|
181
298
|
with_project.call do |dir|
|
|
182
299
|
config = GemKit::Release::Project::Config.new(changelog: "HISTORY.md", test_command: "rake")
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
# Deprecations
|
|
2
2
|
|
|
3
|
-
A deprecation in <%=
|
|
3
|
+
A deprecation in <%= subject %> is a **dated promise**: it names the replacement
|
|
4
4
|
*and* the version the old name stops existing in. That promise is
|
|
5
5
|
machine-readable — every declaration registers itself, and the release tooling
|
|
6
6
|
refuses to ship a version that breaks one.
|
|
7
7
|
|
|
8
|
-
The mechanism is [`GemKit::
|
|
8
|
+
The mechanism is [`GemKit::Deprecate`](https://rubygems.org/gems/gem_kit),
|
|
9
9
|
built on [`Gem::Deprecate`](https://docs.ruby-lang.org/en/master/Gem/Deprecate.html).
|
|
10
10
|
|
|
11
11
|
## The rules
|
|
12
12
|
|
|
13
13
|
1. **Never delete a public name outright.** Leave it working, deprecated, until
|
|
14
14
|
its deadline.
|
|
15
|
-
2. **Every deprecation names a removal version.**
|
|
16
|
-
<%= version %>, so the usual deadline is <%= next_major %>.
|
|
15
|
+
2. **Every deprecation names a removal version.** <%= deadline_rule %>
|
|
17
16
|
3. **Removals happen in major versions only.** A minor or patch release never
|
|
18
17
|
takes a name away.
|
|
19
18
|
4. **The deadline is enforced, not remembered.** `gem kit bump` and `gem kit release`
|
|
@@ -25,7 +24,7 @@ built on [`Gem::Deprecate`](https://docs.ruby-lang.org/en/master/Gem/Deprecate.h
|
|
|
25
24
|
|
|
26
25
|
```ruby
|
|
27
26
|
class Session
|
|
28
|
-
extend GemKit::
|
|
27
|
+
extend GemKit::Deprecate
|
|
29
28
|
|
|
30
29
|
def new_reset
|
|
31
30
|
# ...
|
|
@@ -56,7 +55,7 @@ against the class, not its singleton:
|
|
|
56
55
|
|
|
57
56
|
```ruby
|
|
58
57
|
class << self
|
|
59
|
-
extend GemKit::
|
|
58
|
+
extend GemKit::Deprecate
|
|
60
59
|
deprecate :some_class_method, "Other.method", "<%= next_major %>"
|
|
61
60
|
end
|
|
62
61
|
```
|
|
@@ -69,7 +68,7 @@ its body:
|
|
|
69
68
|
```ruby
|
|
70
69
|
module Old
|
|
71
70
|
class Thing < New::Thing
|
|
72
|
-
extend GemKit::
|
|
71
|
+
extend GemKit::Deprecate
|
|
73
72
|
superseded_by "New::Thing", "<%= next_major %>"
|
|
74
73
|
end
|
|
75
74
|
end
|
|
@@ -98,15 +97,15 @@ Pass a version to ask "what comes due here?" — it exits non-zero if anything
|
|
|
98
97
|
does, which is what makes it usable as a gate in CI:
|
|
99
98
|
|
|
100
99
|
```sh
|
|
101
|
-
gem kit deprecations <%= next_major %>.0
|
|
100
|
+
gem kit deprecations <%= next_major %>.0
|
|
102
101
|
```
|
|
103
102
|
|
|
104
103
|
Programmatically, the same data:
|
|
105
104
|
|
|
106
105
|
```ruby
|
|
107
|
-
GemKit::
|
|
108
|
-
GemKit::
|
|
109
|
-
GemKit::
|
|
106
|
+
GemKit::Deprecate.registry # every declaration
|
|
107
|
+
GemKit::Deprecate.pending("<%= next_major %>.0") # deadlines that have arrived
|
|
108
|
+
GemKit::Deprecate.upcoming("<%= next_major %>.0") # still in their grace period
|
|
110
109
|
```
|
|
111
110
|
|
|
112
111
|
Each entry carries `name`, `replacement`, `removed_in` and `declared_at`.
|
|
@@ -118,7 +117,7 @@ code is actually gone:
|
|
|
118
117
|
|
|
119
118
|
```
|
|
120
119
|
$ gem kit bump major
|
|
121
|
-
ERROR: Refusing to bump <%= version %> -> <%= next_major %>.0
|
|
120
|
+
ERROR: Refusing to bump <%= version %> -> <%= next_major %>.0:
|
|
122
121
|
|
|
123
122
|
<%= next_major %> Session#old_reset -> Session#new_reset
|
|
124
123
|
lib/session.rb:19
|
|
@@ -128,11 +127,11 @@ ERROR: Refusing to bump <%= version %> -> <%= next_major %>.0.0:
|
|
|
128
127
|
|
|
129
128
|
So the order of work is:
|
|
130
129
|
|
|
131
|
-
1. `gem kit deprecations <%= next_major %>.0
|
|
130
|
+
1. `gem kit deprecations <%= next_major %>.0` — read the list.
|
|
132
131
|
2. Delete each deprecated name and its specs. For a constant shim, that means
|
|
133
132
|
deleting the whole file.
|
|
134
133
|
3. Update anything in `examples/` and the docs still using the old name.
|
|
135
|
-
4. Record the removals in
|
|
134
|
+
4. Record the removals in <%= changelog_reference %> under `### Removed`.
|
|
136
135
|
5. `gem kit bump major` — now it goes through.
|
|
137
136
|
|
|
138
137
|
`--force` exists for the case where you have decided to extend a grace period,
|
|
@@ -156,18 +155,18 @@ through:
|
|
|
156
155
|
|
|
157
156
|
```ruby
|
|
158
157
|
captured = []
|
|
159
|
-
original = GemKit::
|
|
160
|
-
GemKit::
|
|
158
|
+
original = GemKit::Deprecate.method(:warn)
|
|
159
|
+
GemKit::Deprecate.define_singleton_method(:warn) { |message| captured << message }
|
|
161
160
|
begin
|
|
162
161
|
legacy.old_reset
|
|
163
162
|
ensure
|
|
164
|
-
GemKit::
|
|
163
|
+
GemKit::Deprecate.define_singleton_method(:warn, original)
|
|
165
164
|
end
|
|
166
165
|
```
|
|
167
166
|
|
|
168
167
|
## See also
|
|
169
168
|
|
|
170
|
-
-
|
|
169
|
+
- <%= release_document_link %> — where the deprecation gates sit in the release
|
|
171
170
|
process.
|
|
172
|
-
-
|
|
173
|
-
|
|
171
|
+
- The changelog — its `### Deprecated` and `### Removed` sections are the
|
|
172
|
+
user-facing half of all this.
|