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