create_github_release 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +36 -21
  5. data/create_github_release.gemspec +1 -0
  6. data/exe/create-github-release +12 -8
  7. data/lib/create_github_release/assertion_base.rb +25 -11
  8. data/lib/create_github_release/assertions/bundle_is_up_to_date.rb +4 -3
  9. data/lib/create_github_release/assertions/{docker_is_running.rb → gh_authenticated.rb} +9 -8
  10. data/lib/create_github_release/assertions/gh_command_exists.rb +3 -2
  11. data/lib/create_github_release/assertions/git_command_exists.rb +3 -2
  12. data/lib/create_github_release/assertions/in_git_repo.rb +3 -2
  13. data/lib/create_github_release/assertions/in_repo_root_directory.rb +3 -2
  14. data/lib/create_github_release/assertions/last_release_tag_exists.rb +45 -0
  15. data/lib/create_github_release/assertions/local_and_remote_on_same_commit.rb +4 -3
  16. data/lib/create_github_release/assertions/local_release_branch_does_not_exist.rb +6 -5
  17. data/lib/create_github_release/assertions/local_release_tag_does_not_exist.rb +3 -3
  18. data/lib/create_github_release/assertions/no_staged_changes.rb +3 -2
  19. data/lib/create_github_release/assertions/no_uncommitted_changes.rb +3 -2
  20. data/lib/create_github_release/assertions/on_default_branch.rb +5 -4
  21. data/lib/create_github_release/assertions/remote_release_branch_does_not_exist.rb +6 -5
  22. data/lib/create_github_release/assertions/remote_release_tag_does_not_exist.rb +6 -5
  23. data/lib/create_github_release/assertions.rb +2 -2
  24. data/lib/create_github_release/backtick_debug.rb +69 -0
  25. data/lib/create_github_release/change.rb +73 -0
  26. data/lib/create_github_release/changelog.rb +40 -68
  27. data/lib/create_github_release/command_line_options.rb +367 -0
  28. data/lib/create_github_release/command_line_parser.rb +113 -25
  29. data/lib/create_github_release/project.rb +787 -0
  30. data/lib/create_github_release/release_assertions.rb +3 -3
  31. data/lib/create_github_release/release_tasks.rb +1 -1
  32. data/lib/create_github_release/task_base.rb +25 -11
  33. data/lib/create_github_release/tasks/commit_release.rb +4 -3
  34. data/lib/create_github_release/tasks/create_github_release.rb +16 -35
  35. data/lib/create_github_release/tasks/create_release_branch.rb +6 -5
  36. data/lib/create_github_release/tasks/create_release_pull_request.rb +10 -42
  37. data/lib/create_github_release/tasks/create_release_tag.rb +6 -5
  38. data/lib/create_github_release/tasks/push_release.rb +5 -4
  39. data/lib/create_github_release/tasks/update_changelog.rb +16 -67
  40. data/lib/create_github_release/tasks/update_version.rb +4 -3
  41. data/lib/create_github_release/version.rb +1 -1
  42. data/lib/create_github_release.rb +4 -2
  43. metadata +23 -8
  44. data/.vscode/settings.json +0 -7
  45. data/lib/create_github_release/assertions/changelog_docker_container_exists.rb +0 -73
  46. data/lib/create_github_release/options.rb +0 -397
  47. data/lib/create_github_release/release.rb +0 -82
@@ -0,0 +1,787 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bump'
4
+ require 'uri'
5
+
6
+ module CreateGithubRelease
7
+ # rubocop:disable Metrics/ClassLength
8
+
9
+ # Captures the options for this script
10
+ #
11
+ # @api public
12
+ #
13
+ class Project
14
+ # Initialize a new Project
15
+ #
16
+ # Project attributes are set using one of these methods:
17
+ # 1. The attribute is explicitly set using an attribute writer
18
+ # 2. The attribute is set using the options object
19
+ # 3. The attribute is set using the default value or running some command
20
+ #
21
+ # Method 1 takes precedence, then method 2, then method 3.
22
+ #
23
+ # The recommended way to set the attributes is to use method 2 to override values
24
+ # and otherwise method 3 to use the default value. Method 1 is only recommended for testing
25
+ # or if there is no other way to accomplish what you need.
26
+ #
27
+ # @example calling `.new` without a block
28
+ # options = CreateGithubRelease::CommandLineOptions.new { |o| o.release_type = 'minor' }
29
+ # project = CreateGithubRelease::Project.new(options)
30
+ # options.release_type = 'minor'
31
+ #
32
+ # @example calling `.new` with a block
33
+ # options = CreateGithubRelease::CommandLineOptions.new { |o| o.release_type = 'minor' }
34
+ # project = CreateGithubRelease::Project.new(options) do |p|
35
+ # p.release_type = 'major'
36
+ # end
37
+ # options.release_type = 'major'
38
+ #
39
+ # @param options [CreateGithubRelease::CommandLineOptions] the options to initialize the instance with
40
+ #
41
+ # @yield [self] an initialization block
42
+ # @yieldparam self [CreateGithubRelease::Project] the instance being initialized aka `self`
43
+ # @yieldreturn [void] the return value is ignored
44
+ #
45
+ def initialize(options)
46
+ @options = options
47
+ yield self if block_given?
48
+ end
49
+
50
+ # @!attribute options [r]
51
+ #
52
+ # The command line options used to initialize this project
53
+ #
54
+ # @example
55
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
56
+ # project = CreateGithubRelease::Project.new(options)
57
+ # project.options == options #=> true
58
+ #
59
+ # @return [CreateGithubRelease::CommandLineOptions]
60
+ #
61
+ attr_reader :options
62
+
63
+ attr_writer \
64
+ :default_branch, :next_release_tag, :next_release_date, :next_release_version,
65
+ :last_release_tag, :last_release_version, :release_branch, :release_log_url,
66
+ :release_type, :release_url, :remote, :remote_base_url, :remote_repository, :remote_url,
67
+ :changelog_path, :changes, :next_release_description, :last_release_changelog,
68
+ :next_release_changelog, :verbose, :quiet
69
+
70
+ # @!attribute [rw] default_branch
71
+ #
72
+ # The default branch of the remote repository
73
+ #
74
+ # This is the default branch as reported by the `HEAD branch` returned by
75
+ # `git remote show #{remote}`.
76
+ #
77
+ # Uses the value of `remote` to determine the remote repository to query.
78
+ #
79
+ # @example By default, `default_branch` is based on `git remote show`
80
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
81
+ # project = CreateGithubRelease::Project.new(options)
82
+ # options.default_branch # => 'main'
83
+ #
84
+ # @example `default_branch` can be set explicitly
85
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
86
+ # project = CreateGithubRelease::Project.new(options)
87
+ # project.default_branch = 'master'
88
+ # project.default_branch #=> 'master'
89
+ #
90
+ # @return [String]
91
+ #
92
+ # @raise [RuntimeError] if the git command fails
93
+ #
94
+ # @api public
95
+ #
96
+ def default_branch
97
+ @default_branch ||= options.default_branch || begin
98
+ output = `git remote show '#{remote}'`
99
+ raise "Could not determine default branch for remote '#{remote}'" unless $CHILD_STATUS.success?
100
+
101
+ output.match(/HEAD branch: (.*?)$/)[1]
102
+ end
103
+ end
104
+
105
+ # @!attribute [rw] next_release_tag
106
+ #
107
+ # The tag to use for the next release
108
+ #
109
+ # Uses the value of `next_release_version` to determine the tag name.
110
+ #
111
+ # @example By default, `next_release_tag` is based on `next_release_version`
112
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
113
+ # project = CreateGithubRelease::Project.new(options)
114
+ # project.next_release_version = '1.0.0'
115
+ # project.next_relase_tag #=> 'v1.0.0'
116
+ #
117
+ # @example `next_tag` can be set explicitly
118
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
119
+ # project = CreateGithubRelease::Project.new(options)
120
+ # project.next_release_tag = 'v1.0.0'
121
+ # project.next_relase_tag #=> 'v1.0.0'
122
+ #
123
+ # @return [String]
124
+ #
125
+ # @api public
126
+ #
127
+ def next_release_tag
128
+ @next_release_tag ||= "v#{next_release_version}"
129
+ end
130
+
131
+ # @!attribute [rw] next_release_date
132
+ #
133
+ # The date next_release_tag was created
134
+ #
135
+ # If the next_release_tag does not exist, Date.today is returned.
136
+ #
137
+ # @example By default, `next_release_date` is based on `next_release_tag`
138
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
139
+ # project = CreateGithubRelease::Project.new(options)
140
+ # project.next_release_tag = 'v1.0.0'
141
+ # project.next_release_date #=> #<Date: 2023-02-01 ((2459189j,0s,0n),+0s,2299161j)>
142
+ #
143
+ # @example It can also be set explicitly
144
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
145
+ # project = CreateGithubRelease::Project.new(options)
146
+ # project.next_release_date = Date.new(2023, 2, 1)
147
+ # project.next_release_date #=> #<Date: 2023-02-01 ((2459189j,0s,0n),+0s,2299161j)>
148
+ #
149
+ # @return [Date]
150
+ #
151
+ # @raise [RuntimeError] if the git command fails
152
+ #
153
+ # @api public
154
+ #
155
+ def next_release_date
156
+ @next_release_date ||=
157
+ if tag_exist?(next_release_tag)
158
+ date = `git show --format=format:%aI --quiet "#{next_release_tag}"`
159
+ raise "Could not determine date for tag '#{next_release_tag}'" unless $CHILD_STATUS.success?
160
+
161
+ Date.parse(date.chomp)
162
+ else
163
+ Date.today
164
+ end
165
+ end
166
+
167
+ # `true` if the given tag exists in the local repository
168
+ #
169
+ # @example
170
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
171
+ # project = CreateGithubRelease::Project.new(options)
172
+ # project.tag_exist?('v1.0.0') #=> false
173
+ #
174
+ # @param tag [String] the tag to check
175
+ #
176
+ # @return [Boolean]
177
+ #
178
+ # @api public
179
+ #
180
+ def tag_exist?(tag)
181
+ tags = `git tag --list "#{tag}"`.chomp
182
+ raise 'Could not list tags' unless $CHILD_STATUS.success?
183
+
184
+ !tags.empty?
185
+ end
186
+
187
+ # @!attribute [rw] next_release_version
188
+ #
189
+ # The version of the next release
190
+ #
191
+ # @example By default, `next_release_version` is based on the value returned by `bump show-next <release_type>`
192
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
193
+ # project = CreateGithubRelease::Project.new(options)
194
+ # project.next_release_version #=> '1.0.0'
195
+ #
196
+ # @example It can also be set explicitly
197
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
198
+ # project = CreateGithubRelease::Project.new(options)
199
+ # project.next_release_version = '1.0.0
200
+ # project.next_release_version #=> '1.0.0'
201
+ #
202
+ # @return [String]
203
+ #
204
+ # @raise [RuntimeError] if the bump command fails
205
+ #
206
+ # @api public
207
+ #
208
+ def next_release_version
209
+ @next_release_version ||= options.next_release_version || begin
210
+ output = `bump show-next #{release_type}`
211
+ raise 'Could not determine next version using bump' unless $CHILD_STATUS.success?
212
+
213
+ output.lines.last.chomp
214
+ end
215
+ end
216
+
217
+ # @!attribute [rw] last_release_tag
218
+ #
219
+ # The tag to used for the last release
220
+ #
221
+ # Uses the value of `last_release_version` to determine the tag name.
222
+ #
223
+ # @example By default, `last_release_tag` is based on `last_release_version`
224
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
225
+ # project = CreateGithubRelease::Project.new(options)
226
+ # project.last_release_version = '0.0.1'
227
+ # project.last_relase_tag #=> 'v0.0.1'
228
+ #
229
+ # @example `last_release_tag` can be set explicitly
230
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
231
+ # project = CreateGithubRelease::Project.new(options)
232
+ # project.last_release_tag = 'v0.0.1'
233
+ # project.last_relase_tag #=> 'v0.0.1'
234
+ #
235
+ # @return [String]
236
+ #
237
+ # @api public
238
+ #
239
+ def last_release_tag
240
+ @last_release_tag ||= "v#{last_release_version}"
241
+ end
242
+
243
+ # @!attribute [rw] last_release_version
244
+ #
245
+ # The version of the last release
246
+ #
247
+ # @example By default, `last_release_version` is based on the value returned by `bump current`
248
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
249
+ # project = CreateGithubRelease::Project.new(options)
250
+ # project.last_release_version #=> '0.0.1'
251
+ #
252
+ # @example It can also be set explicitly
253
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
254
+ # project = CreateGithubRelease::Project.new(options)
255
+ # project.last_release_version = '0.0.1
256
+ # project.last_release_version #=> '0.0.1'
257
+ #
258
+ # @return [String]
259
+ #
260
+ # @raise [RuntimeError] if the bump command fails
261
+ #
262
+ # @api public
263
+ #
264
+ def last_release_version
265
+ @last_release_version ||= options.last_release_version || begin
266
+ output = `bump current`
267
+ raise 'Could not determine current version using bump' unless $CHILD_STATUS.success?
268
+
269
+ output.lines.last.chomp
270
+ end
271
+ end
272
+
273
+ # @!attribute [rw] release_branch
274
+ #
275
+ # The name of the release branch being created
276
+ #
277
+ # @example By default, `release_branch` is based on the value returned by `next_release_tag`
278
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
279
+ # project = CreateGithubRelease::Project.new(options)
280
+ # project.next_release_tag = 'v1.0.0'
281
+ # project.release_branch #=> 'release-v1.0.0'
282
+ #
283
+ # @example It can also be set explicitly
284
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
285
+ # project = CreateGithubRelease::Project.new(options)
286
+ # project.next_release_branch = 'release-v1.0.0'
287
+ # project.next_release_branch #=> 'release-v1.0.0'
288
+ #
289
+ # @return [String]
290
+ #
291
+ # @raise [RuntimeError] if the bump command fails
292
+ #
293
+ # @api public
294
+ #
295
+ def release_branch
296
+ @release_branch ||= options.release_branch || "release-#{next_release_tag}"
297
+ end
298
+
299
+ # @!attribute [rw] release_log_url
300
+ #
301
+ # The URL of the page containing a list of the changes in the release
302
+ #
303
+ # @example By default, `release_log_url` is based on `remote_url`, `last_release_tag`, and `next_release_tag`
304
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
305
+ # project = CreateGithubRelease::Project.new(options)
306
+ # project.remote_url = URI.parse('https://github.com/org/repo')
307
+ # project.last_release_tag = 'v0.0.1'
308
+ # project.next_release_tag = 'v1.0.0'
309
+ # project.release_log_url #=> #<URI::HTTPS https://github.com/org/repo/compare/v0.0.1..v1.0.0>
310
+ #
311
+ # @example It can also be set explicitly
312
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
313
+ # project = CreateGithubRelease::Project.new(options)
314
+ # project.release_log_url = URI.parse('https://github.com/org/repo/compare/v0.0.1..v1.0.0')
315
+ # project.release_log_url #=> #<URI::HTTPS https://github.com/org/repo/compare/v0.0.1..v1.0.0>
316
+ #
317
+ # @return [URI]
318
+ #
319
+ # @raise [RuntimeError] if the bump command fails
320
+ #
321
+ # @api public
322
+ #
323
+ def release_log_url
324
+ @release_log_url ||= URI.parse("#{remote_url}/compare/#{last_release_tag}..#{next_release_tag}")
325
+ end
326
+
327
+ # @!attribute [rw] release_type
328
+ #
329
+ # The type of the release being created (e.g. 'major', 'minor', 'patch')
330
+ #
331
+ # @note this must be one of the values accepted by the `bump` command
332
+ #
333
+ # @example By default, this value comes from the options object
334
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
335
+ # project = CreateGithubRelease::Project.new(options)
336
+ # project.release_type #=> 'major'
337
+ #
338
+ # @example It can also be set explicitly
339
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
340
+ # project = CreateGithubRelease::Project.new(options)
341
+ # project.release_type = 'patch'
342
+ # project.release_type #=> 'patch'
343
+ #
344
+ # @return [String]
345
+ #
346
+ # @raise [ArgumentError] if a release type was not provided
347
+ #
348
+ # @api public
349
+ #
350
+ def release_type
351
+ @release_type ||= options.release_type || raise(ArgumentError, 'release_type is required')
352
+ end
353
+
354
+ # @!attribute [rw] release_url
355
+ #
356
+ # The URL of the page containing a list of the changes in the release
357
+ #
358
+ # @example By default, `release_url` is based on `remote_url` and `next_release_tag`
359
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
360
+ # project = CreateGithubRelease::Project.new(options)
361
+ # project.remote_url = URI.parse('https://github.com/org/repo')
362
+ # project.next_release_tag = 'v1.0.0'
363
+ # project.release_url #=> #<URI::HTTPS https://github.com/org/repo/releases/tag/v1.0.0>
364
+ #
365
+ # @example It can also be set explicitly
366
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
367
+ # project = CreateGithubRelease::Project.new(options)
368
+ # project.release_url = URI.parse('https://github.com/org/repo/releases/tag/v1.0.0')
369
+ # project.release_url #=> #<URI::HTTPS https://github.com/org/repo/releases/tag/v1.0.0>
370
+ #
371
+ # @return [URI]
372
+ #
373
+ # @api public
374
+ #
375
+ def release_url
376
+ @release_url ||= URI.parse("#{remote_url}/releases/tag/#{next_release_tag}")
377
+ end
378
+
379
+ # @!attribute [rw] remote
380
+ #
381
+ # The git remote used to determine the repository url
382
+ #
383
+ # @example By default, 'origin' is used
384
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
385
+ # project = CreateGithubRelease::Project.new(options)
386
+ # project.remote #=> 'origin'
387
+ #
388
+ # @example It can also be set in the options
389
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major', remote: 'upstream')
390
+ # project = CreateGithubRelease::Project.new(options)
391
+ # project.remote #=> 'upstream'
392
+ #
393
+ # @example It can also be set explicitly
394
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
395
+ # project = CreateGithubRelease::Project.new(options)
396
+ # project.remote = 'upstream'
397
+ # project.remote #=> 'upstream'
398
+ #
399
+ # @return [String]
400
+ #
401
+ # @api public
402
+ #
403
+ def remote
404
+ @remote ||= options.remote || 'origin'
405
+ end
406
+
407
+ # @!attribute [rw] remote_base_url
408
+ #
409
+ # The base part of the remote url (e.g. 'https://github.com/')
410
+ #
411
+ # @example By default, this value is based on `remote_url`
412
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
413
+ # project = CreateGithubRelease::Project.new(options)
414
+ # project.remote_url = URI.parse('https://github.com/org/repo')
415
+ # project.remote #=> #<URI::HTTPS https://github.com/>
416
+ #
417
+ # @example It can also be set explicitly
418
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
419
+ # project = CreateGithubRelease::Project.new(options)
420
+ # project.remote_base_url = URI.parse('https://github.com/')
421
+ # project.remote_base_url #=> #<URI::HTTPS https://github.com/>
422
+ #
423
+ # @return [URI]
424
+ #
425
+ # @api public
426
+ #
427
+ def remote_base_url
428
+ @remote_base_url ||= URI.parse(remote_url.to_s[0..-remote_url.path.length])
429
+ end
430
+
431
+ # @!attribute [rw] remote_repository
432
+ #
433
+ # The git remote owner and repository name (e.g. 'org/repo')
434
+ #
435
+ # @example By default, this value is based on `remote_url`
436
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
437
+ # project = CreateGithubRelease::Project.new(options)
438
+ # project.remote_url = URI.parse('htps://github.com/org/repo')
439
+ # project.remote_repository #=> 'org/repo'
440
+ #
441
+ # @example It can also be set explicitly
442
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
443
+ # project = CreateGithubRelease::Project.new(options)
444
+ # project.remote_repository = 'org/repo'
445
+ #
446
+ # @return [String]
447
+ #
448
+ # @api public
449
+ #
450
+ def remote_repository
451
+ @remote_repository ||= remote_url.path.sub(%r{^/}, '').sub(/\.git$/, '')
452
+ end
453
+
454
+ # @!attribute [rw] remote_url
455
+ #
456
+ # The URL of the git remote repository (e.g. 'https://github.com/org/repo')
457
+ #
458
+ # @example By default, this value is based on `remote` and the `git remote get-url` command
459
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
460
+ # project = CreateGithubRelease::Project.new(options)
461
+ # project.remote #=> #<URI::HTTPS https://github.com/org/repo>
462
+ #
463
+ # @example It can also be set explicitly
464
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
465
+ # project = CreateGithubRelease::Project.new(options)
466
+ # project.remote_url = URI.parse('https://github.com/org/repo')
467
+ # project.remote_url #=> #<URI::HTTPS https://github.com/org/repo>
468
+ #
469
+ # @return [URI]
470
+ #
471
+ # @api public
472
+ #
473
+ def remote_url
474
+ @remote_url ||= begin
475
+ remote_url_string = `git remote get-url '#{remote}'`
476
+ raise "Could not determine remote url for remote '#{remote}'" unless $CHILD_STATUS.success?
477
+
478
+ remote_url_string = remote_url_string.chomp
479
+ remote_url_string = remote_url_string[0..-5] if remote_url_string.end_with?('.git')
480
+ URI.parse(remote_url_string)
481
+ end
482
+ end
483
+
484
+ # @!attribute [rw] changelog_path
485
+ #
486
+ # The path relative to the project root where the changelog is located
487
+ #
488
+ # @example By default, this value is 'CHANGELOG.md'
489
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
490
+ # project = CreateGithubRelease::Project.new(options)
491
+ # project.changelog_path #=> 'CHANGELOG.md'
492
+ #
493
+ # @example It can also be set in the options
494
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major', changelog_path: 'docs/CHANGES.txt')
495
+ # project = CreateGithubRelease::Project.new(options)
496
+ # project.remote_repository = 'docs/CHANGES.txt'
497
+ #
498
+ # @example It can also be set explicitly
499
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
500
+ # project = CreateGithubRelease::Project.new(options)
501
+ # project.changelog_path = 'docs/CHANGES.txt'
502
+ # project.remote_repository = 'docs/CHANGES.txt'
503
+ #
504
+ # @return [String]
505
+ #
506
+ # @api public
507
+ #
508
+ def changelog_path
509
+ @changelog_path ||= options.changelog_path || 'CHANGELOG.md'
510
+ end
511
+
512
+ # @!attribute [rw] changes
513
+ #
514
+ # An array containing the changes since the last_release_tag
515
+ #
516
+ # Calls `git log HEAD <next_release_tag>` to list the changes.
517
+ #
518
+ # @example By default, uses `git log`
519
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
520
+ # project = CreateGithubRelease::Project.new(options)
521
+ # pp project.changes
522
+ # [
523
+ # #<CreateGithubRelease::Change:0x00000001084b92f0 @sha="24bdd02", @subject="Foo feature">,
524
+ # #<CreateGithubRelease::Change:0x00000001084b93e0 @sha="d75e1e9", @subject="Bar feature">
525
+ # ]
526
+ #
527
+ # @example It can also be set explicitly
528
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
529
+ # project = CreateGithubRelease::Project.new(options)
530
+ # project.changes = 'All the changes'
531
+ # project.changes #=> 'All the changes'
532
+ #
533
+ # @return [Array<CreateGithubRelease>]
534
+ #
535
+ # @api public
536
+ #
537
+ def changes
538
+ @changes ||= begin
539
+ command = "git log 'HEAD' '^#{last_release_tag}' --oneline --format='format:%h\t%s'"
540
+ git_log = `#{command}`
541
+ raise "Could not determine changes since #{last_release_tag}" unless $CHILD_STATUS.success?
542
+
543
+ git_log.split("\n").map { |l| ::CreateGithubRelease::Change.new(*l.split("\t")) }
544
+ end
545
+ end
546
+
547
+ # @!attribute [rw] next_release_description
548
+ #
549
+ # The formatted release description
550
+ #
551
+ # @example
552
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
553
+ # project = CreateGithubRelease::Project.new(options) do |p|
554
+ # p.remote_url = URI.parse('https://github.com/username/repo')
555
+ # p.last_release_tag = 'v0.1.0'
556
+ # p.next_release_tag = 'v1.0.0'
557
+ # p.next_release_date = Date.new(2022, 11, 7)
558
+ # p.changes = [
559
+ # CreateGithubRelease::Change.new('e718690', 'Release v1.0.0 (#3)'),
560
+ # CreateGithubRelease::Change.new('ab598f3', 'Fix Rubocop offenses (#2)')
561
+ # ]
562
+ # end
563
+ # puts project.next_release_description
564
+ # ## v1.0.0 (2022-11-07)
565
+ #
566
+ # [Full Changelog](https://github.com/username/repo/compare/v0.1.0...v1.0.0
567
+ #
568
+ # * e718690 Release v1.0.0 (#3)
569
+ # * ab598f3 Fix Rubocop offenses (#2)
570
+ #
571
+ # @return [String]
572
+ #
573
+ # @api public
574
+ #
575
+ def next_release_description
576
+ @next_release_description ||= <<~DESCRIPTION
577
+ ## #{next_release_tag} (#{next_release_date.strftime('%Y-%m-%d')})
578
+
579
+ [Full Changelog](#{release_log_url})
580
+
581
+ Changes since #{last_release_tag}:
582
+
583
+ #{list_of_changes}
584
+ DESCRIPTION
585
+ end
586
+
587
+ # @!attribute [rw] last_release_changelog
588
+ #
589
+ # The existing changelog (of the last release) as a string
590
+ #
591
+ # @example
592
+ # changelog_path = 'TEST_CHANGELOG.md'
593
+ # File.write(changelog_path, <<~CHANGELOG)
594
+ # # Project Changelog
595
+ #
596
+ # ## v0.1.0 (2021-11-07)
597
+ #
598
+ # * e718690 Release v0.1.0 (#3)
599
+ # CHANGELOG
600
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
601
+ # project = CreateGithubRelease::Project.new(options) do |p|
602
+ # p.changelog_path = changelog_path
603
+ # end
604
+ # puts project.last_release_changelog
605
+ # # Project Changelog
606
+ #
607
+ # ## v0.1.0 (2021-11-07)
608
+ #
609
+ # * e718690 Release v0.1.0 (#3)
610
+ #
611
+ # @return [String]
612
+ #
613
+ # @api public
614
+ #
615
+ def last_release_changelog
616
+ @last_release_changelog ||= begin
617
+ File.read(changelog_path)
618
+ rescue Errno::ENOENT
619
+ ''
620
+ rescue StandardError
621
+ raise 'Could not read the changelog file'
622
+ end
623
+ end
624
+
625
+ # @!attribute [rw] next_release_changelog
626
+ #
627
+ # The changelog of the next release as a string
628
+ #
629
+ # This is the result of inserting next_release_description into
630
+ # last_release_changelog.
631
+ #
632
+ # @example
633
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
634
+ # project = CreateGithubRelease::Project.new(options) do |p|
635
+ # p.last_release_changelog = <<~CHANGELOG
636
+ # # Project Changelog
637
+ #
638
+ # ## v0.1.0 (2021-11-07)
639
+ #
640
+ # * e718690 Release v0.1.0 (#3)
641
+ # CHANGELOG
642
+ # p.next_release_description = <<~next_release_description
643
+ # ## v1.0.0 (2022-11-07)
644
+ #
645
+ # [Full Changelog](http://github.com/org/repo/compare/v0.1.0...v1.0.0)
646
+ #
647
+ # * e718690 Release v1.0.0 (#3)
648
+ # * ab598f3 Add the FizzBuzz Feature (#2)
649
+ # next_release_description
650
+ # end
651
+ # puts project.next_release_changelog
652
+ #
653
+ # @return [String]
654
+ #
655
+ # @api public
656
+ #
657
+ def next_release_changelog
658
+ @next_release_changelog ||=
659
+ CreateGithubRelease::Changelog.new(last_release_changelog, next_release_description).to_s
660
+ end
661
+
662
+ # Show the project details as a string
663
+ #
664
+ # @example
665
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
666
+ # project = CreateGithubRelease::Project.new(options)
667
+ # puts projects.to_s
668
+ # default_branch: main
669
+ # next_release_tag: v1.0.0
670
+ # next_release_date: 2023-02-01
671
+ # next_release_version: 1.0.0
672
+ # last_release_tag: v0.1.0
673
+ # last_release_version: 0.1.0
674
+ # release_branch: release-v1.0.0
675
+ # release_log_url: https://github.com/org/repo/compare/v0.1.0..v1.0.0
676
+ # release_type: major
677
+ # release_url: https://github.com/org/repo/releases/tag/v1.0.0
678
+ # remote: origin
679
+ # remote_base_url: https://github.com/
680
+ # remote_repository: org/repo
681
+ # remote_url: https://github.com/org/repo
682
+ # changelog_path: CHANGELOG.md
683
+ # verbose?: false
684
+ # quiet?: false
685
+ #
686
+ # @return [String]
687
+ #
688
+ # @api public
689
+ #
690
+ def to_s
691
+ <<~OUTPUT
692
+ default_branch: #{default_branch}
693
+ next_release_tag: #{next_release_tag}
694
+ next_release_date: #{next_release_date}
695
+ next_release_version: #{next_release_version}
696
+ last_release_tag: #{last_release_tag}
697
+ last_release_version: #{last_release_version}
698
+ release_branch: #{release_branch}
699
+ release_log_url: #{release_log_url}
700
+ release_type: #{release_type}
701
+ release_url: #{release_url}
702
+ remote: #{remote}
703
+ remote_base_url: #{remote_base_url}
704
+ remote_repository: #{remote_repository}
705
+ remote_url: #{remote_url}
706
+ verbose?: #{verbose?}
707
+ quiet?: #{quiet?}
708
+ OUTPUT
709
+ end
710
+
711
+ # @!attribute [rw] verbose
712
+ #
713
+ # If `true` enables verbose output
714
+ #
715
+ # @example By default, this value is based on the `verbose` option
716
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major', verbose: true)
717
+ # project = CreateGithubRelease::Project.new(options)
718
+ # project.verbose? #=> true
719
+ #
720
+ # @example It can also be set explicitly
721
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
722
+ # project = CreateGithubRelease::Project.new(options)
723
+ # project.verbose = true
724
+ # project.verbose? #=> true
725
+ #
726
+ # @return [Boolean]
727
+ #
728
+ # @api public
729
+ #
730
+ def verbose
731
+ @verbose ||= options.verbose || false
732
+ end
733
+
734
+ alias verbose? verbose
735
+
736
+ # @!attribute [rw] quiet
737
+ #
738
+ # If `true` supresses all output
739
+ #
740
+ # @example By default, this value is based on the `quiet` option
741
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major', quiet: true)
742
+ # project = CreateGithubRelease::Project.new(options)
743
+ # project.quiet? #=> true
744
+ #
745
+ # @example It can also be set explicitly
746
+ # options = CreateGithubRelease::CommandLineOptions.new(release_type: 'major')
747
+ # project = CreateGithubRelease::Project.new(options)
748
+ # project.quiet = true
749
+ # project.quiet? #=> true
750
+ #
751
+ # @return [Boolean]
752
+ #
753
+ # @api public
754
+ #
755
+ def quiet
756
+ @quiet ||= options.quiet || false
757
+ end
758
+
759
+ alias quiet? quiet
760
+
761
+ private
762
+
763
+ # The list of changes in the release as a string
764
+ # @return [String] The list of changes in the release as a string
765
+ # @api private
766
+ def list_of_changes
767
+ return '* No changes' if changes.empty?
768
+
769
+ changes.map do |change|
770
+ "* #{change.sha} #{change.subject}"
771
+ end.join("\n")
772
+ end
773
+
774
+ # `true` if the `#verbose?` flag is `true`
775
+ # @return [Boolean]
776
+ # @api private
777
+ def backtick_debug?
778
+ verbose?
779
+ end
780
+
781
+ # Override the backtick operator for this class to call super and output
782
+ # debug information if `verbose?` is true
783
+ include CreateGithubRelease::BacktickDebug
784
+ end
785
+
786
+ # rubocop:enable Metrics/ClassLength
787
+ end