reissue 0.4.22 → 0.5.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a9e4d5dc8c0b04d5c41514285fa8e8f2e7e87cc028b156ab7de97473e1e58276
4
- data.tar.gz: e4af6b1a8061ec686f33e852b0abccd0d4e92335b87ad48c286e0645b7932ed7
3
+ metadata.gz: 6c5e8636f0e88ac91af9c6dec7033c12a62e55d6c47a0eb2212dc03d51877058
4
+ data.tar.gz: a83714b57eb820b00a4b3cbfdab77640d5062f7d3e00aba0b52aca83d89cbad0
5
5
  SHA512:
6
- metadata.gz: 12fe862113b9d5a765f7efd562ce4555b873e8496068c480f3a0531b873fbbcedc10bf44c1d971e7404ab1236f43125f08c31bac988ba88fb800dd78d5021618
7
- data.tar.gz: 2235ef992fdfbda13a06e27bf1df4b582cb3ba9cfa034cdcc6bc1e7c61f8fa33167f553031dc7401eefb29edb59fc5e8ecbe699cdec3f2431796002356115b32
6
+ metadata.gz: 63a416ec48050137cd7ee8ecd1d295395aa2e32b243fbb0835aecd185a48ddf36abebfe1c979450ef6be9cec46a67c2163896fba5b265874dc7b13eb1bc76ad5
7
+ data.tar.gz: d6b7f8c2e94997cd952c8e84aa74f29659d06cae56d338e20c10d37405ee1eb8dbf5615688268d366855942d1745bd36d7cd2f8881a1c29104c6045ad29a1947
data/CHANGELOG.md CHANGED
@@ -5,10 +5,23 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](http://keepachangelog.com/)
6
6
  and this project adheres to [Semantic Versioning](http://semver.org/).
7
7
 
8
- ## [0.4.22] - 2026-04-09
8
+ ## [0.5.1] - 2026-07-24
9
+
10
+ ### Added
11
+
12
+ - reissue:next_version task reports the version the next release would use without changing anything (9feb212)
13
+ - reissue:initialize[runbook] scaffolds a starter runbook file (dfa521a)
14
+
15
+ ### Changed
16
+
17
+ - Runbook finalize and new-version reset preserve a custom title and preamble (a0d874c)
9
18
 
10
19
  ### Fixed
11
20
 
12
- - changelog sections not following changelog_sections order on release (5f9c475)
21
+ - Releases no longer publish a version whose changelog entry and git tag name the previous version (acf4c1a)
22
+
23
+ ## [0.4.23] - 2026-07-21
24
+
25
+ ### Added
13
26
 
14
- ## [0.4.21] - 2026-03-11
27
+ - Optional runbook_file setting maintains a post-release runbook populated from Runbook: git trailers or direct edits (b41ba15)
data/README.md CHANGED
@@ -345,6 +345,58 @@ Version trailers are case-insensitive:
345
345
  - **New features:** Use `Version: minor` for backwards-compatible new functionality
346
346
  - **Bug fixes:** Use `Version: patch` for backwards-compatible bug fixes (or omit - patch is the default post-release bump)
347
347
 
348
+ ## Post-release Runbook
349
+
350
+ Reissue can maintain a runbook file listing manual steps to perform after a release — run a rake task, clean up data, re-index documents, and so on. This is aimed at applications; gems are unlikely to need it.
351
+
352
+ ```ruby
353
+ Reissue::Task.create :reissue do |task|
354
+ task.version_file = "config/version.rb"
355
+ task.fragment = :git
356
+ task.runbook_file = "RUNBOOK.md" # Default: nil (disabled)
357
+ end
358
+ ```
359
+
360
+ You don't have to create the file yourself — the first `rake reissue` or `rake reissue:finalize` writes it when `runbook_file` is set. To start from a template you can customize, run `rake reissue:initialize[runbook]`, which creates `RUNBOOK.md` (pass a different name, e.g. `rake reissue:initialize[OPERATIONS.md]`, to use another path).
361
+
362
+ The runbook holds only the latest release. Items are checkboxes so operators can tick them off:
363
+
364
+ ```markdown
365
+ # Runbook
366
+
367
+ Steps to perform after releasing the version below.
368
+
369
+ ## [1.2.3] - 2026-07-21
370
+
371
+ - [ ] Run `rake data:cleanup` (abc1234)
372
+ - [ ] Re-index search documents (def5678)
373
+ ```
374
+
375
+ The title and the preamble beneath it are yours to edit. Give the file a custom heading and intro and reissue preserves them across finalize and new-version resets — only the version heading and the checklist are rewritten, the same way a custom preamble survives in `CHANGELOG.md`.
376
+
377
+ ### Adding Runbook Items
378
+
379
+ Add `Runbook:` trailers to commit messages (case-insensitive, independent of the `fragment` setting):
380
+
381
+ ```bash
382
+ git commit -m "Add data migration
383
+
384
+ Fixed: Duplicate user records
385
+ Runbook: Run \`rake data:cleanup\` after deploy"
386
+ ```
387
+
388
+ You can also edit the checklist under `## [Unreleased]` directly; direct edits and trailers are merged and deduplicated.
389
+
390
+ Note: `Runbook:` is reserved for the runbook — do not add "Runbook" to `changelog_sections`.
391
+
392
+ ### Lifecycle
393
+
394
+ 1. **During development** — items accumulate from `Runbook:` trailers and direct edits.
395
+ 2. **Finalize** (`rake reissue:finalize` / before `rake build`) — trailers since the last version tag are merged into the file and the header is stamped with the release version and date, matching the changelog. The file is committed with the finalize commit, so the release tag contains the finalized runbook.
396
+ 3. **New version setup** (`rake reissue` after release) — the runbook is cleared back to a header-only `## [Unreleased]` section. Prior runbooks remain reachable via release tags.
397
+
398
+ `rake reissue:preview` also shows pending runbook items when `runbook_file` is configured.
399
+
348
400
  ## Development
349
401
 
350
402
  After checking out the repo, run `bin/setup` to install dependencies. Then run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt.
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "pathname"
4
-
5
3
  module Reissue
6
4
  # Handler for reading fragments from a directory
7
5
  class DirectoryFragmentHandler < FragmentHandler
@@ -44,6 +44,16 @@ module Reissue
44
44
  find_last_tag
45
45
  end
46
46
 
47
+ # Read runbook entries from git commit trailers
48
+ #
49
+ # @return [Array<String>] Runbook items with the short SHA appended
50
+ def read_runbook_entries
51
+ return [] unless git_available? && in_git_repo?
52
+
53
+ commits = commits_since_last_tag
54
+ parse_runbook_entries_from_commits(commits)
55
+ end
56
+
47
57
  # Read version bump from git commit trailers
48
58
  #
49
59
  # @return [Symbol, nil] One of :major, :minor, :patch, or nil if none found
@@ -185,6 +195,44 @@ module Reissue
185
195
  result
186
196
  end
187
197
 
198
+ RUNBOOK_TRAILER_REGEX = /^runbook:\s*(.+)$/i
199
+
200
+ def parse_runbook_entries_from_commits(commits)
201
+ entries = []
202
+
203
+ commits.each do |commit|
204
+ sha = commit[:sha]
205
+ lines = commit[:message].lines
206
+
207
+ i = 0
208
+ while i < lines.length
209
+ line = lines[i].rstrip
210
+ i += 1
211
+ next if line.strip.empty?
212
+
213
+ if (match = line.match(RUNBOOK_TRAILER_REGEX))
214
+ trailer_value = match[1].strip
215
+
216
+ # Collect continuation lines (non-empty lines that don't start a new trailer)
217
+ while i < lines.length
218
+ next_line = lines[i].rstrip
219
+ break if next_line.strip.empty?
220
+ break if next_line.match(RUNBOOK_TRAILER_REGEX)
221
+ break if next_line.match(trailer_regex)
222
+ break if next_line.match?(/^[A-Za-z][\w-]+:\s/)
223
+
224
+ trailer_value += " #{next_line.strip}"
225
+ i += 1
226
+ end
227
+
228
+ entries << "#{trailer_value} (#{sha})"
229
+ end
230
+ end
231
+ end
232
+
233
+ entries
234
+ end
235
+
188
236
  def normalize_section_name(name)
189
237
  # Normalize to proper case (e.g., "FIXED" -> "Fixed", "added" -> "Added")
190
238
  name.capitalize
data/lib/reissue/gem.rb CHANGED
@@ -7,6 +7,30 @@ module Reissue
7
7
  super
8
8
  @updated_paths << "checksums"
9
9
  end
10
+
11
+ # Keep bundler's gemspec in step with the bump.
12
+ #
13
+ # Bundler loads the gemspec when bundler/gem_tasks is required, well before
14
+ # reissue:bump rewrites the version file. `gem build` shells out and re-reads
15
+ # the file, so the gem itself carries the bumped version, but the release tag
16
+ # and bundler's confirmation messages read the copy held in memory. Without
17
+ # this they name the version from before the bump, and the release publishes
18
+ # one version under another version's tag.
19
+ def apply_version_bump(updater, bump)
20
+ new_version = super
21
+ sync_bundler_gemspec_version(new_version)
22
+ new_version
23
+ end
24
+
25
+ # @param new_version [String] the version the bump landed on
26
+ def sync_bundler_gemspec_version(new_version)
27
+ return unless defined?(Bundler::GemHelper)
28
+
29
+ helper = Bundler::GemHelper.instance
30
+ return unless helper&.gemspec
31
+
32
+ helper.gemspec.version = ::Gem::Version.new(new_version)
33
+ end
10
34
  end
11
35
  end
12
36
  Reissue::Task.prepend Reissue::Gem
data/lib/reissue/rake.rb CHANGED
@@ -37,6 +37,12 @@ module Reissue
37
37
  # Provide a callable to decide how to store the files.
38
38
  attr_accessor :retain_changelogs
39
39
 
40
+ # The path to a runbook file listing steps to perform after a release.
41
+ # Populated from "Runbook:" git trailers or direct edits, finalized with
42
+ # the release version, and cleared when a new version is prepared.
43
+ # Default: nil (disabled).
44
+ attr_accessor :runbook_file
45
+
40
46
  # The fragment configuration for changelog entries.
41
47
  # @return [String, nil] nil (disabled) or a directory path string for fragment files
42
48
  # @example Using directory-based fragments
@@ -126,6 +132,7 @@ module Reissue
126
132
  @updated_paths = []
127
133
  @changelog_file = "CHANGELOG.md"
128
134
  @retain_changelogs = false
135
+ @runbook_file = nil
129
136
  @fragment = nil
130
137
  @clear_fragments = false
131
138
  @commit = true
@@ -181,15 +188,87 @@ module Reissue
181
188
  end
182
189
  end
183
190
 
191
+ # The version the next release would use, derived exactly the way reissue:bump
192
+ # derives it, without writing anything.
193
+ #
194
+ # @return [Gem::Version] the version a release would publish
195
+ def next_release_version
196
+ require_relative "fragment_handler"
197
+ require_relative "version_updater"
198
+
199
+ # reissue:bump only consults git trailers for :git fragments.
200
+ handler = (Reissue::FragmentHandler.for(:git, tag_pattern: tag_pattern) if fragment == :git)
201
+ tag_version = handler&.last_tag_version
202
+ bump = handler&.read_version_bump
203
+ updater = Reissue::VersionUpdater.new(version_file, version_redo_proc: version_redo_proc)
204
+
205
+ if deferred_versioning
206
+ # The version file holds "Unreleased"; finalize resolves the real version
207
+ # from the last tag and the trailer.
208
+ unless tag_version && bump
209
+ raise "Cannot determine the next version. Deferred versioning resolves it from " \
210
+ "the last version tag and a Version: trailer, and one of those is missing."
211
+ end
212
+ return updater.redo(tag_version, bump)
213
+ end
214
+
215
+ current_version = ::Gem::Version.new(File.read(version_file).match(Reissue::VersionUpdater::VERSION_MATCH)[0])
216
+
217
+ if bump.nil?
218
+ current_version
219
+ elsif tag_version && tag_version != current_version
220
+ # A post-release bump already moved the file past the tag, so the trailer
221
+ # only counts if it would land beyond where the file already is.
222
+ [updater.redo(tag_version, bump), current_version].max
223
+ else
224
+ updater.redo(current_version, bump)
225
+ end
226
+ end
227
+
228
+ # Apply a version bump and leave the changelog agreeing with it.
229
+ #
230
+ # @param updater [Reissue::VersionUpdater] the updater to bump with
231
+ # @param bump [Symbol] the segment to bump
232
+ # @return [String] the version the bump landed on
233
+ def apply_version_bump(updater, bump)
234
+ new_version = updater.call(bump)
235
+ realign_unreleased_changelog(new_version)
236
+ bundle
237
+ puts "Version bumped (#{bump}) to #{new_version}"
238
+ new_version
239
+ end
240
+
241
+ # Point the changelog's unreleased entry at the version just bumped to.
242
+ #
243
+ # The bump moves the version file forward, but the unreleased entry still
244
+ # carries the number from the previous cycle. reissue:finalize dates whatever
245
+ # that entry says, so leaving it alone publishes a gem built at the new
246
+ # version while the only dated changelog entry names the old one.
247
+ #
248
+ # @param new_version [String] the version the bump landed on
249
+ def realign_unreleased_changelog(new_version)
250
+ return unless changelog_file && File.exist?(changelog_file)
251
+
252
+ changelog = Reissue::Parser.parse(File.read(changelog_file))
253
+ unreleased = changelog["versions"].find { |version| version["date"] == "Unreleased" }
254
+ return if unreleased.nil? || unreleased["version"] == new_version
255
+
256
+ unreleased["version"] = new_version
257
+
258
+ changelog_updater = Reissue::ChangelogUpdater.new(changelog_file)
259
+ changelog_updater.instance_variable_set(:@changelog, changelog)
260
+ changelog_updater.write(changelog_file)
261
+ end
262
+
184
263
  # Check if there are staged changes ready to commit.
185
264
  def changes_to_commit?
186
265
  _, _, status = Open3.capture3("git diff --cached --quiet")
187
266
  !status.success?
188
267
  end
189
268
 
190
- # Stage updated_paths that exist on disk.
269
+ # Stage updated_paths (and the runbook file) that exist on disk.
191
270
  def stage_updated_paths
192
- existing = updated_paths.select { |p| File.exist?(p) }
271
+ existing = (updated_paths + [runbook_file].compact).select { |p| File.exist?(p) }
193
272
  if existing.any?
194
273
  run_command("git add #{existing.join(" ")}", "Failed to stage additional paths: #{existing.join(", ")}")
195
274
  end
@@ -206,7 +285,8 @@ module Reissue
206
285
  changelog_file:,
207
286
  version_limit:,
208
287
  fragment: fragment,
209
- tag_pattern:
288
+ tag_pattern:,
289
+ runbook_file:
210
290
  )
211
291
  else
212
292
  segment = args[:segment] || "patch"
@@ -217,7 +297,8 @@ module Reissue
217
297
  version_limit:,
218
298
  version_redo_proc:,
219
299
  fragment: fragment,
220
- tag_pattern:
300
+ tag_pattern:,
301
+ runbook_file:
221
302
  )
222
303
  end
223
304
  bundle
@@ -287,7 +368,8 @@ module Reissue
287
368
  fragment: fragment,
288
369
  tag_pattern:,
289
370
  version_file:,
290
- version_redo_proc:
371
+ version_redo_proc:,
372
+ runbook_file:
291
373
  )
292
374
  else
293
375
  date = args[:version_or_segment] || Time.now.strftime("%Y-%m-%d")
@@ -297,7 +379,8 @@ module Reissue
297
379
  retain_changelogs:,
298
380
  fragment: fragment,
299
381
  tag_pattern:,
300
- version_file:
382
+ version_file:,
383
+ runbook_file:
301
384
  )
302
385
  end
303
386
 
@@ -348,6 +431,15 @@ module Reissue
348
431
  end
349
432
  end
350
433
 
434
+ # Preview reports; it does not fail. next_release_version raises when
435
+ # deferred versioning has no tag or trailer to resolve from.
436
+ previewed_version = begin
437
+ next_release_version
438
+ rescue RuntimeError
439
+ nil
440
+ end
441
+ puts "Version to be released: #{previewed_version || "cannot be determined"}\n\n"
442
+
351
443
  entries = handler.read
352
444
 
353
445
  if entries.empty?
@@ -375,6 +467,20 @@ module Reissue
375
467
  puts "Fragment handling is not configured."
376
468
  puts "Set task.fragment to a directory path or :git to enable changelog fragments."
377
469
  end
470
+
471
+ if runbook_file
472
+ require_relative "fragment_handler"
473
+ runbook_entries = Reissue::FragmentHandler.for(:git, tag_pattern: tag_pattern).read_runbook_entries
474
+
475
+ puts
476
+ if runbook_entries.empty?
477
+ puts "No runbook items found."
478
+ puts " (No Runbook: trailers found since last version tag)"
479
+ else
480
+ puts "Runbook items that will be added to #{runbook_file}:\n\n"
481
+ runbook_entries.each { |item| puts "- [ ] #{item}" }
482
+ end
483
+ end
378
484
  end
379
485
 
380
486
  desc "Clear fragments"
@@ -393,6 +499,11 @@ module Reissue
393
499
  end
394
500
  end
395
501
 
502
+ desc "Print the version the next release would use, without changing anything"
503
+ task "#{name}:next_version" do
504
+ puts next_release_version
505
+ end
506
+
396
507
  desc "Bump version based on git trailers"
397
508
  task "#{name}:bump" do
398
509
  next if deferred_versioning
@@ -416,9 +527,7 @@ module Reissue
416
527
  if tag_version && current_version == tag_version
417
528
  if bump
418
529
  updater = Reissue::VersionUpdater.new(version_file, version_redo_proc: version_redo_proc)
419
- updater.call(bump)
420
- bundle
421
- puts "Version bumped (#{bump}) to #{updater.instance_variable_get(:@new_version)}"
530
+ apply_version_bump(updater, bump)
422
531
  end
423
532
  elsif tag_version && current_version != tag_version
424
533
  if bump
@@ -426,9 +535,7 @@ module Reissue
426
535
  desired_version = updater.redo(tag_version, bump)
427
536
 
428
537
  if desired_version > current_version
429
- updater.call(bump)
430
- bundle
431
- puts "Version bumped (#{bump}) to #{updater.instance_variable_get(:@new_version)}"
538
+ apply_version_bump(updater, bump)
432
539
  else
433
540
  puts "Version already bumped (#{tag_version} → #{current_version}), skipping"
434
541
  end
@@ -437,9 +544,7 @@ module Reissue
437
544
  end
438
545
  elsif bump
439
546
  updater = Reissue::VersionUpdater.new(version_file, version_redo_proc: version_redo_proc)
440
- updater.call(bump)
441
- bundle
442
- puts "Version bumped (#{bump}) to #{updater.instance_variable_get(:@new_version)}"
547
+ apply_version_bump(updater, bump)
443
548
  end
444
549
  end
445
550
  end
@@ -449,8 +554,8 @@ end
449
554
  # Define the reissue:initialize task as a standalone task
450
555
  # This works without any configuration, allowing users to bootstrap their project
451
556
  namespace :reissue do
452
- desc "Initialize reissue by creating a CHANGELOG.md and showing Rakefile configuration"
453
- task :initialize do
557
+ desc "Initialize reissue by creating a CHANGELOG.md and showing Rakefile configuration. Pass [runbook] to also create a RUNBOOK.md."
558
+ task :initialize, [:runbook] do |task, args|
454
559
  changelog_file = "CHANGELOG.md"
455
560
 
456
561
  if File.exist?(changelog_file)
@@ -460,6 +565,16 @@ namespace :reissue do
460
565
  puts "✓ Created #{changelog_file}"
461
566
  end
462
567
 
568
+ if args[:runbook]
569
+ runbook_file = (args[:runbook] == "runbook") ? "RUNBOOK.md" : args[:runbook]
570
+ if File.exist?(runbook_file)
571
+ puts "✓ #{runbook_file} already exists"
572
+ else
573
+ Reissue::Runbook.new(runbook_file).generate
574
+ puts "✓ Created #{runbook_file}"
575
+ end
576
+ end
577
+
463
578
  puts
464
579
  puts "Add the following to your Rakefile to enable reissue tasks:"
465
580
  puts
@@ -487,6 +602,10 @@ namespace :reissue do
487
602
  # task.fragment = "changelog_fragments" # Or use a directory of fragment files
488
603
  task.clear_fragments = false # Clear fragment files after release. Not necessary when using git trailers.
489
604
 
605
+ # Post-release runbook (mainly for applications)
606
+ # task.runbook_file = "RUNBOOK.md" # Maintain a runbook from Runbook: git trailers
607
+ # # Seed a starter file with: rake reissue:initialize[runbook]
608
+
490
609
  # Git workflow options
491
610
  task.commit = true # Auto-commit version bumps
492
611
  task.commit_finalize = true # Auto-commit changelog finalization
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reissue
4
+ # Maintains a post-release runbook file listing steps to perform after
5
+ # releasing a version. The file holds only the latest release.
6
+ class Runbook
7
+ ITEM_PATTERN = /^- (?:\[(?<checked>[ xX])\] )?(?<text>.+)$/
8
+
9
+ DEFAULT_TITLE = "Runbook"
10
+ DEFAULT_PREAMBLE = "Steps to perform after releasing the version below."
11
+
12
+ def initialize(runbook_file)
13
+ @runbook_file = runbook_file
14
+ end
15
+
16
+ attr_reader :runbook_file
17
+
18
+ # Writes a header-only template for the given version, preserving any
19
+ # custom title and preamble already in the file.
20
+ def generate(version: "Unreleased")
21
+ title, preamble = parsed_header
22
+ File.write(runbook_file, template(heading(version), title:, preamble:))
23
+ end
24
+
25
+ alias_method :clear, :generate
26
+
27
+ # Returns the text of each checklist item, without checkbox markers.
28
+ def items
29
+ parsed_items.map { |item| item[:text] }
30
+ end
31
+
32
+ # Stamps the header with the release version and date, merging directly
33
+ # edited items with trailer items (deduplicated by text). Preserves any
34
+ # custom title and preamble already in the file.
35
+ def finalize(version:, date:, trailer_items: [])
36
+ title, preamble = parsed_header
37
+ merged = parsed_items
38
+ texts = merged.map { |item| item[:text] }
39
+ trailer_items.each do |text|
40
+ merged << {text: text, checked: false} unless texts.include?(text)
41
+ end
42
+ File.write(runbook_file, template(heading(version, date), merged, title:, preamble:))
43
+ end
44
+
45
+ private
46
+
47
+ # Returns the [title, preamble] found before the first version heading,
48
+ # falling back to the defaults when the file is missing or header-only.
49
+ def parsed_header
50
+ return [DEFAULT_TITLE, DEFAULT_PREAMBLE] unless File.exist?(runbook_file)
51
+
52
+ header_chunk = File.read(runbook_file).split(/^## /, 2).first.to_s
53
+ title_match = header_chunk.match(/^#\s+(?<title>.+)$/)
54
+ title = title_match ? title_match[:title].strip : DEFAULT_TITLE
55
+ preamble = (title_match ? header_chunk.sub(title_match[0], "") : header_chunk).strip
56
+ preamble = DEFAULT_PREAMBLE if preamble.empty?
57
+ [title, preamble]
58
+ end
59
+
60
+ def parsed_items
61
+ return [] unless File.exist?(runbook_file)
62
+
63
+ File.read(runbook_file).lines.filter_map do |line|
64
+ match = line.rstrip.match(ITEM_PATTERN)
65
+ next unless match
66
+ {text: match[:text], checked: match[:checked]&.downcase == "x"}
67
+ end
68
+ end
69
+
70
+ def heading(version, date = nil)
71
+ date ? "## [#{version}] - #{date}" : "## [#{version}]"
72
+ end
73
+
74
+ def template(heading, items = [], title: DEFAULT_TITLE, preamble: DEFAULT_PREAMBLE)
75
+ header = <<~MARKDOWN
76
+ # #{title}
77
+
78
+ #{preamble}
79
+
80
+ #{heading}
81
+ MARKDOWN
82
+ return header if items.empty?
83
+
84
+ list = items.map { |item| "- [#{item[:checked] ? "x" : " "}] #{item[:text]}" }.join("\n")
85
+ "#{header}\n#{list}\n"
86
+ end
87
+ end
88
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Reissue
4
- VERSION = "0.4.22"
5
- RELEASE_DATE = "2026-04-09"
4
+ VERSION = "0.5.1"
5
+ RELEASE_DATE = "2026-07-24"
6
6
  end
@@ -21,12 +21,12 @@ module Reissue
21
21
  when :patch
22
22
  segments.slice(0, 2) + segments.slice(2..-1).then { |array|
23
23
  array[-1] = array[-1].next
24
- [array.map(&:to_s).join]
24
+ [array.join]
25
25
  }
26
26
  when :pre
27
27
  segments.slice(0, 3) + segments.slice(3..-1).then { |array|
28
28
  array[-1] = array[-1].next
29
- [array.map(&:to_s).join]
29
+ [array.join]
30
30
  }
31
31
  else
32
32
  raise ArgumentError, "Invalid segment name: #{segment_name}"
data/lib/reissue.rb CHANGED
@@ -4,6 +4,7 @@ require_relative "reissue/version"
4
4
  require_relative "reissue/version_updater"
5
5
  require_relative "reissue/changelog_updater"
6
6
  require_relative "reissue/fragment_handler"
7
+ require_relative "reissue/runbook"
7
8
 
8
9
  # Reissue is a module that provides functionality for updating version numbers and changelogs.
9
10
  module Reissue
@@ -40,7 +41,8 @@ module Reissue
40
41
  version_redo_proc: nil,
41
42
  fragment: nil,
42
43
  fragment_directory: nil,
43
- tag_pattern: nil
44
+ tag_pattern: nil,
45
+ runbook_file: nil
44
46
  )
45
47
  # Handle deprecation
46
48
  if fragment_directory && !fragment
@@ -55,10 +57,11 @@ module Reissue
55
57
  changelog_updater = ChangelogUpdater.new(changelog_file)
56
58
  changelog_updater.call(new_version, date:, changes:, changelog_file:, version_limit:, retain_changelogs:, fragment:, tag_pattern:)
57
59
  end
60
+ Runbook.new(runbook_file).clear if runbook_file
58
61
  new_version
59
62
  end
60
63
 
61
- def self.deferred_call(version_file:, changelog_file: "CHANGELOG.md", version_limit: 2, retain_changelogs: false, fragment: nil, tag_pattern: nil)
64
+ def self.deferred_call(version_file:, changelog_file: "CHANGELOG.md", version_limit: 2, retain_changelogs: false, fragment: nil, tag_pattern: nil, runbook_file: nil)
62
65
  version_updater = VersionUpdater.new(version_file)
63
66
  version_updater.set_version("Unreleased", version_file:)
64
67
  version_updater.update_release_date("Unreleased", version_file:)
@@ -68,10 +71,11 @@ module Reissue
68
71
  changelog_updater.call("Unreleased", date: nil, changes: {}, changelog_file:, version_limit:, retain_changelogs:, fragment:, tag_pattern:)
69
72
  end
70
73
 
74
+ Runbook.new(runbook_file).clear if runbook_file
71
75
  "Unreleased"
72
76
  end
73
77
 
74
- def self.deferred_finalize(date = Date.today, version: nil, segment: nil, changelog_file: "CHANGELOG.md", retain_changelogs: false, fragment: nil, tag_pattern: nil, version_file: nil, version_redo_proc: nil)
78
+ def self.deferred_finalize(date = Date.today, version: nil, segment: nil, changelog_file: "CHANGELOG.md", retain_changelogs: false, fragment: nil, tag_pattern: nil, version_file: nil, version_redo_proc: nil, runbook_file: nil)
75
79
  resolved_version = if version&.match?(/^\d/)
76
80
  version
77
81
  elsif segment || version
@@ -145,7 +149,14 @@ module Reissue
145
149
  end
146
150
 
147
151
  changelog = changelog_updater.finalize(date: date, changelog_file: changelog_file, retain_changelogs: retain_changelogs, resolved_version: resolved_version)
148
- changelog["versions"].first.slice("version", "date").values
152
+ changelog["versions"].first.slice("version", "date").values.tap do |finalized_version, finalized_date|
153
+ finalize_runbook(runbook_file, version: finalized_version, date: finalized_date, tag_pattern:) if runbook_file
154
+ end
155
+ end
156
+
157
+ private_class_method def self.finalize_runbook(runbook_file, version:, date:, tag_pattern: nil)
158
+ trailer_items = FragmentHandler.for(:git, tag_pattern:).read_runbook_entries
159
+ Runbook.new(runbook_file).finalize(version:, date:, trailer_items:)
149
160
  end
150
161
 
151
162
  private_class_method def self.resolve_version_from_tag(segment, tag_pattern: nil, version_redo_proc: nil)
@@ -165,7 +176,7 @@ module Reissue
165
176
  # @param fragment_directory [String] @deprecated Use fragment parameter instead
166
177
  #
167
178
  # @return [Array] The version number and release date.
168
- def self.finalize(date = Date.today, changelog_file: "CHANGELOG.md", retain_changelogs: false, fragment: nil, fragment_directory: nil, tag_pattern: nil, version_file: nil)
179
+ def self.finalize(date = Date.today, changelog_file: "CHANGELOG.md", retain_changelogs: false, fragment: nil, fragment_directory: nil, tag_pattern: nil, version_file: nil, runbook_file: nil)
169
180
  # Handle deprecation
170
181
  if fragment_directory && !fragment
171
182
  warn "[DEPRECATION] `fragment_directory` parameter is deprecated. Please use `fragment` instead."
@@ -224,7 +235,9 @@ module Reissue
224
235
  version_updater.update_release_date(date.to_s, version_file:)
225
236
  end
226
237
 
227
- changelog["versions"].first.slice("version", "date").values
238
+ changelog["versions"].first.slice("version", "date").values.tap do |finalized_version, finalized_date|
239
+ finalize_runbook(runbook_file, version: finalized_version, date: finalized_date, tag_pattern:) if runbook_file
240
+ end
228
241
  end
229
242
 
230
243
  # Reformats the changelog file to ensure it is correctly formatted.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reissue
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.22
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jim Gay
@@ -46,6 +46,7 @@ files:
46
46
  - lib/reissue/parser.rb
47
47
  - lib/reissue/printer.rb
48
48
  - lib/reissue/rake.rb
49
+ - lib/reissue/runbook.rb
49
50
  - lib/reissue/version.rb
50
51
  - lib/reissue/version_updater.rb
51
52
  homepage: https://github.com/SOFware/reissue
@@ -68,7 +69,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
68
69
  - !ruby/object:Gem::Version
69
70
  version: '0'
70
71
  requirements: []
71
- rubygems_version: 3.6.7
72
+ rubygems_version: 4.0.6
72
73
  specification_version: 4
73
74
  summary: Keep your versions and changelogs up to date and prepared for release.
74
75
  test_files: []