extension_bump.rs

  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        &current_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        &current_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}