Skip to content

Add a typed step-injection pipeline to the GitHub Actions generator - #475

Merged
avidenic merged 14 commits into
Fallout-build:mainfrom
avidenic:features/456-github-actions-step-injection
Jul 13, 2026
Merged

Add a typed step-injection pipeline to the GitHub Actions generator#475
avidenic merged 14 commits into
Fallout-build:mainfrom
avidenic:features/456-github-actions-step-injection

Conversation

@avidenic

Copy link
Copy Markdown
Contributor

Closes#456.

Problem

GitHubActionsAttribute.GetSteps() yields a fixed, closed step sequence with no hook points and is private. No way to inject a marketplace action or custom shell step at a chosen position.

Outcome

A build implements IConfigureGitHubActions.ConfigureSteps(GitHubActionsStepPipeline) to inject typed uses:/run: steps at four named positions — compile-checked, no reflection.

partialclassBuild:NukeBuild,IConfigureGitHubActions{publicvoidConfigureSteps(GitHubActionsStepPipelinepipeline){if(pipeline.WorkflowName!="security")return;// scope by workflow / runnerpipeline.Insert(GitHubActionsStepPosition.PostRun,newGitHubActionsCustomStep{Name="Perform CodeQL Analysis",Uses="github/codeql-action/analyze@v3",If="github.ref == 'refs/heads/main'",});}}

New public types: GitHubActionsCustomStep, GitHubActionsStepPosition (PostCheckout/PreRun/PostRun/JobEnd), IConfigureGitHubActions, GitHubActionsStepPipeline.

Notes

Acceptance criteria

  • Inject uses:/run: at each of the four positions; multiple inserts at one position render in call order.
  • Positions resolve correctly with cache/artifacts absent.
  • Invalid steps fail generation with a clear ArgumentException.
  • Steps scope to a workflow/runner via WorkflowName/Image (no per-step scoping arrays).
  • No churn on existing snapshots.

Verification

Full Fallout.Common.Specs suite: 114 passed, 7 skipped, 0 failed. Zero snapshot churn. Full-solution build clean.

Introduce the public surface a build uses to inject custom workflow steps:
GitHubActionsCustomStep (uses:/run: with fixed-order YAML rendering),
GitHubActionsStepPosition (PostCheckout/PreRun/PostRun/JobEnd), the
IConfigureGitHubActions hook interface, and GitHubActionsStepPipeline (per-job
context + Insert + read-only BuiltInSteps view). Expose Fallout.Common internals
to Fallout.Common.Specs so the pipeline's generator-only surface stays internal
but testable.
Refactor GitHubActionsAttribute.GetSteps to assemble the base step sequence,
hand it to a per-job GitHubActionsStepPipeline as read-only BuiltInSteps, query
Build for IConfigureGitHubActions, validate the collected inserts, then splice
them in at their anchor positions (call order preserved). Validation fails the
build on an invalid step: exactly one of Uses/Run, With requires Uses, Shell
only on a run step. A build not implementing the interface, or one that inserts
nothing, yields byte-identical YAML.
@avidenicavidenic self-assigned this Jul 10, 2026
@avidenicavidenic added target/vCurrent Targets the current version enhancement New feature or request labels Jul 10, 2026
Read the Write flow top-down; the Scalar/MapBlock local functions that close
over the list-marker flag now sit at the end. Behaviour is unchanged.
@avidenic
avidenic marked this pull request as ready for review July 10, 2026 13:26
@avidenic
avidenic requested a review from a team as a code ownerJuly 10, 2026 13:26
Comment threadtests/Fallout.Common.Specs/CI/GitHubActionsCustomStepSpecs.cs Outdated
Use raw string literals for the multi-line expected YAML (more readable than
line concatenation) and normalize line endings on both sides. The renderer
emits Environment.NewLine, so normalizing keeps these exact-output assertions
passing on the cross-platform post-merge jobs, not just the ubuntu PR gate.
@ChrisonSimtian

Copy link
Copy Markdown
Collaborator

nice feature, thanks for bringing this in :-)

@ChrisonSimtianChrisonSimtian left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really clean, additive design — the generator keeps sole control of the base sequence and the hook only inserts. Test coverage is the standout: all four positions, the cache/artifacts-absent anchoring case, ordering, workflow scoping, and a byte-identical no-op regression guard.

Approving — nothing here blocks, but one is worth fixing before this gets real-world use (inline): custom-step scalar values are emitted unquoted, whereas every built-in step quotes names, so a Name/If/Uses containing a colon-space, #, or leading indicator char emits invalid YAML. That contradicts the "never emits invalid YAML" line, which today only covers the structural uses/run checks. Two smaller notes on dictionary ordering and a null-step guard.

Confirmed Assert.True throws ArgumentException (src/Fallout.Utilities/Assert.cs), so the validation specs assert the right type. 👍

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a public, typed step-injection hook to the GitHub Actions generator so builds can insert custom uses: / run: steps at fixed, named positions per job, while keeping the generator’s base step sequence intact.

Changes:

  • Introduces the step-injection API surface (IConfigureGitHubActions, GitHubActionsStepPipeline, GitHubActionsCustomStep, GitHubActionsStepPosition).
  • Extends GitHubActionsAttribute generation to invoke the hook per job, validate injected steps, and splice inserts into the rendered step list.
  • Adds specs + snapshot coverage for ordering, anchor positions (with/without cache/artifacts), scoping via pipeline context, and validation failures.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
tests/Fallout.Common.Specs/CI/GitHubActionsStepPipelineSpecs.csUnit coverage for pipeline context exposure + insertion ordering semantics.
tests/Fallout.Common.Specs/CI/GitHubActionsStepInjectionSpecs.csBehavioral coverage of injection positions, ordering, scoping, and no-op regression.
tests/Fallout.Common.Specs/CI/GitHubActionsStepInjectionSpecs.Rich_injection_renders_expected_yaml.verified.txtSnapshot contract for rich multi-position injection YAML output.
tests/Fallout.Common.Specs/CI/GitHubActionsCustomStepValidationSpecs.csVerifies invalid injected steps fail generation with ArgumentException.
tests/Fallout.Common.Specs/CI/GitHubActionsCustomStepSpecs.csIsolated rendering specs for GitHubActionsCustomStep YAML output.
src/Fallout.Common/Fallout.Common.csprojAdds InternalsVisibleTo for specs to access internal pipeline APIs.
src/Fallout.Common/CI/GitHubActions/IConfigureGitHubActions.csNew build hook interface invoked per generated job.
src/Fallout.Common/CI/GitHubActions/GitHubActionsStepPosition.csNew enum defining the four named insertion anchors.
src/Fallout.Common/CI/GitHubActions/GitHubActionsAttribute.csHook invocation + splice/validation integrated into job step generation.
src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsStepPipeline.csPipeline implementation collecting inserts and exposing built-in steps context.
src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsCustomStep.csNew user-constructible step type for uses:/run: with optional fields + YAML writer.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Quote custom-step names via SingleQuote(), matching the built-in steps, so a
name containing a colon stays valid YAML (the structural validation didn't
cover value safety).
- Render with:/env: in ordinal key order for deterministic output; Dictionary
enumeration order isn't guaranteed.
- Guard Insert against null steps with Assert.NotNull, and treat null
With/Env/Run as empty, so misuse fails with the clean ArgumentException the
design promises rather than a NullReferenceException.
- Add specs: colon-in-name quoting, multi-entry ordinal ordering, null-step
insert, and null collections.
The shared SingleQuote() helper escapes an embedded quote with a backslash,
which is invalid inside a YAML single-quoted scalar, so a name like "Bob's
step" emitted 'Bob\'s step'. Quote the name locally with YAML doubling ('')
instead; output is identical for names without a quote. Left the shared helper
alone — it is also used for shell/log output across 26 call sites where the
doubling form would be wrong.
Also guard the multi-insert ordering test so a dropped first insert fails
instead of passing vacuously on IndexOf == -1.
Move the YAML-correct single-quote (doubling embedded quotes) out of the custom
step and into StringExtensions.SingleQuoteYaml, next to SingleQuote/DoubleQuote,
with its own unit test. This is the primitive the built-in CI writers should
adopt to replace SingleQuote() in YAML contexts (a separate follow-up).

@ITaluoneITaluone left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work!

Just this two questions

Comment threadsrc/Fallout.Utilities/Text/String.Quoting.cs
Switch GitHubActionsCustomStepSpecs from hand-authored expected strings + a
line-ending-normalizing helper to Verify, matching the generator testing
strategy used elsewhere. Verify normalizes line endings on compare, and the new
'*.verified.* text eol=lf' gitattribute keeps the snapshots LF in the repo, so
the exact-output assertions are platform-independent without bespoke scaffolding.

@ITaluoneITaluone left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice work!

Thanks again

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancementNew feature or requesttarget/vCurrentTargets the current version

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[GitHubActions] generator: a typed step-injection pipeline (custom steps at named positions)

5 participants

@avidenic@ChrisonSimtian@ITaluone@IT-VBFK