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.
@@ -1,66 +1,180 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/cli"
3
+ require "thor"
4
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"
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 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.
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 --help that command's arguments, options, examples
32
+ # gem kit help bump that command's options
22
33
  #
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
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
- # 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
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
- # 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
188
+ it "lists every command with its description" do
77
189
  with_gem do |dir|
78
- status, _out, err = invoke([], dir)
190
+ _status, out, _err = invoke([], dir)
79
191
 
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/)
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 <subcommand>`, not the running program" do
197
+ it "names the commands `gem kit <command>`, not the running program" do
89
198
  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
199
+ _status, out, _err = invoke([], dir)
96
200
 
97
- it "restores $PROGRAM_NAME afterwards" do
98
- with_gem do |dir|
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 subcommand's arguments, options and examples for --help" do
206
+ it "prints a command's options and long description for help" do
106
207
  with_gem do |dir|
107
- _status, out, _err = invoke(["bump", "--help"], dir)
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(/Examples:/)
213
+ out.should.match(/deprecation deadline/)
113
214
  end
114
215
  end
115
216
 
116
- it "fails on an unknown subcommand" do
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 CLI
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: "patch", **options)
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/cli/bump" do
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 CLI
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: nil, **options)
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/cli/changelog" do
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
- module CLI
10
- # Raised instead of exiting, so the caller decides what a failure means:
11
- # the `gem kit` bridge turns it into terminate_interaction, and the specs
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
- # Shared base for the subcommands. Each is thin — declare arguments and
16
- # options so dry-cli can render the help page, ask a library object a
17
- # question, report. The work stays in Gate, VersionFile, Changelog and
18
- # Deprecate.
19
- class Command < Dry::CLI::Command
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 CLI
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: nil, **)
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/cli/deprecations" do
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 CLI
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(**options)
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/cli/release" do
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 CLI
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(**options)
24
- tag = "#{options.fetch(:prefix, "v")}#{project.version}"
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/cli/tag" do
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)