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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +114 -0
- data/exe/puma-release +7 -0
- data/lib/puma_release/agent_client.rb +163 -0
- data/lib/puma_release/build_support.rb +46 -0
- data/lib/puma_release/changelog_generator.rb +171 -0
- data/lib/puma_release/changelog_validator.rb +85 -0
- data/lib/puma_release/ci_checker.rb +108 -0
- data/lib/puma_release/cli.rb +52 -0
- data/lib/puma_release/commands/build.rb +68 -0
- data/lib/puma_release/commands/github.rb +76 -0
- data/lib/puma_release/commands/prepare.rb +178 -0
- data/lib/puma_release/commands/run.rb +51 -0
- data/lib/puma_release/context.rb +167 -0
- data/lib/puma_release/contributor_resolver.rb +52 -0
- data/lib/puma_release/error.rb +5 -0
- data/lib/puma_release/events.rb +18 -0
- data/lib/puma_release/git_repo.rb +169 -0
- data/lib/puma_release/github_client.rb +163 -0
- data/lib/puma_release/link_reference_builder.rb +49 -0
- data/lib/puma_release/options.rb +47 -0
- data/lib/puma_release/release_range.rb +69 -0
- data/lib/puma_release/repo_files.rb +85 -0
- data/lib/puma_release/shell.rb +107 -0
- data/lib/puma_release/stage_detector.rb +66 -0
- data/lib/puma_release/ui.rb +36 -0
- data/lib/puma_release/upgrade_guide_writer.rb +106 -0
- data/lib/puma_release/version.rb +5 -0
- data/lib/puma_release/version_recommender.rb +151 -0
- data/lib/puma_release.rb +28 -0
- data/test/test_helper.rb +72 -0
- data/test/unit/agent_client_test.rb +116 -0
- data/test/unit/build_command_test.rb +23 -0
- data/test/unit/build_support_test.rb +6 -0
- data/test/unit/changelog_validator_test.rb +42 -0
- data/test/unit/context_test.rb +209 -0
- data/test/unit/contributor_resolver_test.rb +47 -0
- data/test/unit/git_repo_test.rb +169 -0
- data/test/unit/github_client_test.rb +90 -0
- data/test/unit/github_command_test.rb +153 -0
- data/test/unit/options_test.rb +17 -0
- data/test/unit/prepare_test.rb +136 -0
- data/test/unit/repo_files_test.rb +119 -0
- data/test/unit/run_test.rb +32 -0
- data/test/unit/shell_test.rb +29 -0
- data/test/unit/stage_detector_test.rb +72 -0
- metadata +143 -0
data/test/test_helper.rb
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "minitest/autorun"
|
|
4
|
+
require "tmpdir"
|
|
5
|
+
require "ostruct"
|
|
6
|
+
require_relative "../lib/puma_release"
|
|
7
|
+
|
|
8
|
+
module TestSupport
|
|
9
|
+
class FakeShell
|
|
10
|
+
Result = Data.define(:stdout, :stderr, :success?, :exitstatus)
|
|
11
|
+
|
|
12
|
+
attr_reader :commands
|
|
13
|
+
|
|
14
|
+
def initialize(outputs = {})
|
|
15
|
+
@outputs = outputs
|
|
16
|
+
@commands = []
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def output(*command, **_options)
|
|
20
|
+
commands << command
|
|
21
|
+
value = @outputs.fetch(command, "")
|
|
22
|
+
value.respond_to?(:call) ? value.call : value
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def run(*command, allow_failure: false, **_options)
|
|
26
|
+
commands << command
|
|
27
|
+
value = @outputs.fetch(command, Result.new(stdout: "", stderr: "", success?: true, exitstatus: 0))
|
|
28
|
+
value = value.call if value.respond_to?(:call)
|
|
29
|
+
return value if value.success? || allow_failure
|
|
30
|
+
|
|
31
|
+
raise PumaRelease::Error, command.join(" ")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def stream_output(*command, **options)
|
|
35
|
+
output(*command, **options)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def stream_json_events(*command, **options)
|
|
39
|
+
output(*command, **options).each_line do |line|
|
|
40
|
+
next if line.strip.empty?
|
|
41
|
+
begin
|
|
42
|
+
yield JSON.parse(line)
|
|
43
|
+
rescue JSON::ParserError
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def optional_output(*command)
|
|
49
|
+
output(*command).strip
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def available?(_command)
|
|
53
|
+
true
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def split(command)
|
|
57
|
+
command.split
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def temp_repo
|
|
62
|
+
Dir.mktmpdir do |dir|
|
|
63
|
+
repo = Pathname(dir)
|
|
64
|
+
repo.join("lib/puma").mkpath
|
|
65
|
+
yield repo
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
class Minitest::Test
|
|
71
|
+
include TestSupport
|
|
72
|
+
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "stringio"
|
|
4
|
+
require_relative "../test_helper"
|
|
5
|
+
|
|
6
|
+
class AgentClientTest < Minitest::Test
|
|
7
|
+
def test_pi_mode_parses_json_from_event_stream_without_printing_json_payload
|
|
8
|
+
shell = Class.new do
|
|
9
|
+
attr_reader :command
|
|
10
|
+
|
|
11
|
+
def stream_json_events(*command, **)
|
|
12
|
+
@command = command
|
|
13
|
+
yield({"type" => "message_update", "assistantMessageEvent" => {"type" => "text_delta", "delta" => "{"}})
|
|
14
|
+
yield({
|
|
15
|
+
"type" => "message_end",
|
|
16
|
+
"message" => {
|
|
17
|
+
"role" => "assistant",
|
|
18
|
+
"provider" => "openai-codex",
|
|
19
|
+
"model" => "gpt-5.4",
|
|
20
|
+
"content" => [
|
|
21
|
+
{"type" => "text", "text" => '{"bump_type":"patch","reasoning_markdown":"Because of [this commit](https://github.com/puma/puma/commit/abc)."}'}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def split(command)
|
|
28
|
+
[command]
|
|
29
|
+
end
|
|
30
|
+
end.new
|
|
31
|
+
|
|
32
|
+
context = OpenStruct.new(agent_cmd: "/tmp/pi", shell:)
|
|
33
|
+
original_stdout = $stdout
|
|
34
|
+
$stdout = StringIO.new
|
|
35
|
+
|
|
36
|
+
client = PumaRelease::AgentClient.new(context)
|
|
37
|
+
result = client.ask_for_json(
|
|
38
|
+
"Choose a version bump",
|
|
39
|
+
system_prompt: "Return JSON",
|
|
40
|
+
schema: {
|
|
41
|
+
type: "object",
|
|
42
|
+
required: %w[bump_type reasoning_markdown],
|
|
43
|
+
properties: {
|
|
44
|
+
bump_type: {type: "string"},
|
|
45
|
+
reasoning_markdown: {type: "string"}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
assert_equal "patch", result.fetch("bump_type")
|
|
51
|
+
assert_includes shell.command, "-p"
|
|
52
|
+
assert_includes shell.command, "--thinking"
|
|
53
|
+
assert_includes shell.command, "xhigh"
|
|
54
|
+
assert_includes shell.command, "--tools"
|
|
55
|
+
assert_includes shell.command, "read,bash"
|
|
56
|
+
assert_includes shell.command, "--extension"
|
|
57
|
+
assert_includes shell.command, File.expand_path("../../config/pi-agent-guard.ts", __dir__)
|
|
58
|
+
assert_includes shell.command, "--mode"
|
|
59
|
+
refute_includes shell.command, "--no-tools"
|
|
60
|
+
refute_includes shell.command, "--no-session"
|
|
61
|
+
assert_equal "openai-codex/gpt-5.4", client.last_model_name
|
|
62
|
+
assert_equal ".\n", $stdout.string
|
|
63
|
+
ensure
|
|
64
|
+
$stdout = original_stdout
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def test_pi_mode_uses_final_answer_text_when_commentary_is_present
|
|
68
|
+
shell = Class.new do
|
|
69
|
+
def stream_json_events(*, **)
|
|
70
|
+
yield({"type" => "message_update", "assistantMessageEvent" => {"type" => "text_delta", "delta" => "I"}})
|
|
71
|
+
yield({
|
|
72
|
+
"type" => "message_end",
|
|
73
|
+
"message" => {
|
|
74
|
+
"role" => "assistant",
|
|
75
|
+
"provider" => "openai-codex",
|
|
76
|
+
"model" => "gpt-5.4",
|
|
77
|
+
"content" => [
|
|
78
|
+
{
|
|
79
|
+
"type" => "text",
|
|
80
|
+
"text" => "I'm thinking through the release range.",
|
|
81
|
+
"textSignature" => '{"v":1,"phase":"commentary"}'
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"type" => "text",
|
|
85
|
+
"text" => '{"bump_type":"minor","reasoning_markdown":"Because of [this commit](https://github.com/puma/puma/commit/abc)."}',
|
|
86
|
+
"textSignature" => '{"v":1,"phase":"final_answer"}'
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def split(command)
|
|
94
|
+
[command]
|
|
95
|
+
end
|
|
96
|
+
end.new
|
|
97
|
+
|
|
98
|
+
context = OpenStruct.new(agent_cmd: "pi", shell:)
|
|
99
|
+
|
|
100
|
+
client = PumaRelease::AgentClient.new(context)
|
|
101
|
+
result = client.ask_for_json(
|
|
102
|
+
"Choose a version bump",
|
|
103
|
+
system_prompt: "Return JSON",
|
|
104
|
+
schema: {
|
|
105
|
+
type: "object",
|
|
106
|
+
required: %w[bump_type reasoning_markdown],
|
|
107
|
+
properties: {
|
|
108
|
+
bump_type: {type: "string"},
|
|
109
|
+
reasoning_markdown: {type: "string"}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
assert_equal "minor", result.fetch("bump_type")
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../test_helper"
|
|
4
|
+
|
|
5
|
+
class BuildCommandTest < Minitest::Test
|
|
6
|
+
def test_sync_release_target_to_tag_is_a_no_op_when_release_is_missing
|
|
7
|
+
git_repo = Object.new
|
|
8
|
+
git_repo.define_singleton_method(:local_tag_sha) { |_tag| "abc123" }
|
|
9
|
+
|
|
10
|
+
github = Object.new
|
|
11
|
+
calls = []
|
|
12
|
+
github.define_singleton_method(:release) { |_tag| nil }
|
|
13
|
+
github.define_singleton_method(:edit_release_target) { |_tag, _sha| calls << :edit_release_target }
|
|
14
|
+
|
|
15
|
+
command = PumaRelease::Commands::Build.allocate
|
|
16
|
+
command.instance_variable_set(:@git_repo, git_repo)
|
|
17
|
+
command.instance_variable_set(:@github, github)
|
|
18
|
+
|
|
19
|
+
command.send(:sync_release_target_to_tag, "v7.2.0")
|
|
20
|
+
|
|
21
|
+
assert_empty calls
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../test_helper"
|
|
4
|
+
|
|
5
|
+
class ChangelogValidatorTest < Minitest::Test
|
|
6
|
+
def test_valid_changelog_has_no_errors
|
|
7
|
+
changelog = <<~CHANGELOG
|
|
8
|
+
* Features
|
|
9
|
+
* Add a nice thing ([#10])
|
|
10
|
+
|
|
11
|
+
* Bugfixes
|
|
12
|
+
* Fix a rough edge ([#11], [#12])
|
|
13
|
+
CHANGELOG
|
|
14
|
+
|
|
15
|
+
assert_empty PumaRelease::ChangelogValidator.new.validate(changelog)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def test_rejects_inline_links
|
|
19
|
+
changelog = <<~CHANGELOG
|
|
20
|
+
* Features
|
|
21
|
+
* Add a nice thing [#10](https://example.test)
|
|
22
|
+
CHANGELOG
|
|
23
|
+
|
|
24
|
+
errors = PumaRelease::ChangelogValidator.new.validate(changelog)
|
|
25
|
+
|
|
26
|
+
assert_includes errors.join("\n"), "inline markdown links are not allowed"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def test_rejects_out_of_order_categories
|
|
30
|
+
changelog = <<~CHANGELOG
|
|
31
|
+
* Docs
|
|
32
|
+
* Update docs ([#10])
|
|
33
|
+
|
|
34
|
+
* Features
|
|
35
|
+
* Add a nice thing ([#11])
|
|
36
|
+
CHANGELOG
|
|
37
|
+
|
|
38
|
+
errors = PumaRelease::ChangelogValidator.new.validate(changelog)
|
|
39
|
+
|
|
40
|
+
assert_includes errors.join("\n"), "categories must appear in this order"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../test_helper"
|
|
4
|
+
|
|
5
|
+
class ContextTest < Minitest::Test
|
|
6
|
+
class FakeUI
|
|
7
|
+
attr_reader :warnings, :confirmations
|
|
8
|
+
attr_accessor :confirm_result
|
|
9
|
+
|
|
10
|
+
def initialize(confirm_result: true)
|
|
11
|
+
@warnings = []
|
|
12
|
+
@confirmations = []
|
|
13
|
+
@confirm_result = confirm_result
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def warn(message)
|
|
17
|
+
warnings << message
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def confirm(message, default: true)
|
|
21
|
+
confirmations << [message, default]
|
|
22
|
+
confirm_result
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def test_release_repo_prefers_a_fork_remote_when_not_live
|
|
27
|
+
shell = FakeShell.new(
|
|
28
|
+
{
|
|
29
|
+
["git", "remote"] => "origin\nmine\n",
|
|
30
|
+
["git", "remote", "get-url", "origin"] => "https://github.com/puma/puma.git\n",
|
|
31
|
+
["git", "remote", "get-url", "mine"] => "git@github.com:nateberkopec/puma.git\n"
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
context = build_context(shell:, live: false)
|
|
36
|
+
|
|
37
|
+
assert_equal "nateberkopec/puma", context.release_repo
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_release_repo_defaults_to_metadata_repo_in_live_mode
|
|
41
|
+
shell = FakeShell.new(
|
|
42
|
+
{
|
|
43
|
+
["git", "remote"] => "origin\nmine\n",
|
|
44
|
+
["git", "remote", "get-url", "origin"] => "https://github.com/puma/puma.git\n",
|
|
45
|
+
["git", "remote", "get-url", "mine"] => "git@github.com:nateberkopec/puma.git\n"
|
|
46
|
+
}
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
context = build_context(shell:, live: true)
|
|
50
|
+
|
|
51
|
+
assert_equal "puma/puma", context.release_repo
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_refuses_writes_to_metadata_repo_without_live
|
|
55
|
+
shell = FakeShell.new
|
|
56
|
+
context = build_context(shell:, live: false, release_repo: "puma/puma")
|
|
57
|
+
|
|
58
|
+
error = assert_raises(PumaRelease::Error) { context.ensure_release_writes_allowed! }
|
|
59
|
+
|
|
60
|
+
assert_includes error.message, "without --live"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def test_release_repo_prefers_the_authenticated_users_fork_when_multiple_candidates_exist
|
|
64
|
+
shell = FakeShell.new(
|
|
65
|
+
{
|
|
66
|
+
["git", "remote"] => "backup\norigin\nupstream\n",
|
|
67
|
+
["git", "remote", "get-url", "backup"] => "git@github.com:someoneelse/puma.git\n",
|
|
68
|
+
["git", "remote", "get-url", "origin"] => "git@github.com:nateberkopec/puma.git\n",
|
|
69
|
+
["git", "remote", "get-url", "upstream"] => "https://github.com/puma/puma.git\n",
|
|
70
|
+
["gh", "api", "user"] => FakeShell::Result.new(stdout: '{"login":"nateberkopec"}', stderr: "", success?: true, exitstatus: 0)
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
context = build_context(shell:, live: false)
|
|
75
|
+
|
|
76
|
+
assert_equal "nateberkopec/puma", context.release_repo
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def test_release_repo_falls_back_to_metadata_repo_when_fork_is_ambiguous
|
|
80
|
+
shell = FakeShell.new(
|
|
81
|
+
{
|
|
82
|
+
["git", "remote"] => "backup\nmirror\norigin\n",
|
|
83
|
+
["git", "remote", "get-url", "backup"] => "git@github.com:someoneelse/puma.git\n",
|
|
84
|
+
["git", "remote", "get-url", "mirror"] => "git@github.com:anotheruser/puma.git\n",
|
|
85
|
+
["git", "remote", "get-url", "origin"] => "https://github.com/puma/puma.git\n",
|
|
86
|
+
["gh", "api", "user"] => FakeShell::Result.new(stdout: '{"login":"nateberkopec"}', stderr: "", success?: true, exitstatus: 0)
|
|
87
|
+
}
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
context = build_context(shell:, live: false)
|
|
91
|
+
|
|
92
|
+
assert_equal "puma/puma", context.release_repo
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def test_announce_live_mode_warns_once
|
|
96
|
+
shell = FakeShell.new
|
|
97
|
+
ui = FakeUI.new
|
|
98
|
+
context = build_context(shell:, live: true, release_repo: "puma/puma", ui:)
|
|
99
|
+
|
|
100
|
+
context.announce_live_mode!
|
|
101
|
+
context.announce_live_mode!
|
|
102
|
+
|
|
103
|
+
assert_equal ["LIVE MODE: writes will go to puma/puma"], ui.warnings
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def test_confirm_live_github_write_prompts_in_live_mode
|
|
107
|
+
shell = FakeShell.new
|
|
108
|
+
ui = FakeUI.new(confirm_result: true)
|
|
109
|
+
context = build_context(shell:, live: true, release_repo: "puma/puma", ui:)
|
|
110
|
+
|
|
111
|
+
assert context.confirm_live_github_write!("publish release v7.3.0")
|
|
112
|
+
assert_equal [["LIVE MODE: publish release v7.3.0 on GitHub for puma/puma. Continue?", true]], ui.confirmations
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def test_confirm_live_github_write_raises_when_declined
|
|
116
|
+
shell = FakeShell.new
|
|
117
|
+
ui = FakeUI.new(confirm_result: false)
|
|
118
|
+
context = build_context(shell:, live: true, release_repo: "puma/puma", ui:)
|
|
119
|
+
|
|
120
|
+
error = assert_raises(PumaRelease::Error) { context.confirm_live_github_write!("publish release v7.3.0") }
|
|
121
|
+
|
|
122
|
+
assert_includes error.message, "Aborted live GitHub action"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def test_confirm_live_github_write_skips_prompt_when_not_live
|
|
126
|
+
shell = FakeShell.new
|
|
127
|
+
ui = FakeUI.new(confirm_result: false)
|
|
128
|
+
context = build_context(shell:, live: false, release_repo: "nateberkopec/puma", ui:)
|
|
129
|
+
|
|
130
|
+
assert context.confirm_live_github_write!("publish release v7.3.0")
|
|
131
|
+
assert_empty ui.confirmations
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def test_confirm_live_github_write_skips_prompt_when_yes_is_set
|
|
135
|
+
shell = FakeShell.new
|
|
136
|
+
ui = FakeUI.new(confirm_result: false)
|
|
137
|
+
context = build_context(shell:, live: true, release_repo: "puma/puma", yes: true, ui:)
|
|
138
|
+
|
|
139
|
+
assert context.confirm_live_github_write!("publish release v7.3.0")
|
|
140
|
+
assert_empty ui.confirmations
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def test_confirm_live_git_command_prompts_in_live_mode_with_the_full_command
|
|
144
|
+
shell = FakeShell.new
|
|
145
|
+
ui = FakeUI.new(confirm_result: true)
|
|
146
|
+
context = build_context(shell:, live: true, release_repo: "puma/puma", ui:)
|
|
147
|
+
|
|
148
|
+
assert context.confirm_live_git_command!("git", "push", "origin", "main")
|
|
149
|
+
assert_equal [["LIVE MODE: about to run git command: git push origin main. Continue?", true]], ui.confirmations
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def test_confirm_live_git_command_raises_when_declined
|
|
153
|
+
shell = FakeShell.new
|
|
154
|
+
ui = FakeUI.new(confirm_result: false)
|
|
155
|
+
context = build_context(shell:, live: true, release_repo: "puma/puma", ui:)
|
|
156
|
+
|
|
157
|
+
error = assert_raises(PumaRelease::Error) { context.confirm_live_git_command!("git", "push", "origin", "main") }
|
|
158
|
+
|
|
159
|
+
assert_includes error.message, "Aborted live git action"
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def test_confirm_live_git_command_skips_prompt_when_yes_is_set
|
|
163
|
+
shell = FakeShell.new
|
|
164
|
+
ui = FakeUI.new(confirm_result: false)
|
|
165
|
+
context = build_context(shell:, live: true, release_repo: "puma/puma", yes: true, ui:)
|
|
166
|
+
|
|
167
|
+
assert context.confirm_live_git_command!("git", "push", "origin", "main")
|
|
168
|
+
assert_empty ui.confirmations
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def test_confirm_live_gh_command_prompts_in_live_mode_with_the_full_command
|
|
172
|
+
shell = FakeShell.new
|
|
173
|
+
ui = FakeUI.new(confirm_result: true)
|
|
174
|
+
context = build_context(shell:, live: true, release_repo: "puma/puma", ui:)
|
|
175
|
+
|
|
176
|
+
assert context.confirm_live_gh_command!("gh", "release", "edit", "v7.3.0", "--repo", "puma/puma", "--draft=false")
|
|
177
|
+
assert_equal [["LIVE MODE: about to run gh command: gh release edit v7.3.0 --repo puma/puma --draft\\=false. Continue?", true]], ui.confirmations
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def test_confirm_live_gh_command_raises_when_declined
|
|
181
|
+
shell = FakeShell.new
|
|
182
|
+
ui = FakeUI.new(confirm_result: false)
|
|
183
|
+
context = build_context(shell:, live: true, release_repo: "puma/puma", ui:)
|
|
184
|
+
|
|
185
|
+
error = assert_raises(PumaRelease::Error) { context.confirm_live_gh_command!("gh", "release", "edit", "v7.3.0", "--repo", "puma/puma", "--draft=false") }
|
|
186
|
+
|
|
187
|
+
assert_includes error.message, "Aborted live gh action"
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
private
|
|
191
|
+
|
|
192
|
+
def build_context(shell:, live:, release_repo: nil, yes: false, ui: PumaRelease::UI.new)
|
|
193
|
+
options = {
|
|
194
|
+
command: "run",
|
|
195
|
+
repo_dir: Pathname(Dir.pwd),
|
|
196
|
+
metadata_repo: "puma/puma",
|
|
197
|
+
release_repo:,
|
|
198
|
+
changelog_backend: "auto",
|
|
199
|
+
allow_unknown_ci: false,
|
|
200
|
+
yes:,
|
|
201
|
+
live:,
|
|
202
|
+
debug: false
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
context = PumaRelease::Context.new(options, env: {}, ui:)
|
|
206
|
+
context.instance_variable_set(:@shell, shell)
|
|
207
|
+
context
|
|
208
|
+
end
|
|
209
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../test_helper"
|
|
4
|
+
|
|
5
|
+
class ContributorResolverTest < Minitest::Test
|
|
6
|
+
def test_resolves_github_login_from_commit_metadata
|
|
7
|
+
shell = FakeShell.new(
|
|
8
|
+
{
|
|
9
|
+
["git", "shortlog", "-s", "-n", "-e", "--no-merges", "v7.2.0..HEAD"] => " 10\tNate Berkopec <nate.berkopec@gmail.com>\n",
|
|
10
|
+
["git", "log", "--format=%H%x09%aN%x09%aE", "v7.2.0..HEAD"] => [
|
|
11
|
+
"abc123\tNate Berkopec\tnate.berkopec@gmail.com",
|
|
12
|
+
"def456\tNate Berkopec\tnate.berkopec@gmail.com"
|
|
13
|
+
].join("\n")
|
|
14
|
+
}
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
context = OpenStruct.new(metadata_repo: "puma/puma")
|
|
18
|
+
git_repo = PumaRelease::GitRepo.new(OpenStruct.new(shell:))
|
|
19
|
+
github = Object.new
|
|
20
|
+
def github.commit_author_login(_repo, sha)
|
|
21
|
+
{"abc123" => "nateberkopec", "def456" => "nateberkopec"}.fetch(sha)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
earner = PumaRelease::ContributorResolver.new(context, git_repo:, github:).codename_earner("v7.2.0")
|
|
25
|
+
|
|
26
|
+
assert_equal "Nate Berkopec", earner.fetch(:name)
|
|
27
|
+
assert_equal "nateberkopec", earner.fetch(:login)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def test_falls_back_to_github_noreply_email_login
|
|
31
|
+
shell = FakeShell.new(
|
|
32
|
+
{
|
|
33
|
+
["git", "shortlog", "-s", "-n", "-e", "--no-merges", "v7.2.0..HEAD"] => " 1\tYuki Nishijima <386234+yuki24@users.noreply.github.com>\n",
|
|
34
|
+
["git", "log", "--format=%H%x09%aN%x09%aE", "v7.2.0..HEAD"] => "abc123\tYuki Nishijima\t386234+yuki24@users.noreply.github.com"
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
context = OpenStruct.new(metadata_repo: "puma/puma")
|
|
39
|
+
git_repo = PumaRelease::GitRepo.new(OpenStruct.new(shell:))
|
|
40
|
+
github = Object.new
|
|
41
|
+
def github.commit_author_login(_repo, _sha) = nil
|
|
42
|
+
|
|
43
|
+
earner = PumaRelease::ContributorResolver.new(context, git_repo:, github:).codename_earner("v7.2.0")
|
|
44
|
+
|
|
45
|
+
assert_equal "yuki24", earner.fetch(:login)
|
|
46
|
+
end
|
|
47
|
+
end
|