use gh_workflow::{ Event, Expression, Input, Job, Level, Permissions, Push, Strategy, UsesJob, Workflow, }; use indoc::indoc; use serde_json::json; use crate::tasks::workflows::{ extensions::WithAppSecrets, run_tests::DETECT_CHANGED_EXTENSIONS_SCRIPT, runners, steps::{self, CommonJobConditions, CommonPermissionSets, NamedJob, named}, vars::{StepOutput, one_workflow_per_non_main_branch}, }; /// Generates a workflow that triggers on push to main, detects changed extensions /// in the `extensions/` directory, and invokes the `extension_bump` reusable workflow /// for each changed extension via a matrix strategy. pub(crate) fn extension_auto_bump() -> Workflow { let detect = detect_changed_extensions(); let bump = bump_extension_versions(&detect); named::workflow() .with_minimal_permissions() .add_event( Event::default().push( Push::default() .add_branch("main") .add_path("extensions/**") .add_path("!extensions/test-extension/**") .add_path("!extensions/workflows/**") .add_path("!extensions/*.md"), ), ) .concurrency(one_workflow_per_non_main_branch()) .add_job(detect.name, detect.job) .add_job(bump.name, bump.job) } fn detect_changed_extensions() -> NamedJob { let preamble = indoc! {r#" COMPARE_REV="$(git rev-parse HEAD~1)" CHANGED_FILES="$(git diff --name-only "$COMPARE_REV" "$GITHUB_SHA")" "#}; let filter_newly_added = indoc! {r#" # Filter out newly added extensions FILTERED="[]" for ext in $(echo "$EXTENSIONS_JSON" | jq -r '.[]'); do if git show HEAD~1:"$ext/extension.toml" >/dev/null 2>&1; then FILTERED=$(echo "$FILTERED" | jq -c --arg e "$ext" '. + [$e]') fi done echo "changed_extensions=$FILTERED" >> "$GITHUB_OUTPUT" "#}; let script = format!( "{preamble}{detect}{filter}", preamble = preamble, detect = DETECT_CHANGED_EXTENSIONS_SCRIPT, filter = filter_newly_added, ); let step = named::bash(script).id("detect"); let output = StepOutput::new(&step, "changed_extensions"); let job = Job::default() .with_repository_owner_guard() .runs_on(runners::LINUX_SMALL) .timeout_minutes(5u32) .add_step(steps::checkout_repo().with_custom_fetch_depth(2)) .add_step(step) .outputs([("changed_extensions".to_owned(), output.to_string())]); named::job(job) } fn bump_extension_versions(detect_job: &NamedJob) -> NamedJob { let job = Job::default() .needs(vec![detect_job.name.clone()]) .cond(Expression::new(format!( "needs.{}.outputs.changed_extensions != '[]'", detect_job.name ))) .permissions( Permissions::default() .contents(Level::Write) .issues(Level::Write) .pull_requests(Level::Write) .actions(Level::Write), ) .strategy( Strategy::default() .fail_fast(false) // TODO: Remove the limit. We currently need this to workaround the concurrency group issue // where different matrix jobs would be placed in the same concurrency group and thus cancelled. .max_parallel(1u32) .matrix(json!({ "extension": format!( "${{{{ fromJson(needs.{}.outputs.changed_extensions) }}}}", detect_job.name ) })), ) .uses_local(".github/workflows/extension_bump.yml") .with( Input::default() .add("working-directory", "${{ matrix.extension }}") .add("force-bump", false), ) .with_app_secrets(); named::job(job) }