diff --git a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsCheckoutStep.cs b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsCheckoutStep.cs
index 2f2cdb62..6c7c06e6 100644
--- a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsCheckoutStep.cs
+++ b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsCheckoutStep.cs
@@ -24,12 +24,14 @@ public class GitHubActionsCheckoutStep : GitHubActionsStep
///
public string Ref { get; set; }
+ public string[] CheckoutWith { get; set; } = new string[0];
+
public override void Write(CustomFileWriter writer)
{
writer.WriteLine("- uses: actions/checkout@v6");
if (Submodules.HasValue || Lfs.HasValue || FetchDepth.HasValue || Progress.HasValue ||
- !Filter.IsNullOrWhiteSpace() || !Ref.IsNullOrWhiteSpace())
+ !Filter.IsNullOrWhiteSpace() || !Ref.IsNullOrWhiteSpace() || CheckoutWith.Length > 0)
{
using (writer.Indent())
{
@@ -57,6 +59,9 @@ public override void Write(CustomFileWriter writer)
writer.WriteLine("repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}");
writer.WriteLine($"ref: {Ref}");
}
+
+ foreach (var line in CheckoutWith)
+ writer.WriteLine(line);
}
}
}
diff --git a/src/Fallout.Common/CI/GitHubActions/GitHubActionsAttribute.cs b/src/Fallout.Common/CI/GitHubActions/GitHubActionsAttribute.cs
index 262a7b9b..c9c48d9f 100644
--- a/src/Fallout.Common/CI/GitHubActions/GitHubActionsAttribute.cs
+++ b/src/Fallout.Common/CI/GitHubActions/GitHubActionsAttribute.cs
@@ -139,6 +139,19 @@ public string CheckoutRef
get => throw new NotSupportedException();
}
+ ///
+ /// Extra actions/checkout inputs, emitted verbatim inside the step's with: block after
+ /// the typed keys (submodules, lfs, fetch-depth, progress, filter,
+ /// ref/repository). An escape hatch for inputs the typed knobs don't cover — token,
+ /// ssh-key, path, clean, persist-credentials, sparse-checkout,
+ /// set-safe-directory.
+ ///
+ /// Each entry is one raw line — passed through unvalidated, so the caller owns correct YAML. Multi-line
+ /// block scalars work by supplying the key (e.g. sparse-checkout: |) and each continuation line
+ /// as separate entries, with the caller's own indentation preserved. Empty emits nothing.
+ ///
+ public string[] CheckoutWith { get; set; } = new string[0];
+
public override CustomFileWriter CreateWriter(StreamWriter streamWriter)
{
return new CustomFileWriter(streamWriter, indentationFactor: 2, commentPrefix: "#");
@@ -204,7 +217,8 @@ private IEnumerable GetSteps(IReadOnlyCollection
+#
+# This code was generated.
+#
+# - To turn off auto-generation set:
+#
+# [TestGitHubActions (AutoGenerate = false)]
+#
+# - To trigger manual generation invoke:
+#
+# fallout --generate-configuration GitHubActions_test --host GitHubActions
+#
+#
+# ------------------------------------------------------------------------------
+
+name: test
+
+on: [push]
+
+jobs:
+ ubuntu-latest:
+ name: ubuntu-latest
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ path: src
+ - name: 'Cache: .fallout/temp, ~/.nuget/packages'
+ uses: actions/cache@v4
+ with:
+ path: |
+ .fallout/temp
+ ~/.nuget/packages
+ key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj', '**/Directory.Packages.props') }}
+ - name: 'Setup: .NET SDK'
+ uses: actions/setup-dotnet@v4
+ with:
+ global-json-file: global.json
+ - name: 'Restore: dotnet tools'
+ run: dotnet tool restore
+ - name: 'Run: Test'
+ run: dotnet fallout Test
+ - name: 'Publish: src'
+ uses: actions/upload-artifact@v5
+ with:
+ name: src
+ path: src
+ - name: 'Publish: test-results'
+ uses: actions/upload-artifact@v5
+ with:
+ name: test-results
+ path: output/test-results
+ - name: 'Publish: coverage-report.zip'
+ uses: actions/upload-artifact@v5
+ with:
+ name: coverage-report.zip
+ path: output/coverage-report.zip
diff --git a/tests/Fallout.Common.Tests/CI/ConfigurationGenerationTest.Test_testName=checkout-with-sparse_attribute=GitHubActionsAttribute.verified.txt b/tests/Fallout.Common.Tests/CI/ConfigurationGenerationTest.Test_testName=checkout-with-sparse_attribute=GitHubActionsAttribute.verified.txt
new file mode 100644
index 00000000..f6d1be72
--- /dev/null
+++ b/tests/Fallout.Common.Tests/CI/ConfigurationGenerationTest.Test_testName=checkout-with-sparse_attribute=GitHubActionsAttribute.verified.txt
@@ -0,0 +1,60 @@
+# ------------------------------------------------------------------------------
+#
+#
+# This code was generated.
+#
+# - To turn off auto-generation set:
+#
+# [TestGitHubActions (AutoGenerate = false)]
+#
+# - To trigger manual generation invoke:
+#
+# fallout --generate-configuration GitHubActions_test --host GitHubActions
+#
+#
+# ------------------------------------------------------------------------------
+
+name: test
+
+on: [push]
+
+jobs:
+ ubuntu-latest:
+ name: ubuntu-latest
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ sparse-checkout: |
+ src
+ build
+ - name: 'Cache: .fallout/temp, ~/.nuget/packages'
+ uses: actions/cache@v4
+ with:
+ path: |
+ .fallout/temp
+ ~/.nuget/packages
+ key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj', '**/Directory.Packages.props') }}
+ - name: 'Setup: .NET SDK'
+ uses: actions/setup-dotnet@v4
+ with:
+ global-json-file: global.json
+ - name: 'Restore: dotnet tools'
+ run: dotnet tool restore
+ - name: 'Run: Test'
+ run: dotnet fallout Test
+ - name: 'Publish: src'
+ uses: actions/upload-artifact@v5
+ with:
+ name: src
+ path: src
+ - name: 'Publish: test-results'
+ uses: actions/upload-artifact@v5
+ with:
+ name: test-results
+ path: output/test-results
+ - name: 'Publish: coverage-report.zip'
+ uses: actions/upload-artifact@v5
+ with:
+ name: coverage-report.zip
+ path: output/coverage-report.zip
diff --git a/tests/Fallout.Common.Tests/CI/ConfigurationGenerationTest.Test_testName=checkout-with_attribute=GitHubActionsAttribute.verified.txt b/tests/Fallout.Common.Tests/CI/ConfigurationGenerationTest.Test_testName=checkout-with_attribute=GitHubActionsAttribute.verified.txt
new file mode 100644
index 00000000..1a694dd6
--- /dev/null
+++ b/tests/Fallout.Common.Tests/CI/ConfigurationGenerationTest.Test_testName=checkout-with_attribute=GitHubActionsAttribute.verified.txt
@@ -0,0 +1,61 @@
+# ------------------------------------------------------------------------------
+#
+#
+# This code was generated.
+#
+# - To turn off auto-generation set:
+#
+# [TestGitHubActions (AutoGenerate = false)]
+#
+# - To trigger manual generation invoke:
+#
+# fallout --generate-configuration GitHubActions_test --host GitHubActions
+#
+#
+# ------------------------------------------------------------------------------
+
+name: test
+
+on: [push]
+
+jobs:
+ ubuntu-latest:
+ name: ubuntu-latest
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+ token: ${{ secrets.CI_PAT }}
+ path: src
+ persist-credentials: false
+ - name: 'Cache: .fallout/temp, ~/.nuget/packages'
+ uses: actions/cache@v4
+ with:
+ path: |
+ .fallout/temp
+ ~/.nuget/packages
+ key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj', '**/Directory.Packages.props') }}
+ - name: 'Setup: .NET SDK'
+ uses: actions/setup-dotnet@v4
+ with:
+ global-json-file: global.json
+ - name: 'Restore: dotnet tools'
+ run: dotnet tool restore
+ - name: 'Run: Test'
+ run: dotnet fallout Test
+ - name: 'Publish: src'
+ uses: actions/upload-artifact@v5
+ with:
+ name: src
+ path: src
+ - name: 'Publish: test-results'
+ uses: actions/upload-artifact@v5
+ with:
+ name: test-results
+ path: output/test-results
+ - name: 'Publish: coverage-report.zip'
+ uses: actions/upload-artifact@v5
+ with:
+ name: coverage-report.zip
+ path: output/coverage-report.zip
diff --git a/tests/Fallout.Common.Tests/CI/ConfigurationGenerationTest.cs b/tests/Fallout.Common.Tests/CI/ConfigurationGenerationTest.cs
index bbbe05d5..f8930cc3 100644
--- a/tests/Fallout.Common.Tests/CI/ConfigurationGenerationTest.cs
+++ b/tests/Fallout.Common.Tests/CI/ConfigurationGenerationTest.cs
@@ -212,6 +212,55 @@ public class TestBuild : FalloutBuild
}
);
+ // Ordering guard: extra CheckoutWith inputs emit verbatim inside the with: block, after
+ // every typed key (here fetch-depth) and in the order supplied.
+ yield return
+ (
+ "checkout-with",
+ new TestGitHubActionsAttribute(GitHubActionsImage.UbuntuLatest)
+ {
+ On = new[] { GitHubActionsTrigger.Push },
+ InvokedTargets = new[] { nameof(Test) },
+ FetchDepth = 0,
+ CheckoutWith = new[]
+ {
+ "token: ${{ secrets.CI_PAT }}",
+ "path: src",
+ "persist-credentials: false"
+ }
+ }
+ );
+
+ // Guard: CheckoutWith with no typed checkout key must still open the with: block.
+ yield return
+ (
+ "checkout-with-only",
+ new TestGitHubActionsAttribute(GitHubActionsImage.UbuntuLatest)
+ {
+ On = new[] { GitHubActionsTrigger.Push },
+ InvokedTargets = new[] { nameof(Test) },
+ CheckoutWith = new[] { "path: src" }
+ }
+ );
+
+ // Multi-line block scalar: the case a 'KEY: value' validator would reject. Proves raw
+ // verbatim emission preserves the caller-supplied continuation lines and indentation.
+ yield return
+ (
+ "checkout-with-sparse",
+ new TestGitHubActionsAttribute(GitHubActionsImage.UbuntuLatest)
+ {
+ On = new[] { GitHubActionsTrigger.Push },
+ InvokedTargets = new[] { nameof(Test) },
+ CheckoutWith = new[]
+ {
+ "sparse-checkout: |",
+ " src",
+ " build"
+ }
+ }
+ );
+
yield return
(
null,