extension_bump.rs

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