@@ -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)
}
@@ -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",