Refresh zed.dev releases page after releases (#42060)

Conrad Irwin created

Release Notes:

- N/A

Change summary

.github/workflows/after_release.yml                |  69 ++++++++
.github/workflows/community_release_actions.yml    |  93 ------------
Cargo.lock                                         |   1 
tooling/xtask/Cargo.toml                           |   1 
tooling/xtask/src/tasks/workflows.rs               |   2 
tooling/xtask/src/tasks/workflows/after_release.rs | 123 ++++++++++++++++
tooling/xtask/src/tasks/workflows/vars.rs          |  21 ++
7 files changed, 217 insertions(+), 93 deletions(-)

Detailed changes

.github/workflows/after_release.yml 🔗

@@ -0,0 +1,69 @@
+# Generated from xtask::workflows::after_release
+# Rebuild with `cargo xtask workflows`.
+name: after_release
+on:
+  release:
+    types:
+    - published
+jobs:
+  rebuild_releases_page:
+    if: github.repository_owner == 'zed-industries'
+    runs-on: namespace-profile-2x4-ubuntu-2404
+    steps:
+    - name: after_release::rebuild_releases_page
+      run: 'curl https://zed.dev/api/revalidate-releases -H "Authorization: Bearer ${RELEASE_NOTES_API_TOKEN}"'
+      shell: bash -euxo pipefail {0}
+      env:
+        RELEASE_NOTES_API_TOKEN: ${{ secrets.RELEASE_NOTES_API_TOKEN }}
+  post_to_discord:
+    needs:
+    - rebuild_releases_page
+    if: github.repository_owner == 'zed-industries'
+    runs-on: namespace-profile-2x4-ubuntu-2404
+    steps:
+    - id: get-release-url
+      name: after_release::post_to_discord::get_release_url
+      run: |
+        if [ "${{ github.event.release.prerelease }}" == "true" ]; then
+            URL="https://zed.dev/releases/preview"
+        else
+            URL="https://zed.dev/releases/stable"
+        fi
+
+        echo "URL=$URL" >> "$GITHUB_OUTPUT"
+      shell: bash -euxo pipefail {0}
+    - id: get-content
+      name: after_release::post_to_discord::get_content
+      uses: 2428392/gh-truncate-string-action@b3ff790d21cf42af3ca7579146eedb93c8fb0757
+      with:
+        stringToTruncate: |
+          📣 Zed [${{ github.event.release.tag_name }}](<${{ steps.get-release-url.outputs.URL }}>) was just released!
+
+          ${{ github.event.release.body }}
+        maxLength: 2000
+        truncationSymbol: '...'
+    - name: after_release::post_to_discord::discord_webhook_action
+      uses: tsickert/discord-webhook@c840d45a03a323fbc3f7507ac7769dbd91bfb164
+      with:
+        webhook-url: ${{ secrets.DISCORD_WEBHOOK_RELEASE_NOTES }}
+        content: ${{ steps.get-content.outputs.string }}
+  publish_winget:
+    runs-on: namespace-profile-2x4-ubuntu-2404
+    steps:
+    - id: set-package-name
+      name: after_release::publish_winget::set_package_name
+      run: |
+        if [ "${{ github.event.release.prerelease }}" == "true" ]; then
+            PACKAGE_NAME=ZedIndustries.Zed.Preview
+        else
+            PACKAGE_NAME=ZedIndustries.Zed
+        fi
+
+        echo "PACKAGE_NAME=$PACKAGE_NAME" >> "$GITHUB_OUTPUT"
+      shell: bash -euxo pipefail {0}
+    - name: after_release::publish_winget::winget_releaser
+      uses: vedantmgoyal9/winget-releaser@19e706d4c9121098010096f9c495a70a7518b30f
+      with:
+        identifier: ${{ steps.set-package-name.outputs.PACKAGE_NAME }}
+        max-versions-to-keep: 5
+        token: ${{ secrets.WINGET_TOKEN }}

.github/workflows/community_release_actions.yml 🔗

@@ -1,93 +0,0 @@
-# IF YOU UPDATE THE NAME OF ANY GITHUB SECRET, YOU MUST CHERRY PICK THE COMMIT
-# TO BOTH STABLE AND PREVIEW CHANNELS
-
-name: Release Actions
-
-on:
-  release:
-    types: [published]
-
-jobs:
-  discord_release:
-    if: github.repository_owner == 'zed-industries'
-    runs-on: ubuntu-latest
-    steps:
-      - name: Get release URL
-        id: get-release-url
-        run: |
-          if [ "${{ github.event.release.prerelease }}" == "true" ]; then
-              URL="https://zed.dev/releases/preview"
-          else
-              URL="https://zed.dev/releases/stable"
-          fi
-
-          echo "URL=$URL" >> "$GITHUB_OUTPUT"
-      - name: Get content
-        uses: 2428392/gh-truncate-string-action@b3ff790d21cf42af3ca7579146eedb93c8fb0757 # v1.4.1
-        id: get-content
-        with:
-          stringToTruncate: |
-            📣 Zed [${{ github.event.release.tag_name }}](<${{ steps.get-release-url.outputs.URL }}>) was just released!
-
-            ${{ github.event.release.body }}
-          maxLength: 2000
-          truncationSymbol: "..."
-      - name: Discord Webhook Action
-        uses: tsickert/discord-webhook@c840d45a03a323fbc3f7507ac7769dbd91bfb164 # v5.3.0
-        with:
-          webhook-url: ${{ secrets.DISCORD_WEBHOOK_RELEASE_NOTES }}
-          content: ${{ steps.get-content.outputs.string }}
-
-  publish-winget:
-    runs-on:
-      - ubuntu-latest
-    steps:
-      - name: Set Package Name
-        id: set-package-name
-        run: |
-          if [ "${{ github.event.release.prerelease }}" == "true" ]; then
-              PACKAGE_NAME=ZedIndustries.Zed.Preview
-          else
-              PACKAGE_NAME=ZedIndustries.Zed
-          fi
-
-          echo "PACKAGE_NAME=$PACKAGE_NAME" >> "$GITHUB_OUTPUT"
-      - uses: vedantmgoyal9/winget-releaser@19e706d4c9121098010096f9c495a70a7518b30f # v2
-        with:
-          identifier: ${{ steps.set-package-name.outputs.PACKAGE_NAME }}
-          max-versions-to-keep: 5
-          token: ${{ secrets.WINGET_TOKEN }}
-
-  send_release_notes_email:
-    if: false && github.repository_owner == 'zed-industries' && !github.event.release.prerelease
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
-        with:
-          fetch-depth: 0
-
-      - name: Check if release was promoted from preview
-        id: check-promotion-from-preview
-        run: |
-          VERSION="${{ github.event.release.tag_name }}"
-          PREVIEW_TAG="${VERSION}-pre"
-
-          if git rev-parse "$PREVIEW_TAG" > /dev/null 2>&1; then
-              echo "was_promoted_from_preview=true" >> "$GITHUB_OUTPUT"
-          else
-              echo "was_promoted_from_preview=false" >> "$GITHUB_OUTPUT"
-          fi
-
-      - name: Send release notes email
-        if: steps.check-promotion-from-preview.outputs.was_promoted_from_preview == 'true'
-        run: |
-          TAG="${{ github.event.release.tag_name }}"
-          cat << 'EOF' > release_body.txt
-          ${{ github.event.release.body }}
-          EOF
-          jq -n --arg tag "$TAG" --rawfile body release_body.txt '{version: $tag, markdown_body: $body}' \
-            > release_data.json
-          curl -X POST "https://zed.dev/api/send_release_notes_email" \
-            -H "Authorization: Bearer ${{ secrets.RELEASE_NOTES_API_TOKEN }}" \
-            -H "Content-Type: application/json" \
-            -d @release_data.json

Cargo.lock 🔗

@@ -20951,6 +20951,7 @@ dependencies = [
  "gh-workflow",
  "indexmap 2.11.4",
  "indoc",
+ "serde",
  "toml 0.8.23",
  "toml_edit 0.22.27",
 ]

tooling/xtask/Cargo.toml 🔗

@@ -17,5 +17,6 @@ clap = { workspace = true, features = ["derive"] }
 toml.workspace = true
 indoc.workspace = true
 indexmap.workspace = true
+serde.workspace = true
 toml_edit.workspace = true
 gh-workflow.workspace = true

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

@@ -3,6 +3,7 @@ use clap::Parser;
 use std::fs;
 use std::path::Path;
 
+mod after_release;
 mod cherry_pick;
 mod compare_perf;
 mod danger;
@@ -33,6 +34,7 @@ pub fn run_workflows(_: GenerateWorkflowArgs) -> Result<()> {
         ("compare_perf.yml", compare_perf::compare_perf()),
         ("run_unit_evals.yml", run_agent_evals::run_unit_evals()),
         ("run_agent_evals.yml", run_agent_evals::run_agent_evals()),
+        ("after_release.yml", after_release::after_release()),
     ];
     fs::create_dir_all(dir)
         .with_context(|| format!("Failed to create directory: {}", dir.display()))?;

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

@@ -0,0 +1,123 @@
+use gh_workflow::*;
+
+use crate::tasks::workflows::{
+    runners,
+    steps::{NamedJob, dependant_job, named},
+    vars::{self, StepOutput},
+};
+
+pub fn after_release() -> Workflow {
+    let refresh_zed_dev = rebuild_releases_page();
+    let post_to_discord = post_to_discord(&[&refresh_zed_dev]);
+    let publish_winget = publish_winget();
+
+    named::workflow()
+        .on(Event::default().release(Release::default().types(vec![ReleaseType::Published])))
+        .add_job(refresh_zed_dev.name, refresh_zed_dev.job)
+        .add_job(post_to_discord.name, post_to_discord.job)
+        .add_job(publish_winget.name, publish_winget.job)
+}
+
+fn rebuild_releases_page() -> NamedJob {
+    named::job(
+        Job::default()
+            .runs_on(runners::LINUX_SMALL)
+            .cond(Expression::new(
+                "github.repository_owner == 'zed-industries'",
+            ))
+            .add_step(named::bash(
+                "curl https://zed.dev/api/revalidate-releases -H \"Authorization: Bearer ${RELEASE_NOTES_API_TOKEN}\"",
+            ).add_env(("RELEASE_NOTES_API_TOKEN", vars::RELEASE_NOTES_API_TOKEN))),
+    )
+}
+
+fn post_to_discord(deps: &[&NamedJob]) -> NamedJob {
+    fn get_release_url() -> Step<Run> {
+        named::bash(indoc::indoc! {r#"
+            if [ "${{ github.event.release.prerelease }}" == "true" ]; then
+                URL="https://zed.dev/releases/preview"
+            else
+                URL="https://zed.dev/releases/stable"
+            fi
+
+            echo "URL=$URL" >> "$GITHUB_OUTPUT"
+        "#})
+        .id("get-release-url")
+    }
+
+    fn get_content() -> Step<Use> {
+        named::uses(
+            "2428392",
+            "gh-truncate-string-action",
+            "b3ff790d21cf42af3ca7579146eedb93c8fb0757", // v1.4.1
+        )
+        .id("get-content")
+        .add_with((
+            "stringToTruncate",
+            indoc::indoc! {r#"
+                📣 Zed [${{ github.event.release.tag_name }}](<${{ steps.get-release-url.outputs.URL }}>) was just released!
+
+                ${{ github.event.release.body }}
+            "#},
+        ))
+        .add_with(("maxLength", 2000))
+        .add_with(("truncationSymbol", "..."))
+    }
+
+    fn discord_webhook_action() -> Step<Use> {
+        named::uses(
+            "tsickert",
+            "discord-webhook",
+            "c840d45a03a323fbc3f7507ac7769dbd91bfb164", // v5.3.0
+        )
+        .add_with(("webhook-url", vars::DISCORD_WEBHOOK_RELEASE_NOTES))
+        .add_with(("content", "${{ steps.get-content.outputs.string }}"))
+    }
+    let job = dependant_job(deps)
+        .runs_on(runners::LINUX_SMALL)
+        .cond(Expression::new(
+            "github.repository_owner == 'zed-industries'",
+        ))
+        .add_step(get_release_url())
+        .add_step(get_content())
+        .add_step(discord_webhook_action());
+    named::job(job)
+}
+
+fn publish_winget() -> NamedJob {
+    fn set_package_name() -> (Step<Run>, StepOutput) {
+        let step = named::bash(indoc::indoc! {r#"
+            if [ "${{ github.event.release.prerelease }}" == "true" ]; then
+                PACKAGE_NAME=ZedIndustries.Zed.Preview
+            else
+                PACKAGE_NAME=ZedIndustries.Zed
+            fi
+
+            echo "PACKAGE_NAME=$PACKAGE_NAME" >> "$GITHUB_OUTPUT"
+        "#})
+        .id("set-package-name");
+
+        let output = StepOutput::new(&step, "PACKAGE_NAME");
+        (step, output)
+    }
+
+    fn winget_releaser(package_name: &StepOutput) -> Step<Use> {
+        named::uses(
+            "vedantmgoyal9",
+            "winget-releaser",
+            "19e706d4c9121098010096f9c495a70a7518b30f", // v2
+        )
+        .add_with(("identifier", package_name.to_string()))
+        .add_with(("max-versions-to-keep", 5))
+        .add_with(("token", vars::WINGET_TOKEN))
+    }
+
+    let (set_package_name, package_name) = set_package_name();
+
+    named::job(
+        Job::default()
+            .runs_on(runners::LINUX_SMALL)
+            .add_step(set_package_name)
+            .add_step(winget_releaser(&package_name)),
+    )
+}

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

@@ -36,6 +36,9 @@ secret!(ZED_SENTRY_MINIDUMP_ENDPOINT);
 secret!(SLACK_APP_ZED_UNIT_EVALS_BOT_TOKEN);
 secret!(ZED_ZIPPY_APP_ID);
 secret!(ZED_ZIPPY_APP_PRIVATE_KEY);
+secret!(DISCORD_WEBHOOK_RELEASE_NOTES);
+secret!(WINGET_TOKEN);
+secret!(RELEASE_NOTES_API_TOKEN);
 
 // todo(ci) make these secrets too...
 var!(AZURE_SIGNING_ACCOUNT_NAME);
@@ -136,6 +139,15 @@ impl StepOutput {
     }
 }
 
+impl serde::Serialize for StepOutput {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        serializer.serialize_str(&self.to_string())
+    }
+}
+
 impl std::fmt::Display for StepOutput {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         write!(f, "${{{{ steps.{}.outputs.{} }}}}", self.step_id, self.name)
@@ -173,6 +185,15 @@ impl std::fmt::Display for Input {
     }
 }
 
+impl serde::Serialize for Input {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        serializer.serialize_str(&self.to_string())
+    }
+}
+
 pub mod assets {
     // NOTE: these asset names also exist in the zed.dev codebase.
     pub const MAC_AARCH64: &str = "Zed-aarch64.dmg";