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