diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 8a6ba81..1be2324 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -83,6 +83,24 @@ jobs:
verbosity: Normal
cake-version: tool-manifest
+ # The demo/ folders exercise the just-built addin DLL under
+ # Cake-major-matching cake.tool / Cake.Frosting versions. They run
+ # in their OWN tool/package context (independent of recipe.cake's
+ # cake.tool, which is constrained by Cake.Recipe's runtime). Both
+ # consume the addin from BuildArtifacts/temp/_PublishedLibraries/
+ # populated by DotNetCore-Pack. demo/sdk joins at the Cake 6
+ # catch-up release.
+
+ - name: Run demo/script
+ if: success()
+ shell: pwsh
+ run: ./demo/script/build.ps1
+
+ - name: Run demo/frosting
+ if: success()
+ shell: pwsh
+ run: ./demo/frosting/build.ps1
+
- name: Upload Issues-Report
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
with:
diff --git a/demo/frosting/.gitignore b/demo/frosting/.gitignore
new file mode 100644
index 0000000..0797399
--- /dev/null
+++ b/demo/frosting/.gitignore
@@ -0,0 +1,4 @@
+bin/
+obj/
+output/
+.vscode/
diff --git a/demo/frosting/build.ps1 b/demo/frosting/build.ps1
new file mode 100644
index 0000000..ec13282
--- /dev/null
+++ b/demo/frosting/build.ps1
@@ -0,0 +1,10 @@
+$ErrorActionPreference = 'Stop'
+
+Set-Location -LiteralPath $PSScriptRoot
+
+$env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = '1'
+$env:DOTNET_CLI_TELEMETRY_OPTOUT = '1'
+$env:DOTNET_NOLOGO = '1'
+
+dotnet run --project build/Build.csproj -- @args
+exit $LASTEXITCODE;
diff --git a/demo/frosting/build.sh b/demo/frosting/build.sh
new file mode 100644
index 0000000..fa7b3d4
--- /dev/null
+++ b/demo/frosting/build.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+set -euox pipefail
+
+cd "$(dirname "${BASH_SOURCE[0]}")"
+
+export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
+export DOTNET_CLI_TELEMETRY_OPTOUT=1
+export DOTNET_NOLOGO=1
+
+dotnet run --project build/Build.csproj -- "$@"
diff --git a/demo/frosting/build/Build.csproj b/demo/frosting/build/Build.csproj
new file mode 100644
index 0000000..e7745ab
--- /dev/null
+++ b/demo/frosting/build/Build.csproj
@@ -0,0 +1,14 @@
+
+
+ Exe
+ net8.0
+ $(MSBuildProjectDirectory)\
+ disable
+
+
+
+ $(MSBuildProjectDirectory)\..\..\..\BuildArtifacts\temp\_PublishedLibraries\Cake.FileHelpers\net8.0\Cake.FileHelpers.dll
+
+
+
+
diff --git a/demo/frosting/build/BuildContext.cs b/demo/frosting/build/BuildContext.cs
new file mode 100644
index 0000000..08bc914
--- /dev/null
+++ b/demo/frosting/build/BuildContext.cs
@@ -0,0 +1,24 @@
+using Cake.Core;
+using Cake.Core.IO;
+using Cake.Frosting;
+
+namespace Build
+{
+ public class BuildContext : FrostingContext
+ {
+ public DirectoryPath WorkDir { get; } = "./BuildArtifacts/temp/test-filehelpers-frosting";
+
+ public FilePath SampleFile => WorkDir.CombineWithFilePath("sample.txt");
+
+ public FilePath SampleFile2 => WorkDir.CombineWithFilePath("sample2.txt");
+
+ public FilePath WrittenFile => WorkDir.CombineWithFilePath("written.txt");
+
+ public FilePath TouchFile => WorkDir.CombineWithFilePath("touched.txt");
+
+ public BuildContext(ICakeContext context)
+ : base(context)
+ {
+ }
+ }
+}
diff --git a/demo/frosting/build/DefaultTask.cs b/demo/frosting/build/DefaultTask.cs
new file mode 100644
index 0000000..2f669c3
--- /dev/null
+++ b/demo/frosting/build/DefaultTask.cs
@@ -0,0 +1,11 @@
+using Build.Tasks;
+using Cake.Frosting;
+
+namespace Build
+{
+ [TaskName("Default")]
+ [IsDependentOn(typeof(CleanupTask))]
+ public sealed class DefaultTask : FrostingTask
+ {
+ }
+}
diff --git a/demo/frosting/build/Program.cs b/demo/frosting/build/Program.cs
new file mode 100644
index 0000000..88476e3
--- /dev/null
+++ b/demo/frosting/build/Program.cs
@@ -0,0 +1,14 @@
+using Cake.Frosting;
+
+namespace Build
+{
+ public static class Program
+ {
+ public static int Main(string[] args)
+ {
+ return new CakeHost()
+ .UseContext()
+ .Run(args);
+ }
+ }
+}
diff --git a/demo/frosting/build/Tasks/AppendOperationsTask.cs b/demo/frosting/build/Tasks/AppendOperationsTask.cs
new file mode 100644
index 0000000..bac0f5b
--- /dev/null
+++ b/demo/frosting/build/Tasks/AppendOperationsTask.cs
@@ -0,0 +1,37 @@
+using System;
+using Cake.Common.Diagnostics;
+using Cake.FileHelpers;
+using Cake.Frosting;
+
+namespace Build.Tasks
+{
+ [TaskName("Append-Operations")]
+ [IsDependentOn(typeof(WriteOperationsTask))]
+ public sealed class AppendOperationsTask : FrostingTask
+ {
+ public override void Run(BuildContext context)
+ {
+ var beforeLines = context.FileReadLines(context.WrittenFile);
+
+ context.FileAppendText(context.WrittenFile, "appended text\n");
+ context.FileAppendLines(context.WrittenFile, new[] { "appended line 1", "appended line 2" });
+ var afterLines = context.FileReadLines(context.WrittenFile);
+
+ AssertThat(
+ afterLines.Length == beforeLines.Length + 3,
+ $"Append: expected {beforeLines.Length + 3} lines, got {afterLines.Length}");
+ AssertThat(
+ afterLines[afterLines.Length - 1] == "appended line 2",
+ "Append: last line mismatch");
+ context.Information("FileAppendText + FileAppendLines OK ({0} lines after append)", afterLines.Length);
+ }
+
+ private static void AssertThat(bool condition, string message)
+ {
+ if (!condition)
+ {
+ throw new Exception("Assertion failed: " + message);
+ }
+ }
+ }
+}
diff --git a/demo/frosting/build/Tasks/CleanupTask.cs b/demo/frosting/build/Tasks/CleanupTask.cs
new file mode 100644
index 0000000..3700e3e
--- /dev/null
+++ b/demo/frosting/build/Tasks/CleanupTask.cs
@@ -0,0 +1,23 @@
+using Cake.Common.Diagnostics;
+using Cake.Common.IO;
+using Cake.Frosting;
+
+namespace Build.Tasks
+{
+ [TaskName("Cleanup")]
+ [IsDependentOn(typeof(ReplaceOperationsTask))]
+ public sealed class CleanupTask : FrostingTask
+ {
+ public override void Run(BuildContext context)
+ {
+ if (context.DirectoryExists(context.WorkDir))
+ {
+ context.DeleteDirectory(
+ context.WorkDir,
+ new DeleteDirectorySettings { Recursive = true });
+ }
+
+ context.Information("Cleanup complete.");
+ }
+ }
+}
diff --git a/demo/frosting/build/Tasks/FindOperationsTask.cs b/demo/frosting/build/Tasks/FindOperationsTask.cs
new file mode 100644
index 0000000..6982add
--- /dev/null
+++ b/demo/frosting/build/Tasks/FindOperationsTask.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Text.RegularExpressions;
+using Cake.Common.Diagnostics;
+using Cake.FileHelpers;
+using Cake.Frosting;
+
+namespace Build.Tasks
+{
+ [TaskName("Find-Operations")]
+ [IsDependentOn(typeof(TouchTask))]
+ public sealed class FindOperationsTask : FrostingTask
+ {
+ public override void Run(BuildContext context)
+ {
+ var samples = context.WorkDir.CombineWithFilePath("sample*.txt").FullPath;
+
+ var byText = context.FindTextInFiles(samples, "alpha");
+ AssertThat(byText.Length == 2, "FindTextInFiles 'alpha': expected 2 files, got " + byText.Length);
+ context.Information("FindTextInFiles 'alpha' OK ({0} files)", byText.Length);
+
+ var byRegex = context.FindRegexInFiles(samples, @"\d+");
+ AssertThat(byRegex.Length == 1, "FindRegexInFiles digits: expected 1 file (sample.txt has '42'), got " + byRegex.Length);
+ context.Information("FindRegexInFiles digits OK ({0} file)", byRegex.Length);
+
+ var single = context.FindRegexMatchInFile(context.SampleFile, @"\d+", RegexOptions.None);
+ AssertThat(single == "1", "FindRegexMatchInFile: expected first digit run '1' (from 'line 1'), got " + (single ?? "null"));
+ context.Information("FindRegexMatchInFile digits OK ('{0}')", single);
+
+ var many = context.FindRegexMatchesInFile(context.SampleFile, @"alpha", RegexOptions.None);
+ AssertThat(many != null && many.Count == 2, $"FindRegexMatchesInFile 'alpha': expected 2 matches, got {many?.Count ?? 0}");
+ context.Information("FindRegexMatchesInFile 'alpha' OK ({0} matches)", many.Count);
+
+ var group = context.FindRegexMatchGroupInFile(context.SampleFile, @"gamma (\d+)", 1, RegexOptions.None);
+ AssertThat(group != null && group.Value == "42", "FindRegexMatchGroupInFile: expected group '42', got " + (group?.Value ?? "null"));
+ context.Information("FindRegexMatchGroupInFile OK ('{0}')", group.Value);
+
+ var groups = context.FindRegexMatchGroupsInFile(context.SampleFile, @"(alpha) (line)", RegexOptions.None);
+ AssertThat(groups != null && groups.Count == 3, $"FindRegexMatchGroupsInFile: expected 3 groups (full + 2 captures), got {groups?.Count ?? 0}");
+ context.Information("FindRegexMatchGroupsInFile OK ({0} groups)", groups.Count);
+
+ var allGroups = context.FindRegexMatchesGroupsInFile(context.SampleFile, @"(alpha) (line)", RegexOptions.None);
+ AssertThat(allGroups != null && allGroups.Count == 2, $"FindRegexMatchesGroupsInFile: expected 2 matches, got {allGroups?.Count ?? 0}");
+ context.Information("FindRegexMatchesGroupsInFile OK ({0} matches)", allGroups.Count);
+ }
+
+ private static void AssertThat(bool condition, string message)
+ {
+ if (!condition)
+ {
+ throw new Exception("Assertion failed: " + message);
+ }
+ }
+ }
+}
diff --git a/demo/frosting/build/Tasks/ReadOperationsTask.cs b/demo/frosting/build/Tasks/ReadOperationsTask.cs
new file mode 100644
index 0000000..2978424
--- /dev/null
+++ b/demo/frosting/build/Tasks/ReadOperationsTask.cs
@@ -0,0 +1,33 @@
+using System;
+using Cake.Common.Diagnostics;
+using Cake.FileHelpers;
+using Cake.Frosting;
+
+namespace Build.Tasks
+{
+ [TaskName("Read-Operations")]
+ [IsDependentOn(typeof(SetupTask))]
+ public sealed class ReadOperationsTask : FrostingTask
+ {
+ public override void Run(BuildContext context)
+ {
+ var lines = context.FileReadLines(context.SampleFile);
+ AssertThat(lines.Length == 4, "FileReadLines: expected 4 lines, got " + lines.Length);
+ AssertThat(lines[0] == "alpha line 1", "FileReadLines: first line mismatch");
+ AssertThat(lines[3] == "alpha line 4", "FileReadLines: last line mismatch");
+ context.Information("FileReadLines OK ({0} lines)", lines.Length);
+
+ var text = context.FileReadText(context.SampleFile);
+ AssertThat(text.Contains("gamma 42 delta"), "FileReadText: expected content not found");
+ context.Information("FileReadText OK ({0} chars)", text.Length);
+ }
+
+ private static void AssertThat(bool condition, string message)
+ {
+ if (!condition)
+ {
+ throw new Exception("Assertion failed: " + message);
+ }
+ }
+ }
+}
diff --git a/demo/frosting/build/Tasks/ReplaceOperationsTask.cs b/demo/frosting/build/Tasks/ReplaceOperationsTask.cs
new file mode 100644
index 0000000..4dc051f
--- /dev/null
+++ b/demo/frosting/build/Tasks/ReplaceOperationsTask.cs
@@ -0,0 +1,40 @@
+using System;
+using Cake.Common.Diagnostics;
+using Cake.FileHelpers;
+using Cake.Frosting;
+
+namespace Build.Tasks
+{
+ [TaskName("Replace-Operations")]
+ [IsDependentOn(typeof(FindOperationsTask))]
+ public sealed class ReplaceOperationsTask : FrostingTask
+ {
+ public override void Run(BuildContext context)
+ {
+ var samples = context.WorkDir.CombineWithFilePath("sample*.txt").FullPath;
+
+ var changed = context.ReplaceTextInFiles(samples, "alpha", "ALPHA");
+ AssertThat(changed.Length == 2, "ReplaceTextInFiles 'alpha'->'ALPHA': expected 2 changed files, got " + changed.Length);
+
+ var afterText = context.FileReadText(context.SampleFile);
+ AssertThat(
+ afterText.Contains("ALPHA") && !afterText.Contains("alpha"),
+ "ReplaceTextInFiles: replacement not applied to sample.txt");
+ context.Information("ReplaceTextInFiles OK ({0} files modified)", changed.Length);
+
+ var changedRx = context.ReplaceRegexInFiles(samples, @"beta", "BETA");
+ AssertThat(changedRx.Length == 1, "ReplaceRegexInFiles 'beta'->'BETA': expected 1 changed file (only sample.txt has 'beta'), got " + changedRx.Length);
+ var afterRegex = context.FileReadText(context.SampleFile);
+ AssertThat(afterRegex.Contains("BETA"), "ReplaceRegexInFiles: replacement not applied");
+ context.Information("ReplaceRegexInFiles OK ({0} files modified)", changedRx.Length);
+ }
+
+ private static void AssertThat(bool condition, string message)
+ {
+ if (!condition)
+ {
+ throw new Exception("Assertion failed: " + message);
+ }
+ }
+ }
+}
diff --git a/demo/frosting/build/Tasks/SetupTask.cs b/demo/frosting/build/Tasks/SetupTask.cs
new file mode 100644
index 0000000..8dec7f7
--- /dev/null
+++ b/demo/frosting/build/Tasks/SetupTask.cs
@@ -0,0 +1,32 @@
+using System.IO;
+using Cake.Common.Diagnostics;
+using Cake.Common.IO;
+using Cake.Frosting;
+
+namespace Build.Tasks
+{
+ [TaskName("Setup")]
+ public sealed class SetupTask : FrostingTask
+ {
+ public override void Run(BuildContext context)
+ {
+ if (context.DirectoryExists(context.WorkDir))
+ {
+ context.DeleteDirectory(
+ context.WorkDir,
+ new DeleteDirectorySettings { Recursive = true });
+ }
+
+ context.EnsureDirectoryExists(context.WorkDir);
+
+ File.WriteAllLines(
+ context.MakeAbsolute(context.SampleFile).FullPath,
+ new[] { "alpha line 1", "beta line 2", "gamma 42 delta", "alpha line 4" });
+ File.WriteAllText(
+ context.MakeAbsolute(context.SampleFile2).FullPath,
+ "second file with the word alpha");
+
+ context.Information("Setup complete.");
+ }
+ }
+}
diff --git a/demo/frosting/build/Tasks/TouchTask.cs b/demo/frosting/build/Tasks/TouchTask.cs
new file mode 100644
index 0000000..4dd3a50
--- /dev/null
+++ b/demo/frosting/build/Tasks/TouchTask.cs
@@ -0,0 +1,39 @@
+using System;
+using System.IO;
+using System.Threading;
+using Cake.Common.Diagnostics;
+using Cake.Common.IO;
+using Cake.FileHelpers;
+using Cake.Frosting;
+
+namespace Build.Tasks
+{
+ [TaskName("Touch")]
+ [IsDependentOn(typeof(AppendOperationsTask))]
+ public sealed class TouchTask : FrostingTask
+ {
+ public override void Run(BuildContext context)
+ {
+ // FileTouch updates the timestamps of an existing file (it does NOT create).
+ var touchPath = context.MakeAbsolute(context.TouchFile).FullPath;
+ File.WriteAllText(touchPath, string.Empty);
+ var beforeUtc = File.GetLastWriteTimeUtc(touchPath);
+ Thread.Sleep(10);
+ context.FileTouch(context.TouchFile);
+ var afterUtc = File.GetLastWriteTimeUtc(touchPath);
+
+ AssertThat(
+ afterUtc > beforeUtc,
+ $"FileTouch: timestamp did not advance (before={beforeUtc:o}, after={afterUtc:o})");
+ context.Information("FileTouch OK (advanced from {0:o} to {1:o})", beforeUtc, afterUtc);
+ }
+
+ private static void AssertThat(bool condition, string message)
+ {
+ if (!condition)
+ {
+ throw new Exception("Assertion failed: " + message);
+ }
+ }
+ }
+}
diff --git a/demo/frosting/build/Tasks/WriteOperationsTask.cs b/demo/frosting/build/Tasks/WriteOperationsTask.cs
new file mode 100644
index 0000000..863b37d
--- /dev/null
+++ b/demo/frosting/build/Tasks/WriteOperationsTask.cs
@@ -0,0 +1,36 @@
+using System;
+using System.IO;
+using Cake.Common.Diagnostics;
+using Cake.Common.IO;
+using Cake.FileHelpers;
+using Cake.Frosting;
+
+namespace Build.Tasks
+{
+ [TaskName("Write-Operations")]
+ [IsDependentOn(typeof(ReadOperationsTask))]
+ public sealed class WriteOperationsTask : FrostingTask
+ {
+ public override void Run(BuildContext context)
+ {
+ context.FileWriteText(context.WrittenFile, "first text\n");
+ var afterText = File.ReadAllText(context.MakeAbsolute(context.WrittenFile).FullPath);
+ AssertThat(afterText == "first text\n", "FileWriteText: content mismatch");
+ context.Information("FileWriteText OK");
+
+ context.FileWriteLines(context.WrittenFile, new[] { "line A", "line B", "line C" });
+ var afterLines = context.FileReadLines(context.WrittenFile);
+ AssertThat(afterLines.Length == 3, "FileWriteLines: expected 3 lines, got " + afterLines.Length);
+ AssertThat(afterLines[1] == "line B", "FileWriteLines: line 2 mismatch");
+ context.Information("FileWriteLines OK (overwrote with {0} lines)", afterLines.Length);
+ }
+
+ private static void AssertThat(bool condition, string message)
+ {
+ if (!condition)
+ {
+ throw new Exception("Assertion failed: " + message);
+ }
+ }
+ }
+}
diff --git a/demo/script/.config/dotnet-tools.json b/demo/script/.config/dotnet-tools.json
new file mode 100644
index 0000000..94b1cfe
--- /dev/null
+++ b/demo/script/.config/dotnet-tools.json
@@ -0,0 +1,12 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "cake.tool": {
+ "version": "5.1.0",
+ "commands": [
+ "dotnet-cake"
+ ]
+ }
+ }
+}
diff --git a/demo/script/build.ps1 b/demo/script/build.ps1
new file mode 100644
index 0000000..5f683cc
--- /dev/null
+++ b/demo/script/build.ps1
@@ -0,0 +1,13 @@
+$ErrorActionPreference = 'Stop'
+
+Set-Location -LiteralPath $PSScriptRoot
+
+$env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = '1'
+$env:DOTNET_CLI_TELEMETRY_OPTOUT = '1'
+$env:DOTNET_NOLOGO = '1'
+
+dotnet tool restore
+if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+
+dotnet cake filehelpers.cake @args
+if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
diff --git a/demo/script/build.sh b/demo/script/build.sh
new file mode 100644
index 0000000..0c5479e
--- /dev/null
+++ b/demo/script/build.sh
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+set -euox pipefail
+
+cd "$(dirname "${BASH_SOURCE[0]}")"
+
+export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
+export DOTNET_CLI_TELEMETRY_OPTOUT=1
+export DOTNET_NOLOGO=1
+
+dotnet tool restore
+
+dotnet cake filehelpers.cake "$@"
diff --git a/test.cake b/demo/script/filehelpers.cake
similarity index 98%
rename from test.cake
rename to demo/script/filehelpers.cake
index a649d1f..d186a6b 100644
--- a/test.cake
+++ b/demo/script/filehelpers.cake
@@ -1,4 +1,4 @@
-#reference "BuildArtifacts/temp/_PublishedLibraries/Cake.FileHelpers/net9.0/Cake.FileHelpers.dll"
+#reference "../../BuildArtifacts/temp/_PublishedLibraries/Cake.FileHelpers/net8.0/Cake.FileHelpers.dll"
// Self-contained exercise of Cake.FileHelpers aliases against a temp
// working directory. Each task asserts the expected outcome and throws
diff --git a/src/Cake.FileHelpers.Tests/Cake.FileHelpers.Tests.csproj b/src/Cake.FileHelpers.Tests/Cake.FileHelpers.Tests.csproj
index e2f97b7..a0daba1 100644
--- a/src/Cake.FileHelpers.Tests/Cake.FileHelpers.Tests.csproj
+++ b/src/Cake.FileHelpers.Tests/Cake.FileHelpers.Tests.csproj
@@ -1,6 +1,6 @@
- net8.0;net9.0
+ net8.0
diff --git a/src/Cake.FileHelpers/Cake.FileHelpers.csproj b/src/Cake.FileHelpers/Cake.FileHelpers.csproj
index 975c2df..fa38e38 100644
--- a/src/Cake.FileHelpers/Cake.FileHelpers.csproj
+++ b/src/Cake.FileHelpers/Cake.FileHelpers.csproj
@@ -12,7 +12,7 @@
Cake.FileHelpers
Cake build aliases for Reading, Writing, and Replacing Text in files.
Cake Build addon to provide Aliases for common File operations (Reading, Writing, Replacing Text).
- cake;script;build;cake-addin;cake-build;addin
+ cake;cake-build;cake-addin;addin;script;build;filehelpers
Redth
Redth; cake-contrib
Copyright 2017-$([System.DateTime]::Now.Year) - Cake Contributions