1use gh_workflow::*;
2use indoc::indoc;
3
4use crate::tasks::workflows::{
5 extension_tests::{self},
6 runners,
7 steps::{self, CommonJobConditions, DEFAULT_REPOSITORY_OWNER_GUARD, NamedJob, named},
8 vars::{
9 JobOutput, StepOutput, WorkflowInput, WorkflowSecret, one_workflow_per_non_main_branch,
10 },
11};
12
13const BUMPVERSION_CONFIG: &str = indoc! {r#"
14 [bumpversion]
15 current_version = "$OLD_VERSION"
16
17 [bumpversion:file:Cargo.toml]
18
19 [bumpversion:file:extension.toml]
20 "#
21};
22
23const VERSION_CHECK: &str = r#"sed -n 's/version = \"\(.*\)\"/\1/p' < extension.toml"#;
24
25// This is used by various extensions repos in the zed-extensions org to bump extension versions.
26pub(crate) fn extension_bump() -> Workflow {
27 let bump_type = WorkflowInput::string("bump-type", Some("patch".to_owned()));
28
29 let app_id = WorkflowSecret::new("app-id", "The app ID used to create the PR");
30 let app_secret =
31 WorkflowSecret::new("app-secret", "The app secret for the corresponding app ID");
32
33 let test_extension = extension_tests::check_extension();
34 let (check_bump_needed, needs_bump, current_version) = check_bump_needed();
35
36 let needs_bump = needs_bump.as_job_output(&check_bump_needed);
37 let current_version = current_version.as_job_output(&check_bump_needed);
38
39 let dependencies = [&test_extension, &check_bump_needed];
40
41 let bump_version =
42 bump_extension_version(&dependencies, &bump_type, &needs_bump, &app_id, &app_secret);
43 let create_label = create_version_label(
44 &dependencies,
45 &needs_bump,
46 ¤t_version,
47 &app_id,
48 &app_secret,
49 );
50
51 named::workflow()
52 .add_event(
53 Event::default().workflow_call(
54 WorkflowCall::default()
55 .add_input(bump_type.name, bump_type.call_input())
56 .secrets([
57 (app_id.name.to_owned(), app_id.secret_configuration()),
58 (
59 app_secret.name.to_owned(),
60 app_secret.secret_configuration(),
61 ),
62 ]),
63 ),
64 )
65 .concurrency(one_workflow_per_non_main_branch())
66 .add_env(("CARGO_TERM_COLOR", "always"))
67 .add_env(("RUST_BACKTRACE", 1))
68 .add_env(("CARGO_INCREMENTAL", 0))
69 .add_env((
70 "ZED_EXTENSION_CLI_SHA",
71 extension_tests::ZED_EXTENSION_CLI_SHA,
72 ))
73 .add_job(test_extension.name, test_extension.job)
74 .add_job(check_bump_needed.name, check_bump_needed.job)
75 .add_job(bump_version.name, bump_version.job)
76 .add_job(create_label.name, create_label.job)
77}
78
79fn check_bump_needed() -> (NamedJob, StepOutput, StepOutput) {
80 let (compare_versions, version_changed, current_version) = compare_versions();
81
82 let job = Job::default()
83 .with_repository_owner_guard()
84 .outputs([
85 (version_changed.name.to_owned(), version_changed.to_string()),
86 (
87 current_version.name.to_string(),
88 current_version.to_string(),
89 ),
90 ])
91 .runs_on(runners::LINUX_SMALL)
92 .timeout_minutes(1u32)
93 .add_step(steps::checkout_repo().add_with(("fetch-depth", 10)))
94 .add_step(compare_versions);
95
96 (named::job(job), version_changed, current_version)
97}
98
99fn create_version_label(
100 dependencies: &[&NamedJob],
101 needs_bump: &JobOutput,
102 current_version: &JobOutput,
103 app_id: &WorkflowSecret,
104 app_secret: &WorkflowSecret,
105) -> NamedJob {
106 let (generate_token, generated_token) = generate_token(app_id, app_secret);
107 let job = steps::dependant_job(dependencies)
108 .cond(Expression::new(format!(
109 "{DEFAULT_REPOSITORY_OWNER_GUARD} && {} == 'false'",
110 needs_bump.expr(),
111 )))
112 .runs_on(runners::LINUX_LARGE)
113 .timeout_minutes(1u32)
114 .add_step(generate_token)
115 .add_step(steps::checkout_repo())
116 .add_step(create_version_tag(current_version, generated_token));
117
118 named::job(job)
119}
120
121fn create_version_tag(current_version: &JobOutput, generated_token: StepOutput) -> Step<Use> {
122 named::uses("actions", "github-script", "v7").with(
123 Input::default()
124 .add(
125 "script",
126 format!(
127 indoc! {r#"
128 github.rest.git.createRef({{
129 owner: context.repo.owner,
130 repo: context.repo.repo,
131 ref: 'refs/tags/v{}',
132 sha: context.sha
133 }})"#
134 },
135 current_version
136 ),
137 )
138 .add("github-token", generated_token.to_string()),
139 )
140}
141
142/// Compares the current and previous commit and checks whether versions changed inbetween.
143fn compare_versions() -> (Step<Run>, StepOutput, StepOutput) {
144 let check_needs_bump = named::bash(format!(
145 indoc! {
146 r#"
147 CURRENT_VERSION="$({})"
148
149 git checkout "$(git log -1 --format=%H)"~1
150
151 PREV_COMMIT_VERSION="$({})"
152
153 [[ "$CURRENT_VERSION" == "$PREV_COMMIT_VERSION" ]] && \
154 echo "needs_bump=true" >> "$GITHUB_OUTPUT" || \
155 echo "needs_bump=false" >> "$GITHUB_OUTPUT"
156
157 echo "current_version=${{CURRENT_VERSION}}" >> "$GITHUB_OUTPUT"
158 "#
159 },
160 VERSION_CHECK, VERSION_CHECK
161 ))
162 .id("compare-versions-check");
163
164 let needs_bump = StepOutput::new(&check_needs_bump, "needs_bump");
165 let current_version = StepOutput::new(&check_needs_bump, "current_version");
166
167 (check_needs_bump, needs_bump, current_version)
168}
169
170fn bump_extension_version(
171 dependencies: &[&NamedJob],
172 bump_type: &WorkflowInput,
173 needs_bump: &JobOutput,
174 app_id: &WorkflowSecret,
175 app_secret: &WorkflowSecret,
176) -> NamedJob {
177 let (generate_token, generated_token) = generate_token(app_id, app_secret);
178 let (bump_version, old_version, new_version) = bump_version(bump_type);
179
180 let job = steps::dependant_job(dependencies)
181 .cond(Expression::new(format!(
182 "{DEFAULT_REPOSITORY_OWNER_GUARD} && {} == 'true'",
183 needs_bump.expr(),
184 )))
185 .runs_on(runners::LINUX_LARGE)
186 .timeout_minutes(1u32)
187 .add_step(generate_token)
188 .add_step(steps::checkout_repo())
189 .add_step(install_bump_2_version())
190 .add_step(bump_version)
191 .add_step(create_pull_request(
192 old_version,
193 new_version,
194 generated_token,
195 ));
196
197 named::job(job)
198}
199
200fn generate_token(app_id: &WorkflowSecret, app_secret: &WorkflowSecret) -> (Step<Use>, StepOutput) {
201 let step = named::uses("actions", "create-github-app-token", "v2")
202 .id("generate-token")
203 .add_with(
204 Input::default()
205 .add("app-id", app_id.to_string())
206 .add("private-key", app_secret.to_string()),
207 );
208
209 let generated_token = StepOutput::new(&step, "token");
210
211 (step, generated_token)
212}
213
214fn install_bump_2_version() -> Step<Run> {
215 named::run(runners::Platform::Linux, "pip install bump2version")
216}
217
218fn bump_version(bump_type: &WorkflowInput) -> (Step<Run>, StepOutput, StepOutput) {
219 let step = named::bash(format!(
220 indoc! {r#"
221 OLD_VERSION="$({})"
222
223 cat <<EOF > .bumpversion.cfg
224 {}
225 EOF
226
227 bump2version --verbose {}
228 NEW_VERSION="$({})"
229 cargo update --workspace
230
231 rm .bumpversion.cfg
232
233 echo "old_version=${{OLD_VERSION}}" >> "$GITHUB_OUTPUT"
234 echo "new_version=${{NEW_VERSION}}" >> "$GITHUB_OUTPUT"
235 "#
236 },
237 VERSION_CHECK, BUMPVERSION_CONFIG, bump_type, VERSION_CHECK
238 ))
239 .id("bump-version");
240
241 let old_version = StepOutput::new(&step, "old_version");
242 let new_version = StepOutput::new(&step, "new_version");
243 (step, old_version, new_version)
244}
245
246fn create_pull_request(
247 old_version: StepOutput,
248 new_version: StepOutput,
249 generated_token: StepOutput,
250) -> Step<Use> {
251 let formatted_version = format!("v{}", new_version);
252
253 named::uses("peter-evans", "create-pull-request", "v7").with(
254 Input::default()
255 .add("title", format!("Bump version to {}", new_version))
256 .add(
257 "body",
258 format!(
259 "This PR bumps the version of this extension to {}",
260 formatted_version
261 ),
262 )
263 .add(
264 "commit-message",
265 format!("Bump version to {}", formatted_version),
266 )
267 .add("branch", format!("bump-from-{}", old_version))
268 .add(
269 "committer",
270 "zed-zippy[bot] <234243425+zed-zippy[bot]@users.noreply.github.com>",
271 )
272 .add("base", "main")
273 .add("delete-branch", true)
274 .add("token", generated_token.to_string())
275 .add("sign-commits", true),
276 )
277}