ci: Ensure generated workflows do not become stale (#55198)

Finn Evers created

This ensures that upon workflow removal in Rust code, the corresponding
workflow file will not accidentally stay in the repository.

Release Notes:

- N/A

Change summary

tooling/xtask/src/tasks/workflows.rs | 33 +++++++++++++++++++++++++++--
1 file changed, 30 insertions(+), 3 deletions(-)

Detailed changes

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

@@ -3,6 +3,7 @@ use clap::Parser;
 use gh_workflow::Workflow;
 use std::fs;
 use std::path::{Path, PathBuf};
+use strum::IntoEnumIterator;
 
 use crate::tasks::workflow_checks::{self};
 
@@ -167,14 +168,17 @@ pub enum WorkflowType {
 }
 
 impl WorkflowType {
+    const PREAMBLE: &str = "# Generated from xtask::workflows::";
+
     fn disclaimer(&self, workflow_name: &str) -> String {
         format!(
             concat!(
-                "# Generated from xtask::workflows::{}{}\n",
+                "{preamble}{workflow_name}{external_disclaimer}\n",
                 "# Rebuild with `cargo xtask workflows`.",
             ),
-            workflow_name,
-            (*self != WorkflowType::Zed)
+            preamble = Self::PREAMBLE,
+            workflow_name = workflow_name,
+            external_disclaimer = (*self != WorkflowType::Zed)
                 .then_some(" within the Zed repository.")
                 .unwrap_or_default(),
         )
@@ -187,6 +191,26 @@ impl WorkflowType {
             WorkflowType::ExtensionsShared => PathBuf::from("extensions/workflows/shared"),
         }
     }
+
+    fn remove_generated_workflows() -> Result<()> {
+        for workflow_type in Self::iter() {
+            for path in fs::read_dir(workflow_type.folder_path())? {
+                let entry = path?;
+                if !entry.file_type().is_ok_and(|file_type| file_type.is_file()) {
+                    continue;
+                }
+
+                let path = entry.path();
+                if fs::read_to_string(&path)
+                    .is_ok_and(|content| content.starts_with(Self::PREAMBLE))
+                {
+                    fs::remove_file(path)?;
+                }
+            }
+        }
+
+        Ok(())
+    }
 }
 
 pub fn run_workflows(args: GenerateWorkflowArgs) -> Result<()> {
@@ -194,6 +218,9 @@ pub fn run_workflows(args: GenerateWorkflowArgs) -> Result<()> {
         anyhow::bail!("xtask workflows must be ran from the project root");
     }
 
+    // Remove all previously generated workflows to ensure these do not become stale.
+    WorkflowType::remove_generated_workflows()?;
+
     let workflows = [
         WorkflowFile::zed(after_release::after_release),
         WorkflowFile::zed(autofix_pr::autofix_pr),