after_release.rs

  1use gh_workflow::*;
  2
  3use crate::tasks::workflows::{
  4    release::{self, notify_on_failure},
  5    runners,
  6    steps::{CommonJobConditions, NamedJob, checkout_repo, dependant_job, named},
  7    vars::{self, StepOutput, WorkflowInput},
  8};
  9
 10const TAG_NAME_ENV: &str = "${{ github.event.release.tag_name || inputs.tag_name }}";
 11const IS_PRERELEASE_ENV: &str = "${{ github.event.release.prerelease || inputs.prerelease }}";
 12const TAG_NAME: &str = "${{ env.TAG_NAME }}";
 13const RELEASE_BODY: &str = "${{ github.event.release.body || inputs.body }}";
 14const DOCS_CHANNEL: &str =
 15    "${{ (github.event.release.prerelease || inputs.prerelease) && 'preview' || 'stable' }}";
 16
 17pub fn after_release() -> Workflow {
 18    let tag_name = WorkflowInput::string("tag_name", None);
 19    let prerelease = WorkflowInput::bool("prerelease", None);
 20    let body = WorkflowInput::string("body", Some(String::new()));
 21
 22    let refresh_zed_dev = rebuild_releases_page();
 23    let deploy_docs = deploy_docs();
 24    let post_to_discord = post_to_discord(&[&refresh_zed_dev]);
 25    let publish_winget = publish_winget();
 26    let create_sentry_release = create_sentry_release();
 27    let notify_on_failure = {
 28        let notify_on_failure = notify_on_failure(&[
 29            &refresh_zed_dev,
 30            &post_to_discord,
 31            &publish_winget,
 32            &create_sentry_release,
 33        ]);
 34        NamedJob {
 35            name: notify_on_failure.name,
 36            job: notify_on_failure.job.add_need(deploy_docs.name.clone()),
 37        }
 38    };
 39
 40    named::workflow()
 41        .add_env(("TAG_NAME", TAG_NAME_ENV))
 42        .add_env(("IS_PRERELEASE", IS_PRERELEASE_ENV))
 43        .on(Event::default()
 44            .release(Release::default().types(vec![ReleaseType::Published]))
 45            .workflow_dispatch(
 46                WorkflowDispatch::default()
 47                    .add_input(tag_name.name, tag_name.input())
 48                    .add_input(prerelease.name, prerelease.input())
 49                    .add_input(body.name, body.input()),
 50            ))
 51        .add_job(refresh_zed_dev.name, refresh_zed_dev.job)
 52        .add_job(deploy_docs.name, deploy_docs.job)
 53        .add_job(post_to_discord.name, post_to_discord.job)
 54        .add_job(publish_winget.name, publish_winget.job)
 55        .add_job(create_sentry_release.name, create_sentry_release.job)
 56        .add_job(notify_on_failure.name, notify_on_failure.job)
 57}
 58
 59fn deploy_docs() -> NamedJob<UsesJob> {
 60    let job = Job::default()
 61        .cond(Expression::new(
 62            "github.repository_owner == 'zed-industries'",
 63        ))
 64        .permissions(Permissions::default().contents(Level::Read))
 65        .uses(
 66            "zed-industries",
 67            "zed",
 68            ".github/workflows/deploy_docs.yml",
 69            "main",
 70        )
 71        .with(
 72            Input::default()
 73                .add("channel", DOCS_CHANNEL)
 74                .add("checkout_ref", TAG_NAME_ENV),
 75        )
 76        .secrets(indexmap::IndexMap::from([
 77            (
 78                "DOCS_AMPLITUDE_API_KEY".to_owned(),
 79                vars::DOCS_AMPLITUDE_API_KEY.to_owned(),
 80            ),
 81            (
 82                "CLOUDFLARE_API_TOKEN".to_owned(),
 83                vars::CLOUDFLARE_API_TOKEN.to_owned(),
 84            ),
 85            (
 86                "CLOUDFLARE_ACCOUNT_ID".to_owned(),
 87                vars::CLOUDFLARE_ACCOUNT_ID.to_owned(),
 88            ),
 89        ]));
 90
 91    NamedJob {
 92        name: "deploy_docs".to_owned(),
 93        job,
 94    }
 95}
 96
 97fn rebuild_releases_page() -> NamedJob {
 98    fn refresh_cloud_releases() -> Step<Run> {
 99        named::bash("curl -fX POST \"https://cloud.zed.dev/releases/refresh?expect_tag=$TAG_NAME\"")
100    }
101
102    fn redeploy_zed_dev() -> Step<Run> {
103        named::bash("./script/redeploy-vercel").add_env(("VERCEL_TOKEN", vars::VERCEL_TOKEN))
104    }
105
106    named::job(
107        Job::default()
108            .runs_on(runners::LINUX_SMALL)
109            .with_repository_owner_guard()
110            .add_step(refresh_cloud_releases())
111            .add_step(checkout_repo())
112            .add_step(redeploy_zed_dev()),
113    )
114}
115
116fn post_to_discord(deps: &[&NamedJob]) -> NamedJob {
117    fn get_release_url() -> Step<Run> {
118        named::bash(
119            r#"if [ "$IS_PRERELEASE" == "true" ]; then
120    URL="https://zed.dev/releases/preview"
121else
122    URL="https://zed.dev/releases/stable"
123fi
124
125echo "URL=$URL" >> "$GITHUB_OUTPUT"
126"#,
127        )
128        .id("get-release-url")
129    }
130
131    fn get_content() -> Step<Use> {
132        named::uses(
133            "2428392",
134            "gh-truncate-string-action",
135            "b3ff790d21cf42af3ca7579146eedb93c8fb0757", // v1.4.1
136        )
137        .id("get-content")
138        .add_with((
139            "stringToTruncate",
140            format!(
141                "📣 Zed [{TAG_NAME}](<${{{{ steps.get-release-url.outputs.URL }}}}>)  was just released!\n\n{RELEASE_BODY}\n"
142            ),
143        ))
144        .add_with(("maxLength", 2000))
145        .add_with(("truncationSymbol", "..."))
146    }
147
148    fn discord_webhook_action() -> Step<Use> {
149        named::uses(
150            "tsickert",
151            "discord-webhook",
152            "c840d45a03a323fbc3f7507ac7769dbd91bfb164", // v5.3.0
153        )
154        .add_with(("webhook-url", vars::DISCORD_WEBHOOK_RELEASE_NOTES))
155        .add_with(("content", "${{ steps.get-content.outputs.string }}"))
156    }
157    let job = dependant_job(deps)
158        .runs_on(runners::LINUX_SMALL)
159        .with_repository_owner_guard()
160        .add_step(get_release_url())
161        .add_step(get_content())
162        .add_step(discord_webhook_action());
163    named::job(job)
164}
165
166fn publish_winget() -> NamedJob {
167    fn sync_winget_pkgs_fork() -> Step<Run> {
168        named::pwsh(indoc::indoc! {r#"
169            $headers = @{
170                "Authorization" = "Bearer $env:WINGET_TOKEN"
171                "Accept" = "application/vnd.github+json"
172                "X-GitHub-Api-Version" = "2022-11-28"
173            }
174            $body = @{ branch = "master" } | ConvertTo-Json
175            $uri = "https://api.github.com/repos/$env:GITHUB_REPOSITORY_OWNER/winget-pkgs/merge-upstream"
176            try {
177                Invoke-RestMethod -Uri $uri -Method Post -Headers $headers -Body $body -ContentType "application/json"
178                Write-Host "Successfully synced winget-pkgs fork"
179            } catch {
180                Write-Host "Fork sync response: $_"
181                Write-Host "Continuing anyway - fork may already be up to date"
182            }
183        "#})
184        .add_env(("WINGET_TOKEN", vars::WINGET_TOKEN))
185    }
186
187    fn set_package_name() -> (Step<Run>, StepOutput) {
188        let script = r#"if ($env:IS_PRERELEASE -eq "true") {
189    $PACKAGE_NAME = "ZedIndustries.Zed.Preview"
190} else {
191    $PACKAGE_NAME = "ZedIndustries.Zed"
192}
193
194echo "PACKAGE_NAME=$PACKAGE_NAME" >> $env:GITHUB_OUTPUT
195"#;
196        let step = named::pwsh(script).id("set-package-name");
197
198        let output = StepOutput::new(&step, "PACKAGE_NAME");
199        (step, output)
200    }
201
202    fn winget_releaser(package_name: &StepOutput) -> Step<Use> {
203        named::uses(
204            "vedantmgoyal9",
205            "winget-releaser",
206            "19e706d4c9121098010096f9c495a70a7518b30f", // v2
207        )
208        .add_with(("identifier", package_name.to_string()))
209        .add_with(("release-tag", TAG_NAME))
210        .add_with(("max-versions-to-keep", 5))
211        .add_with(("token", vars::WINGET_TOKEN))
212    }
213
214    let (set_package_name, package_name) = set_package_name();
215
216    named::job(
217        Job::default()
218            .runs_on(runners::WINDOWS_DEFAULT)
219            .add_step(sync_winget_pkgs_fork())
220            .add_step(set_package_name)
221            .add_step(winget_releaser(&package_name)),
222    )
223}
224
225fn create_sentry_release() -> NamedJob {
226    let job = Job::default()
227        .runs_on(runners::LINUX_SMALL)
228        .with_repository_owner_guard()
229        .add_step(checkout_repo())
230        .add_step(release::create_sentry_release());
231    named::job(job)
232}