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) = check_bump_needed();
35 let bump_version = bump_extension_version(
36 &[&test_extension, &check_bump_needed],
37 &bump_type,
38 needs_bump.as_job_output(&check_bump_needed),
39 &app_id,
40 &app_secret,
41 );
42
43 named::workflow()
44 .add_event(
45 Event::default().workflow_call(
46 WorkflowCall::default()
47 .add_input(bump_type.name, bump_type.call_input())
48 .secrets([
49 (app_id.name.to_owned(), app_id.secret_configuration()),
50 (
51 app_secret.name.to_owned(),
52 app_secret.secret_configuration(),
53 ),
54 ]),
55 ),
56 )
57 .concurrency(one_workflow_per_non_main_branch())
58 .add_env(("CARGO_TERM_COLOR", "always"))
59 .add_env(("RUST_BACKTRACE", 1))
60 .add_env(("CARGO_INCREMENTAL", 0))
61 .add_env((
62 "ZED_EXTENSION_CLI_SHA",
63 extension_tests::ZED_EXTENSION_CLI_SHA,
64 ))
65 .add_job(test_extension.name, test_extension.job)
66 .add_job(check_bump_needed.name, check_bump_needed.job)
67 .add_job(bump_version.name, bump_version.job)
68}
69
70fn check_bump_needed() -> (NamedJob, StepOutput) {
71 let (compare_versions, version_changed) = compare_versions();
72
73 let job = Job::default()
74 .with_repository_owner_guard()
75 .outputs([(version_changed.name.to_owned(), version_changed.to_string())])
76 .runs_on(runners::LINUX_SMALL)
77 .timeout_minutes(1u32)
78 .add_step(steps::checkout_repo().add_with(("fetch-depth", 10)))
79 .add_step(compare_versions);
80
81 (named::job(job), version_changed)
82}
83
84/// Compares the current and previous commit and checks whether versions changed inbetween.
85fn compare_versions() -> (Step<Run>, StepOutput) {
86 let check_needs_bump = named::bash(format!(
87 indoc! {
88 r#"
89 CURRENT_VERSION="$({})"
90
91 git checkout "$(git log -1 --format=%H)"~1
92
93 PREV_COMMIT_VERSION="$({})"
94
95 [[ "$CURRENT_VERSION" == "$PREV_COMMIT_VERSION" ]] && \
96 echo "needs_bump=true" >> "$GITHUB_OUTPUT" || \
97 echo "needs_bump=false" >> "$GITHUB_OUTPUT"
98
99 "#
100 },
101 VERSION_CHECK, VERSION_CHECK
102 ))
103 .id("compare-versions-check");
104
105 let needs_bump = StepOutput::new(&check_needs_bump, "needs_bump");
106
107 (check_needs_bump, needs_bump)
108}
109
110fn bump_extension_version(
111 dependencies: &[&NamedJob],
112 bump_type: &WorkflowInput,
113 needs_bump: JobOutput,
114 app_id: &WorkflowSecret,
115 app_secret: &WorkflowSecret,
116) -> NamedJob {
117 let (generate_token, generated_token) = generate_token(app_id, app_secret);
118 let (bump_version, old_version, new_version) = bump_version(bump_type);
119
120 let job = steps::dependant_job(dependencies)
121 .cond(Expression::new(format!(
122 "{DEFAULT_REPOSITORY_OWNER_GUARD} && {} == 'true'",
123 needs_bump.expr(),
124 )))
125 .runs_on(runners::LINUX_LARGE)
126 .timeout_minutes(1u32)
127 .add_step(generate_token)
128 .add_step(steps::checkout_repo())
129 .add_step(install_bump_2_version())
130 .add_step(bump_version)
131 .add_step(create_pull_request(
132 old_version,
133 new_version,
134 generated_token,
135 ));
136
137 named::job(job)
138}
139
140fn generate_token(app_id: &WorkflowSecret, app_secret: &WorkflowSecret) -> (Step<Use>, StepOutput) {
141 let step = named::uses("actions", "create-github-app-token", "v2")
142 .id("generate-token")
143 .add_with(
144 Input::default()
145 .add("app-id", app_id.to_string())
146 .add("private-key", app_secret.to_string()),
147 );
148
149 let generated_token = StepOutput::new(&step, "token");
150
151 (step, generated_token)
152}
153
154fn install_bump_2_version() -> Step<Run> {
155 named::run(runners::Platform::Linux, "pip install bump2version")
156}
157
158fn bump_version(bump_type: &WorkflowInput) -> (Step<Run>, StepOutput, StepOutput) {
159 let step = named::bash(format!(
160 indoc! {r#"
161 OLD_VERSION="$({})"
162
163 cat <<EOF > .bumpversion.cfg
164 {}
165 EOF
166
167 bump2version --verbose {}
168 NEW_VERSION="$({})"
169 cargo update --workspace
170
171 rm .bumpversion.cfg
172
173 echo "old_version=${{OLD_VERSION}}" >> "$GITHUB_OUTPUT"
174 echo "new_version=${{NEW_VERSION}}" >> "$GITHUB_OUTPUT"
175 "#
176 },
177 VERSION_CHECK, BUMPVERSION_CONFIG, bump_type, VERSION_CHECK
178 ))
179 .id("bump-version");
180
181 let old_version = StepOutput::new(&step, "old_version");
182 let new_version = StepOutput::new(&step, "new_version");
183 (step, old_version, new_version)
184}
185
186fn create_pull_request(
187 old_version: StepOutput,
188 new_version: StepOutput,
189 generated_token: StepOutput,
190) -> Step<Use> {
191 let formatted_version = format!("v{}", new_version);
192
193 named::uses("peter-evans", "create-pull-request", "v7").with(
194 Input::default()
195 .add("title", format!("Bump version to {}", new_version))
196 .add(
197 "body",
198 format!(
199 "This PR bumps the version of this extension to {}",
200 formatted_version
201 ),
202 )
203 .add(
204 "commit-message",
205 format!("Bump version to {}", formatted_version),
206 )
207 .add("branch", format!("bump-from-{}", old_version))
208 .add(
209 "committer",
210 "zed-zippy[bot] <234243425+zed-zippy[bot]@users.noreply.github.com>",
211 )
212 .add("base", "main")
213 .add("delete-branch", true)
214 .add("token", generated_token.to_string())
215 .add("sign-commits", true),
216 )
217}