clean

Ben Kunkle created

Change summary

.github/workflows/deploy_docs.yml                |  74 +-
.github/workflows/release.yml                    |  14 
.github/workflows/run_tests.yml                  |  10 
tooling/xtask/src/tasks/workflows/deploy_docs.rs | 408 ++++++++---------
tooling/xtask/src/tasks/workflows/release.rs     |  51 ++
tooling/xtask/src/tasks/workflows/steps.rs       |   2 
6 files changed, 310 insertions(+), 249 deletions(-)

Detailed changes

.github/workflows/deploy_docs.yml 🔗

@@ -5,14 +5,36 @@ on:
   push:
     branches:
     - main
-    - preview
-    - stable
+  workflow_call:
+    inputs:
+      channel:
+        description: channel
+        type: string
+        default: ''
+      commit_sha:
+        description: commit_sha
+        type: string
+        default: ''
+    secrets:
+      DOCS_AMPLITUDE_API_KEY:
+        description: DOCS_AMPLITUDE_API_KEY
+        required: true
+      CLOUDFLARE_API_TOKEN:
+        description: CLOUDFLARE_API_TOKEN
+        required: true
+      CLOUDFLARE_ACCOUNT_ID:
+        description: CLOUDFLARE_ACCOUNT_ID
+        required: true
   workflow_dispatch:
     inputs:
       channel:
         description: 'Docs channel to deploy: nightly, preview, or stable'
         type: string
-        default: nightly
+        default: ''
+      commit_sha:
+        description: Exact commit SHA to checkout and deploy. Defaults to event SHA when omitted.
+        type: string
+        default: ''
 jobs:
   deploy_docs:
     if: github.repository_owner == 'zed-industries'
@@ -23,31 +45,18 @@ jobs:
       MDBOOK_BOOK__SITE_URL: ${{ steps.resolve-channel.outputs.site_url }}
       DOCS_CHANNEL: ${{ steps.resolve-channel.outputs.channel }}
     steps:
-    - name: steps::checkout_repo
-      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
-      with:
-        clean: false
     - id: resolve-channel
-      name: deploy_docs::resolve_channel_step
+      name: deploy_docs::deploy_docs_job::resolve_channel_step
       run: |
-        if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
-            CHANNEL="${{ inputs.channel }}"
-        else
-            case "${{ github.ref }}" in
-                "refs/heads/main")
-                    CHANNEL="nightly"
-                    ;;
-                "refs/heads/preview")
-                    CHANNEL="preview"
-                    ;;
-                "refs/heads/stable")
-                    CHANNEL="stable"
-                    ;;
-                *)
-                    echo "::error::Unsupported ref for docs deploy: ${{ github.ref }}"
-                    exit 1
-                    ;;
-            esac
+        CHANNEL="${{ inputs.channel }}"
+
+        if [ -z "$CHANNEL" ]; then
+            if [ "${{ github.ref }}" = "refs/heads/main" ]; then
+                CHANNEL="nightly"
+            else
+                echo "::error::channel input is required when ref is not main."
+                exit 1
+            fi
         fi
 
         case "$CHANNEL" in
@@ -72,6 +81,11 @@ jobs:
         echo "channel=$CHANNEL" >> "$GITHUB_OUTPUT"
         echo "site_url=$SITE_URL" >> "$GITHUB_OUTPUT"
         echo "project_name=$PROJECT_NAME" >> "$GITHUB_OUTPUT"
+    - name: steps::checkout_repo
+      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+      with:
+        clean: false
+        ref: ${{ inputs.commit_sha != '' && inputs.commit_sha || github.sha }}
     - name: steps::setup_cargo_config
       run: |
         mkdir -p ./../.cargo
@@ -109,25 +123,25 @@ jobs:
         args: --no-progress --exclude '^http' 'target/deploy/docs'
         fail: true
         jobSummary: false
-    - name: deploy_docs::pages_deploy_step
+    - name: deploy_docs::docs_deploy_steps::deploy_to_cf_pages
       uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65
       with:
         apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
         accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
         command: pages deploy target/deploy --project-name=${{ steps.resolve-channel.outputs.project_name }}
-    - name: deploy_docs::deploy_install_script
+    - name: deploy_docs::docs_deploy_steps::upload_install_script
       uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65
       with:
         apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
         accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
         command: r2 object put -f script/install.sh zed-open-source-website-assets/install.sh
-    - name: deploy_docs::deploy_docs_worker
+    - name: deploy_docs::docs_deploy_steps::deploy_docs_worker
       uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65
       with:
         apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
         accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
         command: deploy .cloudflare/docs-proxy/src/worker.js
-    - name: deploy_docs::upload_wrangler_logs
+    - name: deploy_docs::docs_deploy_steps::upload_wrangler_logs
       if: always()
       uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
       with:

.github/workflows/release.yml 🔗

@@ -619,6 +619,20 @@ jobs:
       run: gh release edit "$GITHUB_REF_NAME" --repo=zed-industries/zed --draft=false
       env:
         GITHUB_TOKEN: ${{ steps.get-app-token.outputs.token }}
+  deploy_docs:
+    needs:
+    - validate_release_assets
+    - create_draft_release
+    permissions:
+      contents: read
+    uses: zed-industries/zed/.github/workflows/deploy_docs.yml@main
+    secrets:
+      DOCS_AMPLITUDE_API_KEY: ${{ secrets.DOCS_AMPLITUDE_API_KEY }}
+      CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+      CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
+    with:
+      channel: ${{ endsWith(github.ref_name, '-pre') && 'preview' || 'stable' }}
+      commit_sha: ${{ github.sha }}
   push_release_update_notification:
     needs:
     - create_draft_release

.github/workflows/run_tests.yml 🔗

@@ -505,19 +505,13 @@ jobs:
     runs-on: namespace-profile-16x32-ubuntu-2204
     env:
       DOCS_AMPLITUDE_API_KEY: ${{ secrets.DOCS_AMPLITUDE_API_KEY }}
-      MDBOOK_BOOK__SITE_URL: ${{ steps.resolve-channel.outputs.site_url }}
-      DOCS_CHANNEL: ${{ steps.resolve-channel.outputs.channel }}
+      MDBOOK_BOOK__SITE_URL: /docs/
+      DOCS_CHANNEL: stable
     steps:
     - name: steps::checkout_repo
       uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
       with:
         clean: false
-    - id: resolve-channel
-      name: deploy_docs::static_channel_resolution_step
-      run: |
-        echo "channel=stable" >> "$GITHUB_OUTPUT"
-        echo "site_url=/docs/" >> "$GITHUB_OUTPUT"
-        echo "project_name=docs" >> "$GITHUB_OUTPUT"
     - name: steps::setup_cargo_config
       run: |
         mkdir -p ./../.cargo

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

@@ -1,4 +1,7 @@
-use gh_workflow::{Event, Expression, Job, Push, Run, Step, Use, Workflow, WorkflowDispatch};
+use gh_workflow::{
+    Event, Expression, Job, Push, Run, Step, Use, Workflow, WorkflowCall, WorkflowCallSecret,
+    WorkflowDispatch,
+};
 
 use crate::tasks::workflows::{
     runners,
@@ -6,6 +9,8 @@ use crate::tasks::workflows::{
     vars::{self, StepOutput, WorkflowInput},
 };
 
+const BUILD_OUTPUT_DIR: &str = "target/deploy";
+
 pub(crate) enum DocsChannel {
     Nightly,
     Preview,
@@ -59,182 +64,26 @@ pub(crate) fn install_mdbook() -> Step<Use> {
 }
 
 pub(crate) fn build_docs_book() -> Step<Run> {
-    named::bash(indoc::indoc! {r#"
-        mkdir -p target/deploy
-        mdbook build ./docs --dest-dir=../target/deploy/docs/
+    named::bash(indoc::formatdoc! {r#"
+        mkdir -p {BUILD_OUTPUT_DIR}
+        mdbook build ./docs --dest-dir=../{BUILD_OUTPUT_DIR}/docs/
     "#})
 }
 
-fn pages_deploy_step(project_name: &StepOutput) -> Step<Use> {
-    named::uses(
-        "cloudflare",
-        "wrangler-action",
-        "da0e0dfe58b7a431659754fdf3f186c529afbe65",
-    ) // v3
-    .add_with(("apiToken", vars::CLOUDFLARE_API_TOKEN))
-    .add_with(("accountId", vars::CLOUDFLARE_ACCOUNT_ID))
-    .add_with((
-        "command",
-        format!(
-            "pages deploy target/deploy --project-name=${{{{ {} }}}}",
-            project_name.expr()
-        ),
-    ))
-}
-
-fn deploy_install_script() -> Step<Use> {
-    named::uses(
-        "cloudflare",
-        "wrangler-action",
-        "da0e0dfe58b7a431659754fdf3f186c529afbe65",
-    ) // v3
-    .add_with(("apiToken", vars::CLOUDFLARE_API_TOKEN))
-    .add_with(("accountId", vars::CLOUDFLARE_ACCOUNT_ID))
-    .add_with((
-        "command",
-        "r2 object put -f script/install.sh zed-open-source-website-assets/install.sh",
-    ))
-}
-
-fn deploy_docs_worker() -> Step<Use> {
-    named::uses(
-        "cloudflare",
-        "wrangler-action",
-        "da0e0dfe58b7a431659754fdf3f186c529afbe65",
-    ) // v3
-    .add_with(("apiToken", vars::CLOUDFLARE_API_TOKEN))
-    .add_with(("accountId", vars::CLOUDFLARE_ACCOUNT_ID))
-    .add_with(("command", "deploy .cloudflare/docs-proxy/src/worker.js"))
-}
-
-fn upload_wrangler_logs() -> Step<Use> {
-    named::uses(
-        "actions",
-        "upload-artifact",
-        "ea165f8d65b6e75b540449e92b4886f43607fa02",
-    ) // v4
-    .if_condition(Expression::new("always()"))
-    .add_with(("name", "wrangler_logs"))
-    .add_with(("path", "/home/runner/.config/.wrangler/logs/"))
-}
-
-fn resolve_channel_step(
-    channel_input: &WorkflowInput,
-) -> (Step<Run>, StepOutput, StepOutput, StepOutput) {
-    let step = named::bash(format!(
-        indoc::indoc! {r#"
-            if [ "${{{{ github.event_name }}}}" = "workflow_dispatch" ]; then
-                CHANNEL="${{{{ {dispatch_channel} }}}}"
-            else
-                case "${{{{ github.ref }}}}" in
-                    "refs/heads/main")
-                        CHANNEL="nightly"
-                        ;;
-                    "refs/heads/preview")
-                        CHANNEL="preview"
-                        ;;
-                    "refs/heads/stable")
-                        CHANNEL="stable"
-                        ;;
-                    *)
-                        echo "::error::Unsupported ref for docs deploy: ${{{{ github.ref }}}}"
-                        exit 1
-                        ;;
-                esac
-            fi
-
-            case "$CHANNEL" in
-                "nightly")
-                    SITE_URL="{nightly_site_url}"
-                    PROJECT_NAME="{nightly_project_name}"
-                    ;;
-                "preview")
-                    SITE_URL="{preview_site_url}"
-                    PROJECT_NAME="{preview_project_name}"
-                    ;;
-                "stable")
-                    SITE_URL="{stable_site_url}"
-                    PROJECT_NAME="{stable_project_name}"
-                    ;;
-                *)
-                    echo "::error::Invalid docs channel '$CHANNEL'. Expected one of: nightly, preview, stable."
-                    exit 1
-                    ;;
-            esac
-
-            echo "channel=$CHANNEL" >> "$GITHUB_OUTPUT"
-            echo "site_url=$SITE_URL" >> "$GITHUB_OUTPUT"
-            echo "project_name=$PROJECT_NAME" >> "$GITHUB_OUTPUT"
-        "#},
-        dispatch_channel = channel_input.expr(),
-        nightly_site_url = DocsChannel::Nightly.site_url(),
-        preview_site_url = DocsChannel::Preview.site_url(),
-        stable_site_url = DocsChannel::Stable.site_url(),
-        nightly_project_name = DocsChannel::Nightly.project_name(),
-        preview_project_name = DocsChannel::Preview.project_name(),
-        stable_project_name = DocsChannel::Stable.project_name(),
-    ))
-    .id("resolve-channel");
-
-    let channel = StepOutput::new(&step, "channel");
-    let site_url = StepOutput::new(&step, "site_url");
-    let project_name = StepOutput::new(&step, "project_name");
-    (step, channel, site_url, project_name)
-}
-
-fn static_channel_resolution_step(
-    channel: DocsChannel,
-) -> (Step<Run>, StepOutput, StepOutput, StepOutput) {
-    let (channel_name, site_url, project_name) = match channel {
-        DocsChannel::Nightly => (
-            DocsChannel::Nightly.channel_name(),
-            DocsChannel::Nightly.site_url(),
-            DocsChannel::Nightly.project_name(),
-        ),
-        DocsChannel::Preview => (
-            DocsChannel::Preview.channel_name(),
-            DocsChannel::Preview.site_url(),
-            DocsChannel::Preview.project_name(),
-        ),
-        DocsChannel::Stable => (
-            DocsChannel::Stable.channel_name(),
-            DocsChannel::Stable.site_url(),
-            DocsChannel::Stable.project_name(),
-        ),
-    };
-
-    let step = named::bash(format!(
-        indoc::indoc! {r#"
-            echo "channel={channel_name}" >> "$GITHUB_OUTPUT"
-            echo "site_url={site_url}" >> "$GITHUB_OUTPUT"
-            echo "project_name={project_name}" >> "$GITHUB_OUTPUT"
-        "#},
-        channel_name = channel_name,
-        site_url = site_url,
-        project_name = project_name,
-    ))
-    .id("resolve-channel");
-
-    let channel = StepOutput::new(&step, "channel");
-    let site_url = StepOutput::new(&step, "site_url");
-    let project_name = StepOutput::new(&step, "project_name");
-    (step, channel, site_url, project_name)
-}
-
 fn docs_build_steps(
     job: Job,
-    resolved_channel_step: Step<Run>,
-    channel: &StepOutput,
-    site_url: &StepOutput,
-    project_name: &StepOutput,
-    include_deploy_steps: bool,
+    checkout_ref: Option<String>,
+    docs_channel: impl Into<String>,
+    site_url: impl Into<String>,
 ) -> Job {
-    let mut job = job
-        .add_env(("DOCS_AMPLITUDE_API_KEY", vars::DOCS_AMPLITUDE_API_KEY))
-        .add_step(steps::checkout_repo())
-        .add_step(resolved_channel_step)
-        .add_env(("MDBOOK_BOOK__SITE_URL", site_url.to_string()))
-        .add_env(("DOCS_CHANNEL", channel.to_string()))
+    job.add_env(("DOCS_AMPLITUDE_API_KEY", vars::DOCS_AMPLITUDE_API_KEY))
+        .add_step(
+            steps::checkout_repo().when_some(checkout_ref, |step, checkout_ref| {
+                step.with_ref(checkout_ref)
+            }),
+        )
+        .add_env(("MDBOOK_BOOK__SITE_URL", site_url.into()))
+        .add_env(("DOCS_CHANNEL", docs_channel.into()))
         .runs_on(runners::LINUX_XL)
         .add_step(steps::setup_cargo_config(runners::Platform::Linux))
         .add_step(steps::cache_rust_dependencies_namespace())
@@ -243,72 +92,211 @@ fn docs_build_steps(
         .add_step(lychee_link_check("./docs/src/**/*"))
         .add_step(install_mdbook())
         .add_step(build_docs_book())
-        .add_step(lychee_link_check("target/deploy/docs"));
+        .add_step(lychee_link_check(&format!("{BUILD_OUTPUT_DIR}/docs")))
+}
+
+fn docs_deploy_steps(job: Job, project_name: &StepOutput) -> Job {
+    fn deploy_to_cf_pages(project_name: &StepOutput) -> Step<Use> {
+        named::uses(
+            "cloudflare",
+            "wrangler-action",
+            "da0e0dfe58b7a431659754fdf3f186c529afbe65",
+        ) // v3
+        .add_with(("apiToken", vars::CLOUDFLARE_API_TOKEN))
+        .add_with(("accountId", vars::CLOUDFLARE_ACCOUNT_ID))
+        .add_with((
+            "command",
+            format!(
+                "pages deploy {BUILD_OUTPUT_DIR} --project-name=${{{{ {} }}}}",
+                project_name.expr()
+            ),
+        ))
+    }
 
-    if include_deploy_steps {
-        job = job
-            .add_step(pages_deploy_step(project_name))
-            .add_step(deploy_install_script())
-            .add_step(deploy_docs_worker())
-            .add_step(upload_wrangler_logs());
+    fn upload_install_script() -> Step<Use> {
+        named::uses(
+            "cloudflare",
+            "wrangler-action",
+            "da0e0dfe58b7a431659754fdf3f186c529afbe65",
+        ) // v3
+        .add_with(("apiToken", vars::CLOUDFLARE_API_TOKEN))
+        .add_with(("accountId", vars::CLOUDFLARE_ACCOUNT_ID))
+        .add_with((
+            "command",
+            "r2 object put -f script/install.sh zed-open-source-website-assets/install.sh",
+        ))
     }
 
-    job
+    fn deploy_docs_worker() -> Step<Use> {
+        named::uses(
+            "cloudflare",
+            "wrangler-action",
+            "da0e0dfe58b7a431659754fdf3f186c529afbe65",
+        ) // v3
+        .add_with(("apiToken", vars::CLOUDFLARE_API_TOKEN))
+        .add_with(("accountId", vars::CLOUDFLARE_ACCOUNT_ID))
+        .add_with(("command", "deploy .cloudflare/docs-proxy/src/worker.js"))
+    }
+
+    fn upload_wrangler_logs() -> Step<Use> {
+        named::uses(
+            "actions",
+            "upload-artifact",
+            "ea165f8d65b6e75b540449e92b4886f43607fa02",
+        ) // v4
+        .if_condition(Expression::new("always()"))
+        .add_with(("name", "wrangler_logs"))
+        .add_with(("path", "/home/runner/.config/.wrangler/logs/"))
+    }
+
+    job.add_step(deploy_to_cf_pages(project_name))
+        .add_step(upload_install_script())
+        .add_step(deploy_docs_worker())
+        .add_step(upload_wrangler_logs())
 }
 
 pub(crate) fn check_docs() -> NamedJob {
-    let (resolve_step, channel, site_url, project_name) =
-        static_channel_resolution_step(DocsChannel::Stable);
-
     NamedJob {
         name: "check_docs".to_owned(),
         job: docs_build_steps(
             release_job(&[]),
-            resolve_step,
-            &channel,
-            &site_url,
-            &project_name,
-            false,
+            None,
+            DocsChannel::Stable.channel_name(),
+            DocsChannel::Stable.site_url(),
         ),
     }
 }
 
-pub(crate) fn deploy_docs_job(channel_input: &WorkflowInput) -> NamedJob {
+pub(crate) fn deploy_docs_job(
+    channel_input: &WorkflowInput,
+    commit_sha_input: &WorkflowInput,
+) -> NamedJob {
+    fn resolve_channel_step(
+        channel_input: &WorkflowInput,
+    ) -> (Step<Run>, StepOutput, StepOutput, StepOutput) {
+        let step = named::bash(format!(
+            indoc::indoc! {r#"
+                CHANNEL="${{{{ {channel_input_expr} }}}}"
+
+                if [ -z "$CHANNEL" ]; then
+                    if [ "${{{{ github.ref }}}}" = "refs/heads/main" ]; then
+                        CHANNEL="nightly"
+                    else
+                        echo "::error::channel input is required when ref is not main."
+                        exit 1
+                    fi
+                fi
+
+                case "$CHANNEL" in
+                    "nightly")
+                        SITE_URL="{nightly_site_url}"
+                        PROJECT_NAME="{nightly_project_name}"
+                        ;;
+                    "preview")
+                        SITE_URL="{preview_site_url}"
+                        PROJECT_NAME="{preview_project_name}"
+                        ;;
+                    "stable")
+                        SITE_URL="{stable_site_url}"
+                        PROJECT_NAME="{stable_project_name}"
+                        ;;
+                    *)
+                        echo "::error::Invalid docs channel '$CHANNEL'. Expected one of: nightly, preview, stable."
+                        exit 1
+                        ;;
+                esac
+
+                echo "channel=$CHANNEL" >> "$GITHUB_OUTPUT"
+                echo "site_url=$SITE_URL" >> "$GITHUB_OUTPUT"
+                echo "project_name=$PROJECT_NAME" >> "$GITHUB_OUTPUT"
+            "#},
+            channel_input_expr = channel_input.expr(),
+            nightly_site_url = DocsChannel::Nightly.site_url(),
+            preview_site_url = DocsChannel::Preview.site_url(),
+            stable_site_url = DocsChannel::Stable.site_url(),
+            nightly_project_name = DocsChannel::Nightly.project_name(),
+            preview_project_name = DocsChannel::Preview.project_name(),
+            stable_project_name = DocsChannel::Stable.project_name(),
+        ))
+        .id("resolve-channel");
+
+        let channel = StepOutput::new(&step, "channel");
+        let site_url = StepOutput::new(&step, "site_url");
+        let project_name = StepOutput::new(&step, "project_name");
+        (step, channel, site_url, project_name)
+    }
     let (resolve_step, channel, site_url, project_name) = resolve_channel_step(channel_input);
 
     NamedJob {
         name: "deploy_docs".to_owned(),
-        job: docs_build_steps(
-            release_job(&[])
-                .name("Build and Deploy Docs")
-                .cond(Expression::new(
-                    "github.repository_owner == 'zed-industries'",
+        job: docs_deploy_steps(
+            docs_build_steps(
+                release_job(&[])
+                    .name("Build and Deploy Docs")
+                    .cond(Expression::new(
+                        "github.repository_owner == 'zed-industries'",
+                    ))
+                    .add_step(resolve_step),
+                Some(format!(
+                    "${{{{ {} != '' && {} || github.sha }}}}",
+                    commit_sha_input.expr(),
+                    commit_sha_input.expr()
                 )),
-            resolve_step,
-            &channel,
-            &site_url,
+                channel.to_string(),
+                site_url.to_string(),
+            ),
             &project_name,
-            true,
         ),
     }
 }
 
 pub(crate) fn deploy_docs() -> Workflow {
-    let channel = WorkflowInput::string("channel", Some("nightly".to_string()))
+    let channel = WorkflowInput::string("channel", Some(String::new()))
         .description("Docs channel to deploy: nightly, preview, or stable");
-
-    let deploy_docs = deploy_docs_job(&channel);
+    let commit_sha = WorkflowInput::string("commit_sha", Some(String::new())).description(
+        "Exact commit SHA to checkout and deploy. Defaults to event SHA when omitted.",
+    );
+    let deploy_docs = deploy_docs_job(&channel, &commit_sha);
 
     named::workflow()
-        .on(Event::default()
-            .push(
-                Push::default()
-                    .add_branch("main")
-                    .add_branch("preview")
-                    .add_branch("stable"),
-            )
-            .workflow_dispatch(
-                WorkflowDispatch::default().add_input(channel.name, channel.input()),
-            ))
+        .add_event(
+            Event::default()
+                .push(Push::default().add_branch("main"))
+                .workflow_dispatch(
+                    WorkflowDispatch::default()
+                        .add_input(channel.name, channel.input())
+                        .add_input(commit_sha.name, commit_sha.input()),
+                ),
+        )
+        .add_event(
+            Event::default().workflow_call(
+                WorkflowCall::default()
+                    .add_input(channel.name, channel.call_input())
+                    .add_input(commit_sha.name, commit_sha.call_input())
+                    .secrets([
+                        (
+                            "DOCS_AMPLITUDE_API_KEY".to_owned(),
+                            WorkflowCallSecret {
+                                description: "DOCS_AMPLITUDE_API_KEY".to_owned(),
+                                required: true,
+                            },
+                        ),
+                        (
+                            "CLOUDFLARE_API_TOKEN".to_owned(),
+                            WorkflowCallSecret {
+                                description: "CLOUDFLARE_API_TOKEN".to_owned(),
+                                required: true,
+                            },
+                        ),
+                        (
+                            "CLOUDFLARE_ACCOUNT_ID".to_owned(),
+                            WorkflowCallSecret {
+                                description: "CLOUDFLARE_ACCOUNT_ID".to_owned(),
+                                required: true,
+                            },
+                        ),
+                    ]),
+            ),
+        )
         .add_job(deploy_docs.name, deploy_docs.job)
 }

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

@@ -1,4 +1,7 @@
-use gh_workflow::{Event, Expression, Push, Run, Step, Use, Workflow, ctx::Context};
+use gh_workflow::{
+    Event, Expression, Input, Job, Level, Permissions, Push, Run, Step, Use, UsesJob, Workflow,
+    ctx::Context,
+};
 use indoc::formatdoc;
 
 use crate::tasks::workflows::{
@@ -60,6 +63,7 @@ pub(crate) fn release() -> Workflow {
     let validate_release_assets = validate_release_assets(&[&upload_release_assets]);
 
     let auto_release_preview = auto_release_preview(&[&validate_release_assets]);
+    let deploy_docs = deploy_docs(&[&validate_release_assets], &create_draft_release);
 
     let test_jobs = [
         &macos_tests,
@@ -101,6 +105,7 @@ pub(crate) fn release() -> Workflow {
         .add_job(upload_release_assets.name, upload_release_assets.job)
         .add_job(validate_release_assets.name, validate_release_assets.job)
         .add_job(auto_release_preview.name, auto_release_preview.job)
+        .add_job(deploy_docs.name, deploy_docs.job)
         .add_job(push_slack_notification.name, push_slack_notification.job)
 }
 
@@ -197,6 +202,50 @@ fn auto_release_preview(deps: &[&NamedJob]) -> NamedJob {
     )
 }
 
+fn deploy_docs(deps: &[&NamedJob], create_draft_release: &NamedJob) -> NamedJob<UsesJob> {
+    let job = Job::default()
+        .needs(
+            deps.iter()
+                .map(|job| job.name.clone())
+                .chain(std::iter::once(create_draft_release.name.clone()))
+                .collect::<Vec<_>>(),
+        )
+        .permissions(Permissions::default().contents(Level::Read))
+        .uses(
+            "zed-industries",
+            "zed",
+            ".github/workflows/deploy_docs.yml",
+            "main",
+        )
+        .with(
+            Input::default()
+                .add(
+                    "channel",
+                    "${{ endsWith(github.ref_name, '-pre') && 'preview' || 'stable' }}",
+                )
+                .add("commit_sha", "${{ github.sha }}"),
+        )
+        .secrets(indexmap::IndexMap::from([
+            (
+                "DOCS_AMPLITUDE_API_KEY".to_owned(),
+                vars::DOCS_AMPLITUDE_API_KEY.to_owned(),
+            ),
+            (
+                "CLOUDFLARE_API_TOKEN".to_owned(),
+                vars::CLOUDFLARE_API_TOKEN.to_owned(),
+            ),
+            (
+                "CLOUDFLARE_ACCOUNT_ID".to_owned(),
+                vars::CLOUDFLARE_ACCOUNT_ID.to_owned(),
+            ),
+        ]));
+
+    NamedJob {
+        name: "deploy_docs".to_owned(),
+        job,
+    }
+}
+
 pub(crate) fn download_workflow_artifacts() -> Step<Use> {
     named::uses(
         "actions",