puma-release 0.1.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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +114 -0
  4. data/exe/puma-release +7 -0
  5. data/lib/puma_release/agent_client.rb +163 -0
  6. data/lib/puma_release/build_support.rb +46 -0
  7. data/lib/puma_release/changelog_generator.rb +171 -0
  8. data/lib/puma_release/changelog_validator.rb +85 -0
  9. data/lib/puma_release/ci_checker.rb +108 -0
  10. data/lib/puma_release/cli.rb +52 -0
  11. data/lib/puma_release/commands/build.rb +68 -0
  12. data/lib/puma_release/commands/github.rb +76 -0
  13. data/lib/puma_release/commands/prepare.rb +178 -0
  14. data/lib/puma_release/commands/run.rb +51 -0
  15. data/lib/puma_release/context.rb +167 -0
  16. data/lib/puma_release/contributor_resolver.rb +52 -0
  17. data/lib/puma_release/error.rb +5 -0
  18. data/lib/puma_release/events.rb +18 -0
  19. data/lib/puma_release/git_repo.rb +169 -0
  20. data/lib/puma_release/github_client.rb +163 -0
  21. data/lib/puma_release/link_reference_builder.rb +49 -0
  22. data/lib/puma_release/options.rb +47 -0
  23. data/lib/puma_release/release_range.rb +69 -0
  24. data/lib/puma_release/repo_files.rb +85 -0
  25. data/lib/puma_release/shell.rb +107 -0
  26. data/lib/puma_release/stage_detector.rb +66 -0
  27. data/lib/puma_release/ui.rb +36 -0
  28. data/lib/puma_release/upgrade_guide_writer.rb +106 -0
  29. data/lib/puma_release/version.rb +5 -0
  30. data/lib/puma_release/version_recommender.rb +151 -0
  31. data/lib/puma_release.rb +28 -0
  32. data/test/test_helper.rb +72 -0
  33. data/test/unit/agent_client_test.rb +116 -0
  34. data/test/unit/build_command_test.rb +23 -0
  35. data/test/unit/build_support_test.rb +6 -0
  36. data/test/unit/changelog_validator_test.rb +42 -0
  37. data/test/unit/context_test.rb +209 -0
  38. data/test/unit/contributor_resolver_test.rb +47 -0
  39. data/test/unit/git_repo_test.rb +169 -0
  40. data/test/unit/github_client_test.rb +90 -0
  41. data/test/unit/github_command_test.rb +153 -0
  42. data/test/unit/options_test.rb +17 -0
  43. data/test/unit/prepare_test.rb +136 -0
  44. data/test/unit/repo_files_test.rb +119 -0
  45. data/test/unit/run_test.rb +32 -0
  46. data/test/unit/shell_test.rb +29 -0
  47. data/test/unit/stage_detector_test.rb +72 -0
  48. metadata +143 -0
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../test_helper"
4
+
5
+ class RepoFilesTest < Minitest::Test
6
+ def test_current_version_reads_puma_const
7
+ temp_repo do |repo|
8
+ repo.join("lib/puma/const.rb").write("PUMA_VERSION = VERSION = \"7.2.0\"\nCODE_NAME = \"On The Corner\"\n")
9
+ repo.join("History.md").write("## 7.2.0 / 2026-01-20\n\n* Bugfixes\n * One thing ([#1])\n")
10
+ context = OpenStruct.new(version_file: repo.join("lib/puma/const.rb"), history_file: repo.join("History.md"))
11
+
12
+ assert_equal "7.2.0", PumaRelease::RepoFiles.new(context).current_version
13
+ end
14
+ end
15
+
16
+ def test_update_version_replaces_codename_for_minor_release
17
+ temp_repo do |repo|
18
+ repo.join("lib/puma/const.rb").write("PUMA_VERSION = VERSION = \"7.2.0\"\nCODE_NAME = \"On The Corner\"\n")
19
+ repo.join("History.md").write("")
20
+ context = OpenStruct.new(version_file: repo.join("lib/puma/const.rb"), history_file: repo.join("History.md"))
21
+
22
+ PumaRelease::RepoFiles.new(context).update_version!("7.3.0", "minor")
23
+
24
+ content = repo.join("lib/puma/const.rb").read
25
+ assert_includes content, 'PUMA_VERSION = VERSION = "7.3.0"'
26
+ assert_includes content, 'CODE_NAME = "INSERT CODENAME HERE"'
27
+ end
28
+ end
29
+
30
+ def test_release_name_includes_codename_for_dot_zero_releases
31
+ temp_repo do |repo|
32
+ repo.join("lib/puma/const.rb").write("PUMA_VERSION = VERSION = \"7.3.0\"\nCODE_NAME = \"Shiny New Codename\"\n")
33
+ repo.join("History.md").write("")
34
+ context = OpenStruct.new(version_file: repo.join("lib/puma/const.rb"), history_file: repo.join("History.md"))
35
+
36
+ assert_equal "v7.3.0 - Shiny New Codename", PumaRelease::RepoFiles.new(context).release_name("7.3.0")
37
+ end
38
+ end
39
+
40
+ def test_release_name_omits_codename_for_patch_releases
41
+ temp_repo do |repo|
42
+ repo.join("lib/puma/const.rb").write("PUMA_VERSION = VERSION = \"7.3.1\"\nCODE_NAME = \"Shiny New Codename\"\n")
43
+ repo.join("History.md").write("")
44
+ context = OpenStruct.new(version_file: repo.join("lib/puma/const.rb"), history_file: repo.join("History.md"))
45
+
46
+ assert_equal "v7.3.1", PumaRelease::RepoFiles.new(context).release_name("7.3.1")
47
+ end
48
+ end
49
+
50
+ def test_update_security_updates_supported_versions_for_major_release
51
+ temp_repo do |repo|
52
+ repo.join("SECURITY.md").write(<<~MD)
53
+ # Security Policy
54
+
55
+ ## Supported Versions
56
+
57
+ | Version | Supported |
58
+ | :------------ | :--------: |
59
+ | Latest release in 7.x | ✅ |
60
+ | Latest release in 6.x | ✅ |
61
+ | All other releases | ❌ |
62
+
63
+ ## Reporting a Vulnerability
64
+
65
+ Contact [Evan Phoenix.](https://github.com/evanphx)
66
+ MD
67
+ context = OpenStruct.new(security_file: repo.join("SECURITY.md"))
68
+
69
+ PumaRelease::RepoFiles.new(context).update_security!("8.0.0")
70
+
71
+ content = repo.join("SECURITY.md").read
72
+ assert_includes content, "Latest release in 8.x"
73
+ assert_includes content, "Latest release in 7.x"
74
+ refute_includes content, "Latest release in 6.x"
75
+ end
76
+ end
77
+
78
+ def test_update_security_preserves_rest_of_file
79
+ temp_repo do |repo|
80
+ repo.join("SECURITY.md").write(<<~MD)
81
+ # Security Policy
82
+
83
+ ## Supported Versions
84
+
85
+ | Version | Supported |
86
+ | :------------ | :--------: |
87
+ | Latest release in 7.x | ✅ |
88
+ | Latest release in 6.x | ✅ |
89
+ | All other releases | ❌ |
90
+
91
+ ## Reporting a Vulnerability
92
+
93
+ Contact [Evan Phoenix.](https://github.com/evanphx)
94
+ MD
95
+ context = OpenStruct.new(security_file: repo.join("SECURITY.md"))
96
+
97
+ PumaRelease::RepoFiles.new(context).update_security!("8.0.0")
98
+
99
+ content = repo.join("SECURITY.md").read
100
+ assert_includes content, "# Security Policy"
101
+ assert_includes content, "All other releases"
102
+ assert_includes content, "Contact [Evan Phoenix.](https://github.com/evanphx)"
103
+ end
104
+ end
105
+
106
+ def test_prepend_history_section_inserts_new_entry_and_refs
107
+ temp_repo do |repo|
108
+ repo.join("lib/puma/const.rb").write("PUMA_VERSION = VERSION = \"7.2.0\"\n")
109
+ repo.join("History.md").write("## 7.2.0 / 2026-01-20\n\n* Bugfixes\n * Existing ([#1])\n\n[#1]:https://example.test/1\n")
110
+ context = OpenStruct.new(version_file: repo.join("lib/puma/const.rb"), history_file: repo.join("History.md"))
111
+
112
+ PumaRelease::RepoFiles.new(context).prepend_history_section!("7.2.1", "* Bugfixes\n * New fix ([#2])", "[#2]:https://example.test/2")
113
+
114
+ updated = repo.join("History.md").read
115
+ assert_includes updated, "## 7.2.1 / #{Date.today.strftime("%Y-%m-%d")}"
116
+ assert_match(/\[#2\]:https:\/\/example\.test\/2\n\[#1\]:https:\/\/example\.test\/1/, updated)
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../test_helper"
4
+
5
+ class RunTest < Minitest::Test
6
+ class FakeUI
7
+ attr_reader :infos
8
+
9
+ def initialize
10
+ @infos = []
11
+ end
12
+
13
+ def info(message)
14
+ infos << message
15
+ end
16
+ end
17
+
18
+ def test_returns_complete_without_prompt_when_release_is_already_complete
19
+ ui = FakeUI.new
20
+ context = OpenStruct.new(ui:)
21
+ run = PumaRelease::Commands::Run.allocate
22
+ run.instance_variable_set(:@context, context)
23
+
24
+ detector = Object.new
25
+ def detector.next_step = :complete
26
+ run.define_singleton_method(:stage_detector) { detector }
27
+ run.define_singleton_method(:confirm_step) { flunk "confirm_step should not be called for a complete release" }
28
+
29
+ assert_equal :complete, run.call
30
+ assert_equal ["The current release is already complete. No action needed."], ui.infos
31
+ end
32
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "stringio"
4
+ require_relative "../test_helper"
5
+
6
+ class ShellTest < Minitest::Test
7
+ def test_stream_output_yields_chunks_without_waiting_for_newline
8
+ shell = PumaRelease::Shell.new(env: {}, cwd: Dir.pwd)
9
+ chunks = []
10
+ times = []
11
+ started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
12
+ original_stdout = $stdout
13
+ $stdout = StringIO.new
14
+
15
+ shell.stream_output(
16
+ Gem.ruby, "-e",
17
+ "STDOUT.write('a'); STDOUT.flush; sleep 0.2; STDOUT.write('b'); STDOUT.flush"
18
+ ) do |chunk|
19
+ chunks << chunk
20
+ times << Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at
21
+ end
22
+
23
+ assert_equal "ab", chunks.join
24
+ assert_operator times.first, :<, 0.15
25
+ assert_operator times.last, :>=, 0.15
26
+ ensure
27
+ $stdout = original_stdout
28
+ end
29
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../test_helper"
4
+
5
+ class StageDetectorTest < Minitest::Test
6
+ def test_returns_wait_for_merge_on_release_branch
7
+ detector = build_detector(current_branch: "release-v7.2.1")
8
+
9
+ assert_equal :wait_for_merge, detector.next_step
10
+ end
11
+
12
+ def test_returns_build_after_release_pr_is_merged_but_before_tag_exists
13
+ detector = build_detector(last_tag: "v7.2.0", current_version: "7.2.1")
14
+
15
+ assert_equal :build, detector.next_step
16
+ end
17
+
18
+ def test_returns_github_when_release_is_still_pending
19
+ detector = build_detector(release: {"isDraft" => true, "assets" => []})
20
+
21
+ assert_equal :github, detector.next_step
22
+ end
23
+
24
+ def test_returns_complete_when_release_is_published_and_there_are_no_new_commits
25
+ detector = build_detector(release: published_release, commits_since: 0)
26
+
27
+ assert_equal :complete, detector.next_step
28
+ end
29
+
30
+ def test_returns_github_when_release_is_missing_but_tag_is_current
31
+ detector = build_detector(release: nil, commits_since: 0)
32
+
33
+ assert_equal :github, detector.next_step
34
+ end
35
+
36
+ def test_returns_prepare_when_release_is_missing_and_new_commits_exist
37
+ detector = build_detector(release: nil, commits_since: 3)
38
+
39
+ assert_equal :prepare, detector.next_step
40
+ end
41
+
42
+ private
43
+
44
+ def build_detector(current_branch: "main", last_tag: "v7.2.1", current_version: "7.2.1", release: nil, open_release_pr: nil, commits_since: 0)
45
+ git_repo = Object.new
46
+ git_repo.define_singleton_method(:current_branch) { current_branch }
47
+ git_repo.define_singleton_method(:last_tag) { last_tag }
48
+ git_repo.define_singleton_method(:release_tag) { |version| "v#{version}" }
49
+ git_repo.define_singleton_method(:commits_since) { |_tag| commits_since }
50
+
51
+ github = Object.new
52
+ github.define_singleton_method(:open_release_pr) { open_release_pr }
53
+ github.define_singleton_method(:release) { |_tag| release }
54
+
55
+ PumaRelease::StageDetector.new(
56
+ OpenStruct.new(shell: FakeShell.new),
57
+ git_repo:,
58
+ repo_files: OpenStruct.new(current_version:),
59
+ github:
60
+ )
61
+ end
62
+
63
+ def published_release
64
+ {
65
+ "isDraft" => false,
66
+ "assets" => [
67
+ {"name" => "puma-7.2.1.gem"},
68
+ {"name" => "puma-7.2.1-java.gem"}
69
+ ]
70
+ }
71
+ end
72
+ end
metadata ADDED
@@ -0,0 +1,143 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: puma-release
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Nate Berkopec
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: minitest
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: ostruct
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rake
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: standard
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ description: Standalone CLI for running Puma's release process against a Puma checkout.
69
+ email:
70
+ - nate.berkopec@gmail.com
71
+ executables:
72
+ - puma-release
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - LICENSE
77
+ - README.md
78
+ - exe/puma-release
79
+ - lib/puma_release.rb
80
+ - lib/puma_release/agent_client.rb
81
+ - lib/puma_release/build_support.rb
82
+ - lib/puma_release/changelog_generator.rb
83
+ - lib/puma_release/changelog_validator.rb
84
+ - lib/puma_release/ci_checker.rb
85
+ - lib/puma_release/cli.rb
86
+ - lib/puma_release/commands/build.rb
87
+ - lib/puma_release/commands/github.rb
88
+ - lib/puma_release/commands/prepare.rb
89
+ - lib/puma_release/commands/run.rb
90
+ - lib/puma_release/context.rb
91
+ - lib/puma_release/contributor_resolver.rb
92
+ - lib/puma_release/error.rb
93
+ - lib/puma_release/events.rb
94
+ - lib/puma_release/git_repo.rb
95
+ - lib/puma_release/github_client.rb
96
+ - lib/puma_release/link_reference_builder.rb
97
+ - lib/puma_release/options.rb
98
+ - lib/puma_release/release_range.rb
99
+ - lib/puma_release/repo_files.rb
100
+ - lib/puma_release/shell.rb
101
+ - lib/puma_release/stage_detector.rb
102
+ - lib/puma_release/ui.rb
103
+ - lib/puma_release/upgrade_guide_writer.rb
104
+ - lib/puma_release/version.rb
105
+ - lib/puma_release/version_recommender.rb
106
+ - test/test_helper.rb
107
+ - test/unit/agent_client_test.rb
108
+ - test/unit/build_command_test.rb
109
+ - test/unit/build_support_test.rb
110
+ - test/unit/changelog_validator_test.rb
111
+ - test/unit/context_test.rb
112
+ - test/unit/contributor_resolver_test.rb
113
+ - test/unit/git_repo_test.rb
114
+ - test/unit/github_client_test.rb
115
+ - test/unit/github_command_test.rb
116
+ - test/unit/options_test.rb
117
+ - test/unit/prepare_test.rb
118
+ - test/unit/repo_files_test.rb
119
+ - test/unit/run_test.rb
120
+ - test/unit/shell_test.rb
121
+ - test/unit/stage_detector_test.rb
122
+ homepage: https://github.com/nateberkopec/puma-release
123
+ licenses:
124
+ - MIT
125
+ metadata: {}
126
+ rdoc_options: []
127
+ require_paths:
128
+ - lib
129
+ required_ruby_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '4.0'
134
+ required_rubygems_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ requirements: []
140
+ rubygems_version: 4.0.6
141
+ specification_version: 4
142
+ summary: Automate Puma releases
143
+ test_files: []