Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T05:37:27.742Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

workflows.rs

254 lines · 8.3 KB · rust
1use anyhow::{Context, Result};
2use clap::Parser;
3use gh_workflow::Workflow;
4use std::fs;
5use std::path::{Path, PathBuf};
6use strum::IntoEnumIterator;
7
8use crate::tasks::workflow_checks::{self};
9
10mod after_release;
11mod autofix_pr;
12mod bump_patch_version;
13mod bump_zed_version;
14mod cherry_pick;
15mod compliance_check;
16mod danger;
17mod deploy_collab;
18mod deploy_docs;
19mod extension_auto_bump;
20mod extension_bump;
21mod extension_tests;
22mod extension_workflow_rollout;
23mod extensions;
24mod nix_build;
25mod publish_extension_cli;
26mod release_nightly;
27mod run_bundling;
28
29mod release;
30mod run_tests;
31mod runners;
32mod steps;
33mod vars;
34
35#[derive(Clone)]
36pub(crate) struct GitSha(String);
37
38impl AsRef<str> for GitSha {
39    fn as_ref(&self) -> &str {
40        &self.0
41    }
42}
43
44#[allow(
45    clippy::disallowed_methods,
46    reason = "This runs only in a CLI environment"
47)]
48fn parse_ref(value: &str) -> Result<GitSha, String> {
49    const GIT_SHA_LENGTH: usize = 40;
50    (value.len() == GIT_SHA_LENGTH)
51        .then_some(value)
52        .ok_or_else(|| {
53            format!(
54                "Git SHA has wrong length! \
55                Only SHAs with a full length of {GIT_SHA_LENGTH} are supported, found {len} characters.",
56                len = value.len()
57            )
58        })
59        .and_then(|value| {
60            let mut tmp = [0; 4];
61            value
62                .chars()
63                .all(|char| u16::from_str_radix(char.encode_utf8(&mut tmp), 16).is_ok()).then_some(value)
64                .ok_or_else(|| "Not a valid Git SHA".to_owned())
65        })
66        .and_then(|sha| {
67           std::process::Command::new("git")
68               .args([
69                   "rev-parse",
70                   "--quiet",
71                   "--verify",
72                   &format!("{sha}^{{commit}}")
73               ])
74               .output()
75               .map_err(|_| "Failed to spawn Git command to verify SHA".to_owned())
76               .and_then(|output|
77                   output
78                       .status.success()
79                       .then_some(sha)
80                       .ok_or_else(|| format!("SHA {sha} is not a valid Git SHA within this repository!")))
81        }).map(|sha| GitSha(sha.to_owned()))
82}
83
84#[derive(Parser)]
85pub(crate) struct GenerateWorkflowArgs {
86    #[arg(value_parser = parse_ref)]
87    /// The Git SHA to use when invoking this
88    pub(crate) sha: Option<GitSha>,
89}
90
91enum WorkflowSource {
92    Contextless(fn() -> Workflow),
93    WithContext(fn(&GenerateWorkflowArgs) -> Workflow),
94}
95
96struct WorkflowFile {
97    source: WorkflowSource,
98    r#type: WorkflowType,
99}
100
101impl WorkflowFile {
102    fn zed(f: fn() -> Workflow) -> WorkflowFile {
103        WorkflowFile {
104            source: WorkflowSource::Contextless(f),
105            r#type: WorkflowType::Zed,
106        }
107    }
108
109    fn extension(f: fn(&GenerateWorkflowArgs) -> Workflow) -> WorkflowFile {
110        WorkflowFile {
111            source: WorkflowSource::WithContext(f),
112            r#type: WorkflowType::ExtensionCi,
113        }
114    }
115
116    fn extension_shared(f: fn(&GenerateWorkflowArgs) -> Workflow) -> WorkflowFile {
117        WorkflowFile {
118            source: WorkflowSource::WithContext(f),
119            r#type: WorkflowType::ExtensionsShared,
120        }
121    }
122
123    fn generate_file(&self, workflow_args: &GenerateWorkflowArgs) -> Result<()> {
124        let workflow = match &self.source {
125            WorkflowSource::Contextless(f) => f(),
126            WorkflowSource::WithContext(f) => f(workflow_args),
127        };
128        let workflow_folder = self.r#type.folder_path();
129
130        fs::create_dir_all(&workflow_folder).with_context(|| {
131            format!("Failed to create directory: {}", workflow_folder.display())
132        })?;
133
134        let workflow_name = workflow
135            .name
136            .as_ref()
137            .expect("Workflow must have a name at this point");
138        let filename = format!(
139            "{workflow_name}.yml",
140            workflow_name = workflow_name.rsplit("::").next().unwrap_or(workflow_name)
141        );
142
143        let workflow_path = workflow_folder.join(filename);
144
145        let content = workflow
146            .to_string()
147            .map_err(|e| anyhow::anyhow!("{:?}: {:?}", workflow_path, e))?;
148
149        let disclaimer = self.r#type.disclaimer(workflow_name);
150
151        let content = [disclaimer, content].join("\n");
152        fs::write(&workflow_path, content).map_err(Into::into)
153    }
154}
155
156#[derive(PartialEq, Eq, strum::EnumIter)]
157pub enum WorkflowType {
158    /// Workflows living in the Zed repository
159    Zed,
160    /// Workflows living in the `zed-extensions/workflows` repository that are
161    /// required workflows for PRs to the extension organization
162    ExtensionCi,
163    /// Workflows living in each of the extensions to perform checks and version
164    /// bumps until a better, more centralized system for that is in place.
165    ExtensionsShared,
166}
167
168impl WorkflowType {
169    const PREAMBLE: &str = "# Generated from xtask::workflows::";
170
171    fn disclaimer(&self, workflow_name: &str) -> String {
172        format!(
173            concat!(
174                "{preamble}{workflow_name}{external_disclaimer}\n",
175                "# Rebuild with `cargo xtask workflows`.",
176            ),
177            preamble = Self::PREAMBLE,
178            workflow_name = workflow_name,
179            external_disclaimer = (*self != WorkflowType::Zed)
180                .then_some(" within the Zed repository.")
181                .unwrap_or_default(),
182        )
183    }
184
185    pub fn folder_path(&self) -> PathBuf {
186        match self {
187            WorkflowType::Zed => PathBuf::from(".github/workflows"),
188            WorkflowType::ExtensionCi => PathBuf::from("extensions/workflows"),
189            WorkflowType::ExtensionsShared => PathBuf::from("extensions/workflows/shared"),
190        }
191    }
192
193    fn remove_generated_workflows() -> Result<()> {
194        for workflow_type in Self::iter() {
195            for path in fs::read_dir(workflow_type.folder_path())? {
196                let entry = path?;
197                if !entry.file_type().is_ok_and(|file_type| file_type.is_file()) {
198                    continue;
199                }
200
201                let path = entry.path();
202                if fs::read_to_string(&path)
203                    .is_ok_and(|content| content.starts_with(Self::PREAMBLE))
204                {
205                    fs::remove_file(path)?;
206                }
207            }
208        }
209
210        Ok(())
211    }
212}
213
214pub fn run_workflows(args: GenerateWorkflowArgs) -> Result<()> {
215    if !Path::new("crates/zed/").is_dir() {
216        anyhow::bail!("xtask workflows must be ran from the project root");
217    }
218
219    // Remove all previously generated workflows to ensure these do not become stale.
220    WorkflowType::remove_generated_workflows()?;
221
222    let workflows = [
223        WorkflowFile::zed(after_release::after_release),
224        WorkflowFile::zed(autofix_pr::autofix_pr),
225        WorkflowFile::zed(bump_patch_version::bump_patch_version),
226        WorkflowFile::zed(bump_zed_version::bump_zed_version),
227        WorkflowFile::zed(cherry_pick::cherry_pick),
228        WorkflowFile::zed(compliance_check::compliance_check),
229        WorkflowFile::zed(danger::danger),
230        WorkflowFile::zed(deploy_collab::deploy_collab),
231        WorkflowFile::zed(deploy_docs::deploy_docs),
232        WorkflowFile::zed(deploy_docs::deploy_nightly_docs),
233        WorkflowFile::zed(extension_bump::extension_bump),
234        WorkflowFile::zed(extension_auto_bump::extension_auto_bump),
235        WorkflowFile::zed(extension_tests::extension_tests),
236        WorkflowFile::zed(extension_workflow_rollout::extension_workflow_rollout),
237        WorkflowFile::zed(nix_build::nix_build),
238        WorkflowFile::zed(publish_extension_cli::publish_extension_cli),
239        WorkflowFile::zed(release::release),
240        WorkflowFile::zed(release_nightly::release_nightly),
241        WorkflowFile::zed(run_bundling::run_bundling),
242        WorkflowFile::zed(run_tests::run_tests),
243        /* workflows used for CI/CD in extension repositories */
244        WorkflowFile::extension(extensions::run_tests::run_tests),
245        WorkflowFile::extension_shared(extensions::bump_version::bump_version),
246    ];
247
248    for workflow_file in workflows {
249        workflow_file.generate_file(&args)?;
250    }
251
252    workflow_checks::validate(Default::default())
253}
254
Served at tenant.openagents/omega Member data and write actions are omitted.