extension_ci: Merge extension workflow files into one file (#48810)

Finn Evers created

This PR consolidates the two workflow files shipped to extensions into
one, which stops us from requiring a separate runner for what can
actually be done in one workflow.

It also
- adds a check that version bumps will now only be created by zed-zippy
in separate commits.
- Makes the bumping of versions faster and more reliable.
- (Hopefully) fixes an issue we were seeing in some CI tests.

Release Notes:

- N/A

Change summary

.github/workflows/extension_bump.yml                            |  55 
.github/workflows/extension_release.yml                         |  45 
.github/workflows/extension_tests.yml                           |  30 
extensions/workflows/shared/release_version.yml                 |  19 
tooling/xtask/src/tasks/workflows.rs                            |   3 
tooling/xtask/src/tasks/workflows/extension_bump.rs             | 207 +-
tooling/xtask/src/tasks/workflows/extension_release.rs          |  75 -
tooling/xtask/src/tasks/workflows/extension_tests.rs            |  22 
tooling/xtask/src/tasks/workflows/extensions.rs                 |   1 
tooling/xtask/src/tasks/workflows/extensions/bump_version.rs    |  12 
tooling/xtask/src/tasks/workflows/extensions/release_version.rs |  31 
11 files changed, 226 insertions(+), 274 deletions(-)

Detailed changes

.github/workflows/extension_bump.yml 🔗

@@ -25,7 +25,7 @@ on:
         description: The app secret for the corresponding app ID
         required: true
 jobs:
-  check_bump_needed:
+  check_version_changed:
     if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
     runs-on: namespace-profile-2x4-ubuntu-2404
     steps:
@@ -51,20 +51,20 @@ jobs:
         PARENT_COMMIT_VERSION="$(sed -n 's/version = \"\(.*\)\"/\1/p' < extension.toml)"
 
         [[ "$CURRENT_VERSION" == "$PARENT_COMMIT_VERSION" ]] && \
-          echo "needs_bump=true" >> "$GITHUB_OUTPUT" || \
-          echo "needs_bump=false" >> "$GITHUB_OUTPUT"
+            echo "version_changed=false" >> "$GITHUB_OUTPUT" || \
+            echo "version_changed=true" >> "$GITHUB_OUTPUT"
 
         echo "current_version=${CURRENT_VERSION}" >> "$GITHUB_OUTPUT"
     outputs:
-      needs_bump: ${{ steps.compare-versions-check.outputs.needs_bump }}
+      version_changed: ${{ steps.compare-versions-check.outputs.version_changed }}
       current_version: ${{ steps.compare-versions-check.outputs.current_version }}
     timeout-minutes: 1
   bump_extension_version:
     needs:
-    - check_bump_needed
+    - check_version_changed
     if: |-
       (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions') &&
-      (inputs.force-bump == 'true' || needs.check_bump_needed.outputs.needs_bump == 'true')
+      (inputs.force-bump == 'true' || needs.check_version_changed.outputs.version_changed == 'false')
     runs-on: namespace-profile-2x4-ubuntu-2404
     steps:
     - id: generate-token
@@ -82,7 +82,7 @@ jobs:
     - id: bump-version
       name: extension_bump::bump_version
       run: |
-        OLD_VERSION="${{ needs.check_bump_needed.outputs.current_version }}"
+        OLD_VERSION="${{ needs.check_version_changed.outputs.current_version }}"
 
         BUMP_FILES=("extension.toml")
         if [[ -f "Cargo.toml" ]]; then
@@ -96,7 +96,7 @@ jobs:
             --no-configured-files ${{ inputs.bump-type }} "${BUMP_FILES[@]}"
 
         if [[ -f "Cargo.toml" ]]; then
-            cargo update --workspace
+            cargo update --workspace --offline
         fi
 
         NEW_VERSION="$(sed -n 's/version = \"\(.*\)\"/\1/p' < extension.toml)"
@@ -118,8 +118,8 @@ jobs:
     timeout-minutes: 1
   create_version_label:
     needs:
-    - check_bump_needed
-    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions') && github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.check_bump_needed.outputs.needs_bump == 'false'
+    - check_version_changed
+    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions') && github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.check_version_changed.outputs.version_changed == 'true'
     runs-on: namespace-profile-2x4-ubuntu-2404
     steps:
     - id: generate-token
@@ -139,11 +139,44 @@ jobs:
           github.rest.git.createRef({
               owner: context.repo.owner,
               repo: context.repo.repo,
-              ref: 'refs/tags/v${{ needs.check_bump_needed.outputs.current_version }}',
+              ref: 'refs/tags/v${{ needs.check_version_changed.outputs.current_version }}',
               sha: context.sha
           })
         github-token: ${{ steps.generate-token.outputs.token }}
     timeout-minutes: 1
+  trigger_release:
+    needs:
+    - check_version_changed
+    - create_version_label
+    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
+    runs-on: namespace-profile-2x4-ubuntu-2404
+    steps:
+    - id: generate-token
+      name: extension_bump::generate_token
+      uses: actions/create-github-app-token@v2
+      with:
+        app-id: ${{ secrets.app-id }}
+        private-key: ${{ secrets.app-secret }}
+        owner: zed-industries
+        repositories: extensions
+    - name: steps::checkout_repo
+      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+      with:
+        clean: false
+    - id: get-extension-id
+      name: extension_bump::get_extension_id
+      run: |
+        EXTENSION_ID="$(sed -n 's/id = \"\(.*\)\"/\1/p' < extension.toml)"
+
+        echo "extension_id=${EXTENSION_ID}" >> "$GITHUB_OUTPUT"
+    - name: extension_bump::release_action
+      uses: huacnlee/zed-extension-action@v2
+      with:
+        extension-name: ${{ steps.get-extension-id.outputs.extension_id }}
+        push-to: zed-industries/extensions
+        tag: v${{ needs.check_version_changed.outputs.current_version }}
+      env:
+        COMMITTER_TOKEN: ${{ steps.generate-token.outputs.token }}
 concurrency:
   group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
   cancel-in-progress: true

.github/workflows/extension_release.yml 🔗

@@ -1,45 +0,0 @@
-# Generated from xtask::workflows::extension_release
-# Rebuild with `cargo xtask workflows`.
-name: extension_release
-on:
-  workflow_call:
-    secrets:
-      app-id:
-        description: The app ID used to create the PR
-        required: true
-      app-secret:
-        description: The app secret for the corresponding app ID
-        required: true
-jobs:
-  create_release:
-    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
-    runs-on: namespace-profile-2x4-ubuntu-2404
-    steps:
-    - id: generate-token
-      name: extension_bump::generate_token
-      uses: actions/create-github-app-token@v2
-      with:
-        app-id: ${{ secrets.app-id }}
-        private-key: ${{ secrets.app-secret }}
-        owner: zed-industries
-        repositories: extensions
-    - name: steps::checkout_repo
-      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
-      with:
-        clean: false
-    - id: get-extension-id
-      name: extension_release::get_extension_id
-      run: |
-        EXTENSION_ID="$(sed -n 's/id = \"\(.*\)\"/\1/p' < extension.toml)"
-
-        echo "extension_id=${EXTENSION_ID}" >> "$GITHUB_OUTPUT"
-    - name: extension_release::release_action
-      uses: huacnlee/zed-extension-action@v2
-      with:
-        extension-name: ${{ steps.get-extension-id.outputs.extension_id }}
-        push-to: zed-industries/extensions
-      env:
-        COMMITTER_TOKEN: ${{ steps.generate-token.outputs.token }}
-defaults:
-  run:
-    shell: bash -euxo pipefail {0}

.github/workflows/extension_tests.yml 🔗

@@ -52,7 +52,7 @@ jobs:
     needs:
     - orchestrate
     if: needs.orchestrate.outputs.check_rust == 'true'
-    runs-on: namespace-profile-8x16-ubuntu-2204
+    runs-on: namespace-profile-8x32-ubuntu-2404
     steps:
     - name: steps::checkout_repo
       uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
@@ -102,6 +102,34 @@ jobs:
         mkdir -p /tmp/ext-scratch
         mkdir -p /tmp/ext-output
         ./zed-extension --source-dir . --scratch-dir /tmp/ext-scratch --output-dir /tmp/ext-output
+    - id: compare-versions-check
+      name: extension_bump::compare_versions
+      run: |
+        CURRENT_VERSION="$(sed -n 's/version = \"\(.*\)\"/\1/p' < extension.toml)"
+        PR_PARENT_SHA="${{ github.event.pull_request.head.sha }}"
+
+        if [[ -n "$PR_PARENT_SHA" ]]; then
+            git checkout "$PR_PARENT_SHA"
+        elif BRANCH_PARENT_SHA="$(git merge-base origin/main origin/zed-zippy-autobump)"; then
+            git checkout "$BRANCH_PARENT_SHA"
+        else
+            git checkout "$(git log -1 --format=%H)"~1
+        fi
+
+        PARENT_COMMIT_VERSION="$(sed -n 's/version = \"\(.*\)\"/\1/p' < extension.toml)"
+
+        [[ "$CURRENT_VERSION" == "$PARENT_COMMIT_VERSION" ]] && \
+            echo "version_changed=false" >> "$GITHUB_OUTPUT" || \
+            echo "version_changed=true" >> "$GITHUB_OUTPUT"
+
+        echo "current_version=${CURRENT_VERSION}" >> "$GITHUB_OUTPUT"
+    - name: extension_tests::verify_version_did_not_change
+      run: |
+        if [[ ${{ steps.compare-versions-check.outputs.version_changed }} == "true" && "${{ github.event_name }}" == "pull_request" && "${{ github.event.pull_request.user.login }}" != "zed-zippy[bot]" ]] ; then
+            echo "Version change detected in your change!"
+            echo "Version changes happen in separate PRs and will be performed by the zed-zippy bot"
+            exit 42
+        fi
     timeout-minutes: 4
   tests_pass:
     needs:

extensions/workflows/shared/release_version.yml 🔗

@@ -1,19 +0,0 @@
-# Generated from xtask::workflows::extensions::release_version within the Zed repository.
-# Rebuild with `cargo xtask workflows`.
-name: extensions::release_version
-on:
-  push:
-    tags:
-    - v**
-jobs:
-  call_release_version:
-    permissions:
-      contents: write
-      pull-requests: write
-    uses: zed-industries/zed/.github/workflows/extension_release.yml@main
-    secrets:
-      app-id: ${{ secrets.ZED_ZIPPY_APP_ID }}
-      app-secret: ${{ secrets.ZED_ZIPPY_APP_PRIVATE_KEY }}
-defaults:
-  run:
-    shell: bash -euxo pipefail {0}

tooling/xtask/src/tasks/workflows.rs 🔗

@@ -11,7 +11,6 @@ mod cherry_pick;
 mod compare_perf;
 mod danger;
 mod extension_bump;
-mod extension_release;
 mod extension_tests;
 mod extension_workflow_rollout;
 mod extensions;
@@ -135,7 +134,6 @@ pub fn run_workflows(_: GenerateWorkflowArgs) -> Result<()> {
         WorkflowFile::zed(compare_perf::compare_perf),
         WorkflowFile::zed(danger::danger),
         WorkflowFile::zed(extension_bump::extension_bump),
-        WorkflowFile::zed(extension_release::extension_release),
         WorkflowFile::zed(extension_tests::extension_tests),
         WorkflowFile::zed(extension_workflow_rollout::extension_workflow_rollout),
         WorkflowFile::zed(publish_extension_cli::publish_extension_cli),
@@ -149,7 +147,6 @@ pub fn run_workflows(_: GenerateWorkflowArgs) -> Result<()> {
         /* workflows used for CI/CD in extension repositories */
         WorkflowFile::extension(extensions::run_tests::run_tests),
         WorkflowFile::extension_shared(extensions::bump_version::bump_version),
-        WorkflowFile::extension_shared(extensions::release_version::release_version),
     ];
 
     for workflow_file in workflows {

tooling/xtask/src/tasks/workflows/extension_bump.rs 🔗

@@ -1,12 +1,12 @@
 use gh_workflow::{ctx::Context, *};
-use indoc::indoc;
+use indoc::{formatdoc, indoc};
 
 use crate::tasks::workflows::{
-    extension_release::extension_workflow_secrets,
     extension_tests::{self},
     runners,
     steps::{
-        self, CommonJobConditions, DEFAULT_REPOSITORY_OWNER_GUARD, FluentBuilder, NamedJob, named,
+        self, CommonJobConditions, DEFAULT_REPOSITORY_OWNER_GUARD, FluentBuilder, NamedJob,
+        checkout_repo, dependant_job, named,
     },
     vars::{
         JobOutput, StepOutput, WorkflowInput, WorkflowSecret, one_workflow_per_non_main_branch,
@@ -23,28 +23,34 @@ pub(crate) fn extension_bump() -> Workflow {
     let force_bump = WorkflowInput::bool("force-bump", None);
 
     let (app_id, app_secret) = extension_workflow_secrets();
-    let (check_bump_needed, needs_bump, current_version) = check_bump_needed();
+    let (check_version_changed, version_changed, current_version) = check_version_changed();
 
-    let needs_bump = needs_bump.as_job_output(&check_bump_needed);
-    let current_version = current_version.as_job_output(&check_bump_needed);
+    let version_changed = version_changed.as_job_output(&check_version_changed);
+    let current_version = current_version.as_job_output(&check_version_changed);
 
-    let dependencies = [&check_bump_needed];
+    let dependencies = [&check_version_changed];
     let bump_version = bump_extension_version(
         &dependencies,
         &current_version,
         &bump_type,
-        &needs_bump,
+        &version_changed,
         &force_bump,
         &app_id,
         &app_secret,
     );
     let create_label = create_version_label(
         &dependencies,
-        &needs_bump,
+        &version_changed,
         &current_version,
         &app_id,
         &app_secret,
     );
+    let trigger_release = trigger_release(
+        &[&check_version_changed, &create_label],
+        current_version,
+        &app_id,
+        &app_secret,
+    );
 
     named::workflow()
         .add_event(
@@ -69,12 +75,13 @@ pub(crate) fn extension_bump() -> Workflow {
             "ZED_EXTENSION_CLI_SHA",
             extension_tests::ZED_EXTENSION_CLI_SHA,
         ))
-        .add_job(check_bump_needed.name, check_bump_needed.job)
+        .add_job(check_version_changed.name, check_version_changed.job)
         .add_job(bump_version.name, bump_version.job)
         .add_job(create_label.name, create_label.job)
+        .add_job(trigger_release.name, trigger_release.job)
 }
 
-fn check_bump_needed() -> (NamedJob, StepOutput, StepOutput) {
+fn check_version_changed() -> (NamedJob, StepOutput, StepOutput) {
     let (compare_versions, version_changed, current_version) = compare_versions();
 
     let job = Job::default()
@@ -96,7 +103,7 @@ fn check_bump_needed() -> (NamedJob, StepOutput, StepOutput) {
 
 fn create_version_label(
     dependencies: &[&NamedJob],
-    needs_bump: &JobOutput,
+    version_changed_output: &JobOutput,
     current_version: &JobOutput,
     app_id: &WorkflowSecret,
     app_secret: &WorkflowSecret,
@@ -105,8 +112,9 @@ fn create_version_label(
         generate_token(&app_id.to_string(), &app_secret.to_string(), None);
     let job = steps::dependant_job(dependencies)
         .cond(Expression::new(format!(
-            "{DEFAULT_REPOSITORY_OWNER_GUARD} && github.event_name == 'push' && github.ref == 'refs/heads/main' && {} == 'false'",
-            needs_bump.expr(),
+            "{DEFAULT_REPOSITORY_OWNER_GUARD} && github.event_name == 'push' && \
+            github.ref == 'refs/heads/main' && {version_changed} == 'true'",
+            version_changed = version_changed_output.expr(),
         )))
         .runs_on(runners::LINUX_SMALL)
         .timeout_minutes(1u32)
@@ -122,28 +130,24 @@ fn create_version_tag(current_version: &JobOutput, generated_token: StepOutput)
         Input::default()
             .add(
                 "script",
-                format!(
-                    indoc! {r#"
-                        github.rest.git.createRef({{
-                            owner: context.repo.owner,
-                            repo: context.repo.repo,
-                            ref: 'refs/tags/v{}',
-                            sha: context.sha
-                        }})"#
-                    },
-                    current_version
-                ),
+                formatdoc! {r#"
+                    github.rest.git.createRef({{
+                        owner: context.repo.owner,
+                        repo: context.repo.repo,
+                        ref: 'refs/tags/v{current_version}',
+                        sha: context.sha
+                    }})"#
+                },
             )
             .add("github-token", generated_token.to_string()),
     )
 }
 
 /// Compares the current and previous commit and checks whether versions changed inbetween.
-fn compare_versions() -> (Step<Run>, StepOutput, StepOutput) {
-    let check_needs_bump = named::bash(format!(
-        indoc! {
-        r#"
-        CURRENT_VERSION="$({})"
+pub(crate) fn compare_versions() -> (Step<Run>, StepOutput, StepOutput) {
+    let check_needs_bump = named::bash(formatdoc! {
+    r#"
+        CURRENT_VERSION="$({VERSION_CHECK})"
         PR_PARENT_SHA="${{{{ github.event.pull_request.head.sha }}}}"
 
         if [[ -n "$PR_PARENT_SHA" ]]; then
@@ -154,31 +158,29 @@ fn compare_versions() -> (Step<Run>, StepOutput, StepOutput) {
             git checkout "$(git log -1 --format=%H)"~1
         fi
 
-        PARENT_COMMIT_VERSION="$({})"
+        PARENT_COMMIT_VERSION="$({VERSION_CHECK})"
 
         [[ "$CURRENT_VERSION" == "$PARENT_COMMIT_VERSION" ]] && \
-          echo "needs_bump=true" >> "$GITHUB_OUTPUT" || \
-          echo "needs_bump=false" >> "$GITHUB_OUTPUT"
+            echo "version_changed=false" >> "$GITHUB_OUTPUT" || \
+            echo "version_changed=true" >> "$GITHUB_OUTPUT"
 
         echo "current_version=${{CURRENT_VERSION}}" >> "$GITHUB_OUTPUT"
         "#
-        },
-        VERSION_CHECK, VERSION_CHECK
-    ))
+    })
     .id("compare-versions-check");
 
-    let needs_bump = StepOutput::new(&check_needs_bump, "needs_bump");
+    let version_changed = StepOutput::new(&check_needs_bump, "version_changed");
     let current_version = StepOutput::new(&check_needs_bump, "current_version");
 
-    (check_needs_bump, needs_bump, current_version)
+    (check_needs_bump, version_changed, current_version)
 }
 
 fn bump_extension_version(
     dependencies: &[&NamedJob],
     current_version: &JobOutput,
     bump_type: &WorkflowInput,
-    needs_bump: &JobOutput,
-    force_bump: &WorkflowInput,
+    version_changed_output: &JobOutput,
+    force_bump_output: &WorkflowInput,
     app_id: &WorkflowSecret,
     app_secret: &WorkflowSecret,
 ) -> NamedJob {
@@ -188,9 +190,9 @@ fn bump_extension_version(
 
     let job = steps::dependant_job(dependencies)
         .cond(Expression::new(format!(
-            "{DEFAULT_REPOSITORY_OWNER_GUARD} &&\n({} == 'true' || {} == 'true')",
-            force_bump.expr(),
-            needs_bump.expr(),
+            "{DEFAULT_REPOSITORY_OWNER_GUARD} &&\n({force_bump} == 'true' || {version_changed} == 'false')",
+            force_bump = force_bump_output.expr(),
+            version_changed = version_changed_output.expr(),
         )))
         .runs_on(runners::LINUX_SMALL)
         .timeout_minutes(1u32)
@@ -254,32 +256,29 @@ fn install_bump_2_version() -> Step<Run> {
 }
 
 fn bump_version(current_version: &JobOutput, bump_type: &WorkflowInput) -> (Step<Run>, StepOutput) {
-    let step = named::bash(format!(
-        indoc! {r#"
-            OLD_VERSION="{}"
-
-            BUMP_FILES=("extension.toml")
-            if [[ -f "Cargo.toml" ]]; then
-                BUMP_FILES+=("Cargo.toml")
-            fi
-
-            bump2version \
-                --search "version = \"{{current_version}}"\" \
-                --replace "version = \"{{new_version}}"\" \
-                --current-version "$OLD_VERSION" \
-                --no-configured-files {} "${{BUMP_FILES[@]}}"
-
-            if [[ -f "Cargo.toml" ]]; then
-                cargo update --workspace
-            fi
-
-            NEW_VERSION="$({})"
-
-            echo "new_version=${{NEW_VERSION}}" >> "$GITHUB_OUTPUT"
-            "#
-        },
-        current_version, bump_type, VERSION_CHECK
-    ))
+    let step = named::bash(formatdoc! {r#"
+        OLD_VERSION="{current_version}"
+
+        BUMP_FILES=("extension.toml")
+        if [[ -f "Cargo.toml" ]]; then
+            BUMP_FILES+=("Cargo.toml")
+        fi
+
+        bump2version \
+            --search "version = \"{{current_version}}"\" \
+            --replace "version = \"{{new_version}}"\" \
+            --current-version "$OLD_VERSION" \
+            --no-configured-files {bump_type} "${{BUMP_FILES[@]}}"
+
+        if [[ -f "Cargo.toml" ]]; then
+            cargo update --workspace --offline
+        fi
+
+        NEW_VERSION="$({VERSION_CHECK})"
+
+        echo "new_version=${{NEW_VERSION}}" >> "$GITHUB_OUTPUT"
+        "#
+    })
     .id("bump-version");
 
     let new_version = StepOutput::new(&step, "new_version");
@@ -287,21 +286,18 @@ fn bump_version(current_version: &JobOutput, bump_type: &WorkflowInput) -> (Step
 }
 
 fn create_pull_request(new_version: StepOutput, generated_token: StepOutput) -> Step<Use> {
-    let formatted_version = format!("v{}", new_version);
+    let formatted_version = format!("v{new_version}");
 
     named::uses("peter-evans", "create-pull-request", "v7").with(
         Input::default()
-            .add("title", format!("Bump version to {}", new_version))
+            .add("title", format!("Bump version to {new_version}"))
             .add(
                 "body",
-                format!(
-                    "This PR bumps the version of this extension to {}",
-                    formatted_version
-                ),
+                format!("This PR bumps the version of this extension to {formatted_version}",),
             )
             .add(
                 "commit-message",
-                format!("Bump version to {}", formatted_version),
+                format!("Bump version to {formatted_version}"),
             )
             .add("branch", "zed-zippy-autobump")
             .add(
@@ -316,6 +312,65 @@ fn create_pull_request(new_version: StepOutput, generated_token: StepOutput) ->
     )
 }
 
+fn trigger_release(
+    dependencies: &[&NamedJob],
+    version: JobOutput,
+    app_id: &WorkflowSecret,
+    app_secret: &WorkflowSecret,
+) -> NamedJob {
+    let extension_registry = RepositoryTarget::new("zed-industries", &["extensions"]);
+    let (generate_token, generated_token) = generate_token(
+        &app_id.to_string(),
+        &app_secret.to_string(),
+        Some(extension_registry),
+    );
+    let (get_extension_id, extension_id) = get_extension_id();
+
+    let job = dependant_job(dependencies)
+        .with_repository_owner_guard()
+        .runs_on(runners::LINUX_SMALL)
+        .add_step(generate_token)
+        .add_step(checkout_repo())
+        .add_step(get_extension_id)
+        .add_step(release_action(extension_id, version, generated_token));
+
+    named::job(job)
+}
+
+fn get_extension_id() -> (Step<Run>, StepOutput) {
+    let step = named::bash(indoc! {
+    r#"
+        EXTENSION_ID="$(sed -n 's/id = \"\(.*\)\"/\1/p' < extension.toml)"
+
+        echo "extension_id=${EXTENSION_ID}" >> "$GITHUB_OUTPUT"
+    "#})
+    .id("get-extension-id");
+
+    let extension_id = StepOutput::new(&step, "extension_id");
+
+    (step, extension_id)
+}
+
+fn release_action(
+    extension_id: StepOutput,
+    version: JobOutput,
+    generated_token: StepOutput,
+) -> Step<Use> {
+    named::uses("huacnlee", "zed-extension-action", "v2")
+        .add_with(("extension-name", extension_id.to_string()))
+        .add_with(("push-to", "zed-industries/extensions"))
+        .add_with(("tag", format!("v{version}")))
+        .add_env(("COMMITTER_TOKEN", generated_token.to_string()))
+}
+
+fn extension_workflow_secrets() -> (WorkflowSecret, WorkflowSecret) {
+    let app_id = WorkflowSecret::new("app-id", "The app ID used to create the PR");
+    let app_secret =
+        WorkflowSecret::new("app-secret", "The app secret for the corresponding app ID");
+
+    (app_id, app_secret)
+}
+
 pub(crate) struct RepositoryTarget {
     owner: Option<String>,
     repositories: Option<String>,

tooling/xtask/src/tasks/workflows/extension_release.rs 🔗

@@ -1,75 +0,0 @@
-use gh_workflow::{Event, Job, Run, Step, Use, Workflow, WorkflowCall};
-use indoc::indoc;
-
-use crate::tasks::workflows::{
-    extension_bump::{RepositoryTarget, generate_token},
-    runners,
-    steps::{CommonJobConditions, NamedJob, checkout_repo, named},
-    vars::{StepOutput, WorkflowSecret},
-};
-
-pub(crate) fn extension_release() -> Workflow {
-    let (app_id, app_secret) = extension_workflow_secrets();
-
-    let create_release = create_release(&app_id, &app_secret);
-    named::workflow()
-        .on(
-            Event::default().workflow_call(WorkflowCall::default().secrets([
-                (app_id.name.to_owned(), app_id.secret_configuration()),
-                (
-                    app_secret.name.to_owned(),
-                    app_secret.secret_configuration(),
-                ),
-            ])),
-        )
-        .add_job(create_release.name, create_release.job)
-}
-
-fn create_release(app_id: &WorkflowSecret, app_secret: &WorkflowSecret) -> NamedJob {
-    let extension_registry = RepositoryTarget::new("zed-industries", &["extensions"]);
-    let (generate_token, generated_token) = generate_token(
-        &app_id.to_string(),
-        &app_secret.to_string(),
-        Some(extension_registry),
-    );
-    let (get_extension_id, extension_id) = get_extension_id();
-
-    let job = Job::default()
-        .with_repository_owner_guard()
-        .runs_on(runners::LINUX_SMALL)
-        .add_step(generate_token)
-        .add_step(checkout_repo())
-        .add_step(get_extension_id)
-        .add_step(release_action(extension_id, generated_token));
-
-    named::job(job)
-}
-
-fn get_extension_id() -> (Step<Run>, StepOutput) {
-    let step = named::bash(indoc! {
-    r#"
-        EXTENSION_ID="$(sed -n 's/id = \"\(.*\)\"/\1/p' < extension.toml)"
-
-        echo "extension_id=${EXTENSION_ID}" >> "$GITHUB_OUTPUT"
-    "#})
-    .id("get-extension-id");
-
-    let extension_id = StepOutput::new(&step, "extension_id");
-
-    (step, extension_id)
-}
-
-fn release_action(extension_id: StepOutput, generated_token: StepOutput) -> Step<Use> {
-    named::uses("huacnlee", "zed-extension-action", "v2")
-        .add_with(("extension-name", extension_id.to_string()))
-        .add_with(("push-to", "zed-industries/extensions"))
-        .add_env(("COMMITTER_TOKEN", generated_token.to_string()))
-}
-
-pub(crate) fn extension_workflow_secrets() -> (WorkflowSecret, WorkflowSecret) {
-    let app_id = WorkflowSecret::new("app-id", "The app ID used to create the PR");
-    let app_secret =
-        WorkflowSecret::new("app-secret", "The app secret for the corresponding app ID");
-
-    (app_id, app_secret)
-}

tooling/xtask/src/tasks/workflows/extension_tests.rs 🔗

@@ -1,7 +1,8 @@
 use gh_workflow::*;
-use indoc::indoc;
+use indoc::{formatdoc, indoc};
 
 use crate::tasks::workflows::{
+    extension_bump::compare_versions,
     run_tests::{orchestrate_without_package_filter, tests_pass},
     runners,
     steps::{self, CommonJobConditions, FluentBuilder, NamedJob, named},
@@ -58,7 +59,7 @@ fn run_clippy() -> Step<Run> {
 fn check_rust() -> NamedJob {
     let job = Job::default()
         .with_repository_owner_guard()
-        .runs_on(runners::LINUX_LARGE)
+        .runs_on(runners::LINUX_LARGE_RAM)
         .timeout_minutes(6u32)
         .add_step(steps::checkout_repo())
         .add_step(steps::cache_rust_dependencies_namespace())
@@ -78,6 +79,8 @@ fn check_rust() -> NamedJob {
 
 pub(crate) fn check_extension() -> NamedJob {
     let (cache_download, cache_hit) = cache_zed_extension_cli();
+    let (check_version_job, version_changed, _) = compare_versions();
+
     let job = Job::default()
         .with_repository_owner_guard()
         .runs_on(runners::LINUX_LARGE_RAM)
@@ -85,7 +88,9 @@ pub(crate) fn check_extension() -> NamedJob {
         .add_step(steps::checkout_repo())
         .add_step(cache_download)
         .add_step(download_zed_extension_cli(cache_hit))
-        .add_step(check());
+        .add_step(check())
+        .add_step(check_version_job)
+        .add_step(verify_version_did_not_change(version_changed));
 
     named::job(job)
 }
@@ -126,3 +131,14 @@ pub fn check() -> Step<Run> {
         "#
     })
 }
+
+fn verify_version_did_not_change(version_changed: StepOutput) -> Step<Run> {
+    named::bash(formatdoc! {r#"
+        if [[ {version_changed} == "true" && "${{{{ github.event_name }}}}" == "pull_request" && "${{{{ github.event.pull_request.user.login }}}}" != "zed-zippy[bot]" ]] ; then
+            echo "Version change detected in your change!"
+            echo "Version changes happen in separate PRs and will be performed by the zed-zippy bot"
+            exit 42
+        fi
+        "#
+    })
+}

tooling/xtask/src/tasks/workflows/extensions.rs 🔗

@@ -4,7 +4,6 @@ use indexmap::IndexMap;
 use crate::tasks::workflows::vars;
 
 pub(crate) mod bump_version;
-pub(crate) mod release_version;
 pub(crate) mod run_tests;
 
 pub(crate) trait WithAppSecrets: Sized {

tooling/xtask/src/tasks/workflows/extensions/bump_version.rs 🔗

@@ -2,13 +2,13 @@ use gh_workflow::{
     Event, Expression, Input, Job, Level, Permissions, PullRequest, PullRequestType, Push, Run,
     Step, UsesJob, Workflow, WorkflowDispatch,
 };
-use indexmap::IndexMap;
 use indoc::indoc;
 
 use crate::tasks::workflows::{
+    extensions::WithAppSecrets,
     runners,
     steps::{CommonJobConditions, NamedJob, named},
-    vars::{self, JobOutput, StepOutput, one_workflow_per_non_main_branch_and_token},
+    vars::{JobOutput, StepOutput, one_workflow_per_non_main_branch_and_token},
 };
 
 pub(crate) fn bump_version() -> Workflow {
@@ -59,13 +59,7 @@ pub(crate) fn call_bump_version(
                 .add("bump-type", bump_type.to_string())
                 .add("force-bump", true),
         )
-        .secrets(IndexMap::from([
-            ("app-id".to_owned(), vars::ZED_ZIPPY_APP_ID.to_owned()),
-            (
-                "app-secret".to_owned(),
-                vars::ZED_ZIPPY_APP_PRIVATE_KEY.to_owned(),
-            ),
-        ]));
+        .with_app_secrets();
 
     named::job(job)
 }

tooling/xtask/src/tasks/workflows/extensions/release_version.rs 🔗

@@ -1,31 +0,0 @@
-use gh_workflow::{Event, Job, Level, Permissions, Push, UsesJob, Workflow};
-
-use crate::tasks::workflows::{
-    extensions::WithAppSecrets,
-    steps::{NamedJob, named},
-};
-
-pub(crate) fn release_version() -> Workflow {
-    let create_release = call_release_version();
-    named::workflow()
-        .on(Event::default().push(Push::default().add_tag("v**")))
-        .add_job(create_release.name, create_release.job)
-}
-
-pub(crate) fn call_release_version() -> NamedJob<UsesJob> {
-    let job = Job::default()
-        .permissions(
-            Permissions::default()
-                .contents(Level::Write)
-                .pull_requests(Level::Write),
-        )
-        .uses(
-            "zed-industries",
-            "zed",
-            ".github/workflows/extension_release.yml",
-            "main",
-        )
-        .with_app_secrets();
-
-    named::job(job)
-}