豆豆友情提示:这是一个非官方 GitHub 代理镜像,主要用于网络测试或访问加速。请勿在此进行登录、注册或处理任何敏感信息。进行这些操作请务必访问官方网站 github.com。 Raw 内容也通过此代理提供。
Skip to content

Commit c44f7c3

Browse files
committed
Add TitleBuilder class for composing PR titles with prefixes
1 parent c1516a5 commit c44f7c3

File tree

3 files changed

+360
-2
lines changed

3 files changed

+360
-2
lines changed

common/lib/dependabot/pull_request_creator/message_builder.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class MessageBuilder
2424
require_relative "message_builder/metadata_presenter"
2525
require_relative "message_builder/issue_linker"
2626
require_relative "message_builder/link_and_mention_sanitizer"
27+
require_relative "message_builder/title_builder"
2728
require_relative "pr_name_prefixer"
2829

2930
sig { returns(Dependabot::Source) }
@@ -130,8 +131,10 @@ def initialize(
130131
sig { returns(String) }
131132
def pr_name
132133
name = dependency_group ? group_pr_name : solo_pr_name
133-
name[0] = T.must(name[0]).capitalize if pr_name_prefixer.capitalize_first_word?
134-
"#{pr_name_prefix}#{name}"
134+
MessageBuilder::TitleBuilder.new(
135+
base_title: name,
136+
prefixer: pr_name_prefixer
137+
).build
135138
end
136139

137140
sig { returns(String) }
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# typed: strict
2+
# frozen_string_literal: true
3+
4+
require "sorbet-runtime"
5+
require "dependabot/dependency"
6+
require "dependabot/logger"
7+
8+
module Dependabot
9+
class PullRequestCreator
10+
class MessageBuilder
11+
# Composes a final PR title from a base title + prefix.
12+
#
13+
# Works in two modes:
14+
# 1. With a full PrNamePrefixer (updater path — has source/credentials for
15+
# commit style auto-detection)
16+
# 2. With just commit_message_options (API path — explicit prefix only,
17+
# no network calls needed)
18+
class TitleBuilder
19+
extend T::Sig
20+
21+
sig { returns(String) }
22+
attr_reader :base_title
23+
24+
sig { returns(T.nilable(Dependabot::PullRequestCreator::PrNamePrefixer)) }
25+
attr_reader :prefixer
26+
27+
sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
28+
attr_reader :commit_message_options
29+
30+
sig { returns(T.nilable(T::Array[Dependabot::Dependency])) }
31+
attr_reader :dependencies
32+
33+
sig do
34+
params(
35+
base_title: String,
36+
prefixer: T.nilable(Dependabot::PullRequestCreator::PrNamePrefixer),
37+
commit_message_options: T.nilable(T::Hash[Symbol, T.untyped]),
38+
dependencies: T.nilable(T::Array[Dependabot::Dependency])
39+
).void
40+
end
41+
def initialize(base_title:, prefixer: nil, commit_message_options: nil, dependencies: nil)
42+
@base_title = base_title
43+
@prefixer = prefixer
44+
@commit_message_options = commit_message_options
45+
@dependencies = dependencies
46+
end
47+
48+
# Generates a base title for multi-ecosystem combined PR updates.
49+
sig { params(group_name: String, update_count: Integer).returns(String) }
50+
def self.multi_ecosystem_base_title(group_name:, update_count:)
51+
"bump the \"#{group_name}\" group with " \
52+
"#{update_count} update#{'s' if update_count > 1} across multiple ecosystems"
53+
end
54+
55+
sig { returns(String) }
56+
def build
57+
name = base_title.dup
58+
name[0] = T.must(name[0]).capitalize if capitalize?
59+
"#{prefix}#{name}"
60+
end
61+
62+
private
63+
64+
sig { returns(String) }
65+
def prefix
66+
return T.must(prefixer).pr_name_prefix if prefixer
67+
68+
build_explicit_prefix
69+
rescue StandardError => e
70+
Dependabot.logger.error("Error while generating PR name prefix: #{e.message}")
71+
Dependabot.logger.error(e.backtrace&.join("\n"))
72+
""
73+
end
74+
75+
sig { returns(T::Boolean) }
76+
def capitalize?
77+
return T.must(prefixer).capitalize_first_word? if prefixer
78+
79+
false
80+
end
81+
82+
# Builds prefix from explicit commit_message_options only.
83+
# Same logic as PrNamePrefixer#prefix_from_explicitly_provided_details
84+
# but without requiring source/credentials.
85+
sig { returns(String) }
86+
def build_explicit_prefix
87+
return "" unless commit_message_options&.key?(:prefix)
88+
89+
prefix = explicit_prefix_string
90+
return "" if prefix.empty?
91+
92+
prefix += "(#{scope})" if commit_message_options&.dig(:include_scope)
93+
# Append colon after alphanumeric or closing bracket to follow
94+
# conventional commit format (e.g., "chore: ..." or "fix(deps): ...")
95+
prefix += ":" if prefix.match?(/[A-Za-z0-9\)\]]\Z/)
96+
prefix += " " unless prefix.end_with?(" ")
97+
prefix
98+
end
99+
100+
sig { returns(String) }
101+
def explicit_prefix_string
102+
if production_dependencies?
103+
commit_message_options&.dig(:prefix).to_s
104+
elsif commit_message_options&.key?(:prefix_development)
105+
commit_message_options&.dig(:prefix_development).to_s
106+
else
107+
commit_message_options&.dig(:prefix).to_s
108+
end
109+
end
110+
111+
sig { returns(T::Boolean) }
112+
def production_dependencies?
113+
dependencies&.any?(&:production?) != false
114+
rescue StandardError
115+
true
116+
end
117+
118+
sig { returns(String) }
119+
def scope
120+
production_dependencies? ? "deps" : "deps-dev"
121+
end
122+
end
123+
end
124+
end
125+
end
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
# typed: false
2+
# frozen_string_literal: true
3+
4+
require "spec_helper"
5+
require "dependabot/pull_request_creator/message_builder/title_builder"
6+
require "dependabot/pull_request_creator/pr_name_prefixer"
7+
8+
RSpec.describe Dependabot::PullRequestCreator::MessageBuilder::TitleBuilder do
9+
before do
10+
Dependabot::Dependency.register_production_check(
11+
"npm_and_yarn",
12+
lambda do |groups|
13+
return true if groups.empty?
14+
return true if groups.include?("optionalDependencies")
15+
16+
groups.include?("dependencies")
17+
end
18+
)
19+
end
20+
21+
describe "#build" do
22+
context "with no prefix" do
23+
subject(:builder) do
24+
described_class.new(base_title: "bump lodash from 4.0.0 to 5.0.0")
25+
end
26+
27+
it "returns the base title unchanged" do
28+
expect(builder.build).to eq("bump lodash from 4.0.0 to 5.0.0")
29+
end
30+
end
31+
32+
context "with explicit commit_message_options prefix" do
33+
subject(:builder) do
34+
described_class.new(
35+
base_title: "bump lodash from 4.0.0 to 5.0.0",
36+
commit_message_options: { prefix: "[ci]" },
37+
dependencies: dependencies
38+
)
39+
end
40+
41+
let(:dependencies) do
42+
[
43+
Dependabot::Dependency.new(
44+
name: "lodash",
45+
version: "5.0.0",
46+
previous_version: "4.0.0",
47+
package_manager: "npm_and_yarn",
48+
requirements: [{ file: "package.json", requirement: "^5.0.0", groups: [], source: nil }],
49+
previous_requirements: [{ file: "package.json", requirement: "^4.0.0", groups: [], source: nil }]
50+
)
51+
]
52+
end
53+
54+
it "applies the prefix" do
55+
expect(builder.build).to eq("[ci]: bump lodash from 4.0.0 to 5.0.0")
56+
end
57+
end
58+
59+
context "with prefix ending in space" do
60+
subject(:builder) do
61+
described_class.new(
62+
base_title: "bump lodash from 4.0.0 to 5.0.0",
63+
commit_message_options: { prefix: "[ci] " },
64+
dependencies: dependencies
65+
)
66+
end
67+
68+
let(:dependencies) do
69+
[
70+
Dependabot::Dependency.new(
71+
name: "lodash",
72+
version: "5.0.0",
73+
previous_version: "4.0.0",
74+
package_manager: "npm_and_yarn",
75+
requirements: [{ file: "package.json", requirement: "^5.0.0", groups: [], source: nil }],
76+
previous_requirements: [{ file: "package.json", requirement: "^4.0.0", groups: [], source: nil }]
77+
)
78+
]
79+
end
80+
81+
it "does not double-space" do
82+
expect(builder.build).to eq("[ci] bump lodash from 4.0.0 to 5.0.0")
83+
end
84+
end
85+
86+
context "with include_scope option" do
87+
subject(:builder) do
88+
described_class.new(
89+
base_title: "bump lodash from 4.0.0 to 5.0.0",
90+
commit_message_options: { prefix: "chore", include_scope: true },
91+
dependencies: dependencies
92+
)
93+
end
94+
95+
let(:dependencies) do
96+
[
97+
Dependabot::Dependency.new(
98+
name: "lodash",
99+
version: "5.0.0",
100+
previous_version: "4.0.0",
101+
package_manager: "npm_and_yarn",
102+
requirements: [{
103+
file: "package.json",
104+
requirement: "^5.0.0",
105+
groups: ["dependencies"],
106+
source: nil
107+
}],
108+
previous_requirements: [{
109+
file: "package.json",
110+
requirement: "^4.0.0",
111+
groups: ["dependencies"],
112+
source: nil
113+
}]
114+
)
115+
]
116+
end
117+
118+
it "includes scope in the prefix" do
119+
expect(builder.build).to eq("chore(deps): bump lodash from 4.0.0 to 5.0.0")
120+
end
121+
end
122+
123+
context "with prefix_development for dev dependency" do
124+
subject(:builder) do
125+
described_class.new(
126+
base_title: "bump eslint from 7.0.0 to 8.0.0",
127+
commit_message_options: { prefix: "fix", prefix_development: "chore" },
128+
dependencies: dependencies
129+
)
130+
end
131+
132+
let(:dependencies) do
133+
[
134+
Dependabot::Dependency.new(
135+
name: "eslint",
136+
version: "8.0.0",
137+
previous_version: "7.0.0",
138+
package_manager: "npm_and_yarn",
139+
requirements: [{
140+
file: "package.json",
141+
requirement: "^8.0.0",
142+
groups: ["devDependencies"],
143+
source: nil
144+
}],
145+
previous_requirements: [{
146+
file: "package.json",
147+
requirement: "^7.0.0",
148+
groups: ["devDependencies"],
149+
source: nil
150+
}]
151+
)
152+
]
153+
end
154+
155+
it "uses the development prefix" do
156+
expect(builder.build).to eq("chore: bump eslint from 7.0.0 to 8.0.0")
157+
end
158+
end
159+
160+
context "with a PrNamePrefixer" do
161+
subject(:builder) do
162+
described_class.new(
163+
base_title: "bump lodash from 4.0.0 to 5.0.0",
164+
prefixer: prefixer
165+
)
166+
end
167+
168+
let(:prefixer) { instance_double(Dependabot::PullRequestCreator::PrNamePrefixer) }
169+
170+
before do
171+
allow(prefixer).to receive_messages(pr_name_prefix: "⬆️ ", capitalize_first_word?: true)
172+
end
173+
174+
it "uses prefixer for prefix and capitalization" do
175+
expect(builder.build).to eq("⬆️ Bump lodash from 4.0.0 to 5.0.0")
176+
end
177+
end
178+
179+
context "with empty prefix string" do
180+
subject(:builder) do
181+
described_class.new(
182+
base_title: "bump lodash from 4.0.0 to 5.0.0",
183+
commit_message_options: { prefix: "" },
184+
dependencies: dependencies
185+
)
186+
end
187+
188+
let(:dependencies) do
189+
[
190+
Dependabot::Dependency.new(
191+
name: "lodash",
192+
version: "5.0.0",
193+
previous_version: "4.0.0",
194+
package_manager: "npm_and_yarn",
195+
requirements: [{ file: "package.json", requirement: "^5.0.0", groups: [], source: nil }],
196+
previous_requirements: [{ file: "package.json", requirement: "^4.0.0", groups: [], source: nil }]
197+
)
198+
]
199+
end
200+
201+
it "returns the base title without prefix" do
202+
expect(builder.build).to eq("bump lodash from 4.0.0 to 5.0.0")
203+
end
204+
end
205+
end
206+
207+
describe ".multi_ecosystem_base_title" do
208+
it "returns the multi-ecosystem title with plural updates" do
209+
expect(described_class.multi_ecosystem_base_title(group_name: "my-dependencies", update_count: 3)).to eq(
210+
"bump the \"my-dependencies\" group with 3 updates across multiple ecosystems"
211+
)
212+
end
213+
214+
context "with a single update" do
215+
it "returns singular update" do
216+
expect(described_class.multi_ecosystem_base_title(group_name: "my-dependencies", update_count: 1)).to eq(
217+
"bump the \"my-dependencies\" group with 1 update across multiple ecosystems"
218+
)
219+
end
220+
end
221+
222+
context "with a different group name" do
223+
it "uses the group name" do
224+
expect(described_class.multi_ecosystem_base_title(group_name: "security-patches", update_count: 3)).to eq(
225+
"bump the \"security-patches\" group with 3 updates across multiple ecosystems"
226+
)
227+
end
228+
end
229+
end
230+
end

0 commit comments

Comments
 (0)