From 9c46e61bf51d85b0d226b988079b0e08cb87f7cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belin?= Date: Tue, 2 Dec 2025 02:02:25 +0100 Subject: [PATCH 01/26] Update the Git settings --- .gitattributes | 4 ++++ .gitignore | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index c510df3..7fdb9cb 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,16 +5,20 @@ *.cs text diff=csharp *.csproj text *.css text diff=css +*.esproj text *.html text diff=html *.http text *.ini text *.iss text +*.js text *.json text *.md text diff=markdown *.ps1 text +*.psd1 text *.slnx text *.sql text *.svg text +*.ts text *.txt text *.xml text *.yaml text diff --git a/.gitignore b/.gitignore index b4ce5b1..03555ec 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,5 @@ /.idea/ /.vs/ /bin/ -/src/obj/ +/*/obj/ /var/ From 380b58759040a53de350497bef81156ca9075c6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belin?= Date: Tue, 2 Dec 2025 02:05:09 +0100 Subject: [PATCH 02/26] Update the editor settings --- .editorconfig | 20 ++++++++++++++++++++ .vscode/extensions.json | 1 + 2 files changed, 21 insertions(+) diff --git a/.editorconfig b/.editorconfig index 46a2f0f..776fb87 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,6 +9,26 @@ insert_final_newline = true tab_width = 2 trim_trailing_whitespace = true +[*.cs] +csharp_indent_case_contents_when_block = false +csharp_indent_labels = no_change +csharp_new_line_before_open_brace = none +csharp_prefer_braces = false +csharp_space_after_cast = true +csharp_space_before_colon_in_inheritance_clause = false +csharp_style_expression_bodied_constructors = true +csharp_style_expression_bodied_local_functions = true +csharp_style_expression_bodied_methods = true +csharp_style_expression_bodied_operators = true +csharp_style_namespace_declarations = file_scoped +csharp_style_var_elsewhere = true +csharp_style_var_for_built_in_types = true +csharp_style_var_when_type_is_apparent = true +csharp_using_directive_placement = inside_namespace +dotnet_diagnostic.CS8524.severity = silent +dotnet_sort_system_directives_first = false +dotnet_style_namespace_match_folder = false + [*.md] trim_trailing_whitespace = false diff --git a/.vscode/extensions.json b/.vscode/extensions.json index b408c54..e2d0e78 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,5 +1,6 @@ { "recommendations": [ + "ms-dotnettools.csdevkit", "ms-vscode.powershell" ] } From 750eb5d55490dc39563cb23b3ef0e55df34cb74c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belin?= Date: Tue, 2 Dec 2025 02:05:34 +0100 Subject: [PATCH 03/26] Update the "Invoke" script --- Invoke.ps1 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Invoke.ps1 b/Invoke.ps1 index e96d6ee..6be28b1 100755 --- a/Invoke.ps1 +++ b/Invoke.ps1 @@ -5,7 +5,10 @@ param ( param ($commandName, $parameterName, $wordToComplete) (Get-Item "$PSScriptRoot/tool/$wordToComplete*.ps1").BaseName })] - [string] $Command = "Default" + [string] $Command = "Default", + + [Parameter()] + [switch] $Release ) $ErrorActionPreference = "Stop" From 797ab503a2396cab2c072385a7847d6c020ed326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belin?= Date: Tue, 2 Dec 2025 02:05:57 +0100 Subject: [PATCH 04/26] Updated the PowerShell module manifest --- SetupAnt.psd1 | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/SetupAnt.psd1 b/SetupAnt.psd1 index 528d918..8ce783d 100644 --- a/SetupAnt.psd1 +++ b/SetupAnt.psd1 @@ -2,7 +2,7 @@ DefaultCommandPrefix = "Ant" ModuleVersion = "5.1.0" PowerShellVersion = "7.4" - RootModule = "src/Main.psm1" + RootModule = "bin/Belin.SetupAnt.dll" Author = "Cédric Belin " CompanyName = "Cedric-Belin.fr" @@ -11,10 +11,10 @@ GUID = "30b52520-21cd-44c4-aa11-b1f0dc085686" AliasesToExport = @() - CmdletsToExport = @() + FunctionsToExport = @() VariablesToExport = @() - FunctionsToExport = @( + CmdletsToExport = @( "Find-Release" "Get-Release" "Install-Release" @@ -22,11 +22,6 @@ "Test-Release" ) - NestedModules = @( - "src/Release.psm1" - "src/Setup.psm1" - ) - PrivateData = @{ PSData = @{ LicenseUri = "https://github.com/cedx/setup-ant/blob/main/License.md" From 0ec36606266a70c37dbf6a233ab18f1834a2630d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belin?= Date: Tue, 2 Dec 2025 02:14:20 +0100 Subject: [PATCH 05/26] Add the C --- SetupAnt.slnx | 10 +++------- src/SetupAnt.csproj | 18 ++++++++++++++++++ src/SetupAnt.esproj | 7 ------- test/SetupAnt.Tests.csproj | 30 ++++++++++++++++++++++++++++++ test/SetupAnt.Tests.esproj | 7 ------- 5 files changed, 51 insertions(+), 21 deletions(-) create mode 100644 src/SetupAnt.csproj delete mode 100644 src/SetupAnt.esproj create mode 100644 test/SetupAnt.Tests.csproj delete mode 100644 test/SetupAnt.Tests.esproj diff --git a/SetupAnt.slnx b/SetupAnt.slnx index 800db64..0f6bc03 100644 --- a/SetupAnt.slnx +++ b/SetupAnt.slnx @@ -33,12 +33,8 @@ - - - - - - - + + + diff --git a/src/SetupAnt.csproj b/src/SetupAnt.csproj new file mode 100644 index 0000000..29225c2 --- /dev/null +++ b/src/SetupAnt.csproj @@ -0,0 +1,18 @@ + + + Cedric-Belin.fr + © Cédric Belin + Set up your GitHub Actions workflow with a specific version of Apache Ant. + Setup Ant + 1.0.0 + + + + false + Belin.SetupAnt + enable + enable + ../bin + net8.0 + + diff --git a/src/SetupAnt.esproj b/src/SetupAnt.esproj deleted file mode 100644 index a2c426d..0000000 --- a/src/SetupAnt.esproj +++ /dev/null @@ -1,7 +0,0 @@ - - - ../ - false - false - - diff --git a/test/SetupAnt.Tests.csproj b/test/SetupAnt.Tests.csproj new file mode 100644 index 0000000..3bd29ec --- /dev/null +++ b/test/SetupAnt.Tests.csproj @@ -0,0 +1,30 @@ + + + Cedric-Belin.fr + © Cédric Belin + Setup Ant + 1.0.0 + + + + false + Belin.SetupAnt.Tests + obj + enable + enable + ../bin + en + net8.0 + + + + + + + + + + + + + diff --git a/test/SetupAnt.Tests.esproj b/test/SetupAnt.Tests.esproj deleted file mode 100644 index a2c426d..0000000 --- a/test/SetupAnt.Tests.esproj +++ /dev/null @@ -1,7 +0,0 @@ - - - ../ - false - false - - From 42c13c02c495be565de15a6521cd915631eb1f3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belin?= Date: Tue, 2 Dec 2025 02:16:48 +0100 Subject: [PATCH 06/26] Update the build system [skip ci] --- src/SetupAnt.csproj | 2 +- test/SetupAnt.Tests.csproj | 2 +- tool/Version.ps1 | 5 +++++ tool/Watch.ps1 | 3 +++ 4 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 tool/Version.ps1 create mode 100644 tool/Watch.ps1 diff --git a/src/SetupAnt.csproj b/src/SetupAnt.csproj index 29225c2..456f1e3 100644 --- a/src/SetupAnt.csproj +++ b/src/SetupAnt.csproj @@ -4,7 +4,7 @@ © Cédric Belin Set up your GitHub Actions workflow with a specific version of Apache Ant. Setup Ant - 1.0.0 + 5.1.0 diff --git a/test/SetupAnt.Tests.csproj b/test/SetupAnt.Tests.csproj index 3bd29ec..e283fc1 100644 --- a/test/SetupAnt.Tests.csproj +++ b/test/SetupAnt.Tests.csproj @@ -3,7 +3,7 @@ Cedric-Belin.fr © Cédric Belin Setup Ant - 1.0.0 + 5.1.0 diff --git a/tool/Version.ps1 b/tool/Version.ps1 new file mode 100644 index 0000000..b623de3 --- /dev/null +++ b/tool/Version.ps1 @@ -0,0 +1,5 @@ +"Updating the version number in the sources..." +$version = (Import-PowerShellDataFile "SetupAnt.psd1").ModuleVersion +foreach ($item in Get-Item "*/*.csproj") { + (Get-Content $item) -replace "\d+(\.\d+){2}", "$version" | Out-File $item +} diff --git a/tool/Watch.ps1 b/tool/Watch.ps1 new file mode 100644 index 0000000..7582a8b --- /dev/null +++ b/tool/Watch.ps1 @@ -0,0 +1,3 @@ +"Watching for file changes..." +$configuration = $Release ? "Release" : "Debug" +Start-Process dotnet "watch build --configuration $configuration" -NoNewWindow -Wait -WorkingDirectory src From 095513f638bf16b2aa86bca347b5b508327db307 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belin?= Date: Tue, 2 Dec 2025 02:23:21 +0100 Subject: [PATCH 07/26] Update the build system [skip ci] --- SetupAnt.slnx | 5 +++++ test/Main.Tests.ps1 | 2 +- tool/Build.ps1 | 2 ++ tool/Clean.ps1 | 2 ++ tool/Default.ps1 | 2 ++ tool/Format.ps1 | 2 ++ tool/Lint.ps1 | 1 - tool/Outdated.ps1 | 2 ++ 8 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 tool/Build.ps1 create mode 100644 tool/Format.ps1 create mode 100644 tool/Outdated.ps1 diff --git a/SetupAnt.slnx b/SetupAnt.slnx index 0f6bc03..634ddc3 100644 --- a/SetupAnt.slnx +++ b/SetupAnt.slnx @@ -21,13 +21,18 @@ + + + + + diff --git a/test/Main.Tests.ps1 b/test/Main.Tests.ps1 index e923c6a..435fb1b 100644 --- a/test/Main.Tests.ps1 +++ b/test/Main.Tests.ps1 @@ -6,7 +6,7 @@ using namespace System.Diagnostics.CodeAnalysis #> Describe "Main" { BeforeAll { - Import-Module ./SetupAnt.psd1 + Import-Module "$PSScriptRoot/../SetupAnt.psd1" [SuppressMessage("PSUseDeclaredVarsMoreThanAssignments", "")] $existingRelease = New-AntRelease "1.10.15" diff --git a/tool/Build.ps1 b/tool/Build.ps1 new file mode 100644 index 0000000..0b76275 --- /dev/null +++ b/tool/Build.ps1 @@ -0,0 +1,2 @@ +"Building the solution..." +dotnet build --configuration ($Release ? "Release" : "Debug") diff --git a/tool/Clean.ps1 b/tool/Clean.ps1 index 7a9909c..ffc27f0 100644 --- a/tool/Clean.ps1 +++ b/tool/Clean.ps1 @@ -1,2 +1,4 @@ "Deleting all generated files..." +Remove-Item "bin" -ErrorAction Ignore -Force -Recurse +Remove-Item "*/obj" -Force -Recurse Remove-Item "var/*" -Exclude ".gitkeep" -Force -Recurse diff --git a/tool/Default.ps1 b/tool/Default.ps1 index 4e1a44f..b3a7a6a 100644 --- a/tool/Default.ps1 +++ b/tool/Default.ps1 @@ -1 +1,3 @@ & "$PSScriptRoot/Clean.ps1" +& "$PSScriptRoot/Version.ps1" +& "$PSScriptRoot/Build.ps1" diff --git a/tool/Format.ps1 b/tool/Format.ps1 new file mode 100644 index 0000000..d968024 --- /dev/null +++ b/tool/Format.ps1 @@ -0,0 +1,2 @@ +"Formatting the source code..." +dotnet format diff --git a/tool/Lint.ps1 b/tool/Lint.ps1 index db91afb..055a902 100644 --- a/tool/Lint.ps1 +++ b/tool/Lint.ps1 @@ -1,6 +1,5 @@ "Performing the static analysis of source code..." Import-Module PSScriptAnalyzer Invoke-ScriptAnalyzer $PSScriptRoot -Recurse -Invoke-ScriptAnalyzer src -Recurse Invoke-ScriptAnalyzer test -Recurse Test-ModuleManifest SetupAnt.psd1 | Out-Null diff --git a/tool/Outdated.ps1 b/tool/Outdated.ps1 new file mode 100644 index 0000000..be4b30f --- /dev/null +++ b/tool/Outdated.ps1 @@ -0,0 +1,2 @@ +"Checking for outdated dependencies..." +dotnet package list --outdated From 84d210b77da40afb1ef9a44e90b504cf9f4de6b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belin?= Date: Tue, 2 Dec 2025 02:27:50 +0100 Subject: [PATCH 08/26] Update the dependencies [skip ci] --- src/SetupAnt.csproj | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/SetupAnt.csproj b/src/SetupAnt.csproj index 456f1e3..f7689e6 100644 --- a/src/SetupAnt.csproj +++ b/src/SetupAnt.csproj @@ -15,4 +15,12 @@ ../bin net8.0 + + + + + + + + From b821a99e2c36629568d8d037d65ec9a22dbdfd82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belin?= Date: Tue, 2 Dec 2025 02:31:43 +0100 Subject: [PATCH 09/26] Update the dependencies [skip ci] --- src/SetupAnt.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SetupAnt.csproj b/src/SetupAnt.csproj index f7689e6..b21a348 100644 --- a/src/SetupAnt.csproj +++ b/src/SetupAnt.csproj @@ -17,7 +17,7 @@ - + From 623fae429110200eb5959b3b20bc6a051aca0abb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belin?= Date: Tue, 2 Dec 2025 02:38:22 +0100 Subject: [PATCH 10/26] Add the C# class skeletons [skip ci] --- src/Cmdlets/Find-Release.cs | 1 + src/Cmdlets/Get-Release.cs | 1 + src/Cmdlets/Install-Release.cs | 1 + src/Cmdlets/New-Release.cs | 1 + src/Cmdlets/Test-Release.cs | 1 + src/Data.cs | 1 + src/Release.cs | 1 + src/Setup.cs | 1 + 8 files changed, 8 insertions(+) create mode 100644 src/Cmdlets/Find-Release.cs create mode 100644 src/Cmdlets/Get-Release.cs create mode 100644 src/Cmdlets/Install-Release.cs create mode 100644 src/Cmdlets/New-Release.cs create mode 100644 src/Cmdlets/Test-Release.cs create mode 100644 src/Data.cs create mode 100644 src/Release.cs create mode 100644 src/Setup.cs diff --git a/src/Cmdlets/Find-Release.cs b/src/Cmdlets/Find-Release.cs new file mode 100644 index 0000000..d960930 --- /dev/null +++ b/src/Cmdlets/Find-Release.cs @@ -0,0 +1 @@ +namespace Belin.SetupAnt.Cmdlets; diff --git a/src/Cmdlets/Get-Release.cs b/src/Cmdlets/Get-Release.cs new file mode 100644 index 0000000..d960930 --- /dev/null +++ b/src/Cmdlets/Get-Release.cs @@ -0,0 +1 @@ +namespace Belin.SetupAnt.Cmdlets; diff --git a/src/Cmdlets/Install-Release.cs b/src/Cmdlets/Install-Release.cs new file mode 100644 index 0000000..d960930 --- /dev/null +++ b/src/Cmdlets/Install-Release.cs @@ -0,0 +1 @@ +namespace Belin.SetupAnt.Cmdlets; diff --git a/src/Cmdlets/New-Release.cs b/src/Cmdlets/New-Release.cs new file mode 100644 index 0000000..d960930 --- /dev/null +++ b/src/Cmdlets/New-Release.cs @@ -0,0 +1 @@ +namespace Belin.SetupAnt.Cmdlets; diff --git a/src/Cmdlets/Test-Release.cs b/src/Cmdlets/Test-Release.cs new file mode 100644 index 0000000..d960930 --- /dev/null +++ b/src/Cmdlets/Test-Release.cs @@ -0,0 +1 @@ +namespace Belin.SetupAnt.Cmdlets; diff --git a/src/Data.cs b/src/Data.cs new file mode 100644 index 0000000..96416a9 --- /dev/null +++ b/src/Data.cs @@ -0,0 +1 @@ +namespace Belin.SetupAnt; diff --git a/src/Release.cs b/src/Release.cs new file mode 100644 index 0000000..96416a9 --- /dev/null +++ b/src/Release.cs @@ -0,0 +1 @@ +namespace Belin.SetupAnt; diff --git a/src/Setup.cs b/src/Setup.cs new file mode 100644 index 0000000..96416a9 --- /dev/null +++ b/src/Setup.cs @@ -0,0 +1 @@ +namespace Belin.SetupAnt; From fc241ac179ac24ad5f078e93810a35c445e96c77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belin?= Date: Tue, 2 Dec 2025 11:01:30 +0100 Subject: [PATCH 11/26] Add the cmdlet skeletons --- src/Cmdlets/Find-Release.cs | 7 +++++++ src/Cmdlets/Get-Release.cs | 7 +++++++ src/Cmdlets/Install-Release.cs | 7 +++++++ src/Cmdlets/New-Release.cs | 7 +++++++ src/Cmdlets/Test-Release.cs | 7 +++++++ 5 files changed, 35 insertions(+) diff --git a/src/Cmdlets/Find-Release.cs b/src/Cmdlets/Find-Release.cs index d960930..5171f18 100644 --- a/src/Cmdlets/Find-Release.cs +++ b/src/Cmdlets/Find-Release.cs @@ -1 +1,8 @@ namespace Belin.SetupAnt.Cmdlets; + +/// +/// Finds a release that matches the specified version constraint. +/// +public class FindReleaseCommand: Cmdlet { + +} diff --git a/src/Cmdlets/Get-Release.cs b/src/Cmdlets/Get-Release.cs index d960930..f816cf9 100644 --- a/src/Cmdlets/Get-Release.cs +++ b/src/Cmdlets/Get-Release.cs @@ -1 +1,8 @@ namespace Belin.SetupAnt.Cmdlets; + +/// +/// Gets the release corresponding to the specified version. +/// +public class GetReleaseCommand: Cmdlet { + +} diff --git a/src/Cmdlets/Install-Release.cs b/src/Cmdlets/Install-Release.cs index d960930..eea0224 100644 --- a/src/Cmdlets/Install-Release.cs +++ b/src/Cmdlets/Install-Release.cs @@ -1 +1,8 @@ namespace Belin.SetupAnt.Cmdlets; + +/// +/// Installs Apache Ant, after downloading it. +/// +public class InstallReleaseCommand: Cmdlet { + +} diff --git a/src/Cmdlets/New-Release.cs b/src/Cmdlets/New-Release.cs index d960930..211f588 100644 --- a/src/Cmdlets/New-Release.cs +++ b/src/Cmdlets/New-Release.cs @@ -1 +1,8 @@ namespace Belin.SetupAnt.Cmdlets; + +/// +/// Creates a new release. +/// +public class NewReleaseCommand: Cmdlet { + +} diff --git a/src/Cmdlets/Test-Release.cs b/src/Cmdlets/Test-Release.cs index d960930..0bb2239 100644 --- a/src/Cmdlets/Test-Release.cs +++ b/src/Cmdlets/Test-Release.cs @@ -1 +1,8 @@ namespace Belin.SetupAnt.Cmdlets; + +/// +/// Gets a value indicating whether a release with the specified version exists. +/// +public class TestReleaseCommand: Cmdlet { + +} From 2e769f4bea8ceb303509701eb90cf1d8ef003117 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belin?= Date: Tue, 2 Dec 2025 11:13:27 +0100 Subject: [PATCH 12/26] Port the release data --- src/{ => Cmdlets}/Main.psm1 | 4 +-- src/Data.cs | 1 - src/Data.psd1 | 52 ------------------------------- src/Release.Data.cs | 61 +++++++++++++++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 55 deletions(-) rename src/{ => Cmdlets}/Main.psm1 (94%) delete mode 100644 src/Data.cs delete mode 100644 src/Data.psd1 create mode 100644 src/Release.Data.cs diff --git a/src/Main.psm1 b/src/Cmdlets/Main.psm1 similarity index 94% rename from src/Main.psm1 rename to src/Cmdlets/Main.psm1 index e70392c..369c68f 100644 --- a/src/Main.psm1 +++ b/src/Cmdlets/Main.psm1 @@ -10,7 +10,7 @@ using module ./Setup.psm1 .INPUTS A string that contains a version constraint. .OUTPUTS - The release corresponding to the specified constraint, or `$null` if not found. + The release corresponding to the specified constraint, or if not found. #> function Find-Release { [CmdletBinding()] @@ -33,7 +33,7 @@ function Find-Release { .INPUTS A string that contains a version number. .OUTPUTS - The release corresponding to the specified version, or `$null` if not found. + The release corresponding to the specified version, or if not found. #> function Get-Release { [CmdletBinding()] diff --git a/src/Data.cs b/src/Data.cs deleted file mode 100644 index 96416a9..0000000 --- a/src/Data.cs +++ /dev/null @@ -1 +0,0 @@ -namespace Belin.SetupAnt; diff --git a/src/Data.psd1 b/src/Data.psd1 deleted file mode 100644 index bb8e9a0..0000000 --- a/src/Data.psd1 +++ /dev/null @@ -1,52 +0,0 @@ -@{ - Releases = @( - "1.10.15" - "1.10.14" - "1.10.13" - "1.10.12" - "1.10.11" - "1.10.10" - "1.10.9" - "1.10.8" - "1.10.7" - "1.10.6" - "1.10.5" - "1.10.4" - "1.10.3" - "1.10.2" - "1.10.1" - "1.10.0" - "1.9.16" - "1.9.15" - "1.9.14" - "1.9.13" - "1.9.12" - "1.9.11" - "1.9.10" - "1.9.9" - "1.9.8" - "1.9.7" - "1.9.6" - "1.9.5" - "1.9.4" - "1.9.3" - "1.9.2" - "1.9.1" - "1.9.0" - "1.8.4" - "1.8.3" - "1.8.2" - "1.8.1" - "1.8.0" - "1.7.1" - "1.7.0" - "1.6.5" - "1.6.4" - "1.6.3" - "1.6.2" - "1.6.1" - "1.6.0" - "1.5.4" - "1.5.2" - ) -} diff --git a/src/Release.Data.cs b/src/Release.Data.cs new file mode 100644 index 0000000..cf28678 --- /dev/null +++ b/src/Release.Data.cs @@ -0,0 +1,61 @@ +namespace Belin.SetupAnt; + +/// +/// Represents an Apache Ant release. +/// +public partial class Release { + + /// + /// The list of all releases. + /// + private static readonly Release[] data = [ + new Release("1.10.15"), + new Release("1.10.14"), + new Release("1.10.13"), + new Release("1.10.12"), + new Release("1.10.11"), + new Release("1.10.10"), + new Release("1.10.9"), + new Release("1.10.8"), + new Release("1.10.7"), + new Release("1.10.6"), + new Release("1.10.5"), + new Release("1.10.4"), + new Release("1.10.3"), + new Release("1.10.2"), + new Release("1.10.1"), + new Release("1.10.0"), + new Release("1.9.16"), + new Release("1.9.15"), + new Release("1.9.14"), + new Release("1.9.13"), + new Release("1.9.12"), + new Release("1.9.11"), + new Release("1.9.10"), + new Release("1.9.9"), + new Release("1.9.8"), + new Release("1.9.7"), + new Release("1.9.6"), + new Release("1.9.5"), + new Release("1.9.4"), + new Release("1.9.3"), + new Release("1.9.2"), + new Release("1.9.1"), + new Release("1.9.0"), + new Release("1.8.4"), + new Release("1.8.3"), + new Release("1.8.2"), + new Release("1.8.1"), + new Release("1.8.0"), + new Release("1.7.1"), + new Release("1.7.0"), + new Release("1.6.5"), + new Release("1.6.4"), + new Release("1.6.3"), + new Release("1.6.2"), + new Release("1.6.1"), + new Release("1.6.0"), + new Release("1.5.4"), + new Release("1.5.2") + ]; +} From 4e57a91ef0b986ea617573ef1955105534d606fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belin?= Date: Tue, 2 Dec 2025 11:13:42 +0100 Subject: [PATCH 13/26] Port the `Release` class --- src/Release.cs | 69 ++++++++++++++++++++++++++++++ src/Release.psm1 | 107 ----------------------------------------------- 2 files changed, 69 insertions(+), 107 deletions(-) delete mode 100644 src/Release.psm1 diff --git a/src/Release.cs b/src/Release.cs index 96416a9..9e5ad2a 100644 --- a/src/Release.cs +++ b/src/Release.cs @@ -1 +1,70 @@ namespace Belin.SetupAnt; + +using System.Text.RegularExpressions; + +/// +/// Represents an Apache Ant release. +/// +/// The version number. +public partial class Release(Version version) { + + /// + /// The latest release. + /// + public static Release Latest => data.First(); + + /// + /// The download URL. + /// + public Uri Url => new($"https://archive.apache.org/dist/ant/binaries/apache-ant-{Version}-bin.zip"); + + /// + /// The version number. + /// + public Version Version => version; + + /// + /// Creates a new release. + /// + /// The version number. + public Release(string version): this(Version.Parse(version)) {} + + /// + /// Finds a release that matches the specified version constraint. + /// + /// The version constraint. + /// The release corresponding to the specified constraint, or if not found. + /// The version constraint is invalid. + public static Release? Find(string constraint) { + var opPattern = new Regex(@"^([^\d]+)\d"); + var (op, version) = true switch { + true when Regex.IsMatch(constraint, @"^(\*|latest)$", RegexOptions.IgnoreCase) => ("=", Latest.Version), + true when opPattern.IsMatch(constraint) => (opPattern.Match(constraint).Groups[1].Value, Version.Parse(Regex.Replace(constraint, @"^[^\d]+", ""))), + true when Regex.IsMatch(constraint, @"^\d") => ("=", Version.Parse(constraint)), + _ => throw new FormatException("The version constraint is invalid.") + }; + + return data.FirstOrDefault(op switch { + ">" => release => release.Version > version, + ">=" => release => release.Version >= version, + "=" => release => release.Version == version, + "<=" => release => release.Version <= version, + "<" => release => release.Version < version, + _ => throw new FormatException("The version constraint is invalid.") + }); + } + + /// + /// Gets the release corresponding to the specified version. + /// + /// The version number of a release. + /// The release corresponding to the specified version, or if not found. + public static Release? Get(string version) => Get(Version.Parse(version)); + + /// + /// Gets the release corresponding to the specified version. + /// + /// The version number of a release. + /// The release corresponding to the specified version, or if not found. + public static Release? Get(Version version) => data.SingleOrDefault(release => release.Version == version); +} diff --git a/src/Release.psm1 b/src/Release.psm1 deleted file mode 100644 index 0e9a265..0000000 --- a/src/Release.psm1 +++ /dev/null @@ -1,107 +0,0 @@ -<# -.SYNOPSIS - Represents an Apache Ant release. -#> -class Release { - - <# - .SYNOPSIS - The list of all releases. - #> - hidden static [Release[]] $Data - - <# - .SYNOPSIS - The version number. - #> - [ValidateNotNull()] - [semver] $Version - - <# - .SYNOPSIS - Creates a new release. - .PARAMETER Version - The version number. - #> - Release([string] $Version) { - $this.Version = $Version - } - - <# - .SYNOPSIS - Initializes the class. - #> - static Release() { - [Release]::Data = (Import-PowerShellDataFile "$PSScriptRoot/Data.psd1").Releases.ForEach{ [Release] $_ } - } - - <# - .SYNOPSIS - Gets a value indicating whether this release exists. - .OUTPUTS - `$true` if this release exists, otherwise `$false`. - #> - [bool] Exists() { - return $null -ne [Release]::Get($this.Version) - } - - <# - .SYNOPSIS - Gets the download URL. - .OUTPUTS - The download URL. - #> - [uri] Url() { - return "https://archive.apache.org/dist/ant/binaries/apache-ant-$($this.Version)-bin.zip" - } - - <# - .SYNOPSIS - Finds a release that matches the specified version constraint. - .PARAMETER Constraint - The version constraint. - .OUTPUTS - The release corresponding to the specified constraint, or `$null` if not found. - #> - static [Release] Find([string] $Constraint) { - $operator, $semver = switch -Regex ($Constraint) { - "^(\*|latest)$" { "=", [Release]::Latest().Version; break } - "^([^\d]+)\d" { $Matches[1], [semver] ($Constraint -replace "^[^\d]+", ""); break } - "^\d" { ">=", [semver] $Constraint; break } - default { throw [FormatException] "The version constraint is invalid." } - } - - $predicate = switch ($operator) { - ">=" { { $_.Version -ge $semver }; break } - ">" { { $_.Version -gt $semver }; break } - "<=" { { $_.Version -le $semver }; break } - "<" { { $_.Version -lt $semver }; break } - "=" { { $_.Version -eq $semver }; break } - default { throw [FormatException] "The version constraint is invalid." } - } - - return [Release]::Data.Where($predicate)[0] - } - - <# - .SYNOPSIS - Gets the release corresponding to the specified version. - .PARAMETER Version - The version number of a release. - .OUTPUTS - The release corresponding to the specified version, or `$null` if not found. - #> - static [Release] Get([string] $Version) { - return [Release]::Data.Where({ $_.Version -eq $Version }, "First")[0] - } - - <# - .SYNOPSIS - Gets the latest release. - .OUTPUTS - The latest release, or `$null` if not found. - #> - static [Release] Latest() { - return [Release]::Data[0] - } -} From 1de684b691c67baa4d441f71dda2fae2c704ac4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belin?= Date: Tue, 2 Dec 2025 11:13:55 +0100 Subject: [PATCH 14/26] Update the tests [skip ci] --- test/AssemblyInfo.cs | 1 + test/{ => Cmdlets}/Main.Tests.ps1 | 0 2 files changed, 1 insertion(+) create mode 100644 test/AssemblyInfo.cs rename test/{ => Cmdlets}/Main.Tests.ps1 (100%) diff --git a/test/AssemblyInfo.cs b/test/AssemblyInfo.cs new file mode 100644 index 0000000..300f5b1 --- /dev/null +++ b/test/AssemblyInfo.cs @@ -0,0 +1 @@ +[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)] diff --git a/test/Main.Tests.ps1 b/test/Cmdlets/Main.Tests.ps1 similarity index 100% rename from test/Main.Tests.ps1 rename to test/Cmdlets/Main.Tests.ps1 From 7118b97e1b07fce1889f616b6e221c7bc3023e2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belin?= Date: Tue, 2 Dec 2025 12:36:48 +0100 Subject: [PATCH 15/26] Update the test settings [skip ci] --- .runsettings | 5 +++++ SetupAnt.slnx | 1 + tool/Test.ps1 | 3 ++- 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 .runsettings diff --git a/.runsettings b/.runsettings new file mode 100644 index 0000000..5aaedf1 --- /dev/null +++ b/.runsettings @@ -0,0 +1,5 @@ + + + var/TestResults + + diff --git a/SetupAnt.slnx b/SetupAnt.slnx index 634ddc3..e1985c4 100644 --- a/SetupAnt.slnx +++ b/SetupAnt.slnx @@ -3,6 +3,7 @@ + diff --git a/tool/Test.ps1 b/tool/Test.ps1 index 226e684..e102065 100644 --- a/tool/Test.ps1 +++ b/tool/Test.ps1 @@ -1,6 +1,7 @@ "Running the test suite..." +dotnet test --settings .runsettings pwsh -Command { Import-Module Pester - Invoke-Pester test + # Invoke-Pester test exit $LASTEXITCODE } From dc1741e1c4cfd18568b02b2b21b35654b7ef6f98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belin?= Date: Tue, 2 Dec 2025 16:14:50 +0100 Subject: [PATCH 16/26] Update the Git attributes [skip ci] --- .gitattributes | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index 7fdb9cb..c16a191 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,7 @@ * text=auto .* text +*.cjs text *.config text *.cs text diff=csharp *.csproj text @@ -9,17 +10,21 @@ *.html text diff=html *.http text *.ini text -*.iss text *.js text *.json text *.md text diff=markdown +*.mjs text +*.php text diff=php *.ps1 text *.psd1 text +*.razor text +*.scss text diff=css *.slnx text *.sql text *.svg text *.ts text *.txt text +*.webmanifest text *.xml text *.yaml text *.yml text From f10e2524920f7d5a00abce847b143f5d54aaee9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belin?= Date: Tue, 2 Dec 2025 20:24:46 +0100 Subject: [PATCH 17/26] Add the tests of the `Release` class --- src/Release.cs | 71 ++++++++++++++++++++++++++++++++++++------- src/SetupAnt.csproj | 3 +- test/Release.Tests.cs | 55 +++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 12 deletions(-) create mode 100644 test/Release.Tests.cs diff --git a/src/Release.cs b/src/Release.cs index 9e5ad2a..862f44a 100644 --- a/src/Release.cs +++ b/src/Release.cs @@ -1,22 +1,33 @@ namespace Belin.SetupAnt; +using System.Linq; using System.Text.RegularExpressions; /// /// Represents an Apache Ant release. /// /// The version number. -public partial class Release(Version version) { +public partial class Release(Version version): IEquatable { /// /// The latest release. /// public static Release Latest => data.First(); + /// + /// Value indicating whether this release exists. + /// + public bool Exists => data.Any(release => release == this); + /// /// The download URL. /// - public Uri Url => new($"https://archive.apache.org/dist/ant/binaries/apache-ant-{Version}-bin.zip"); + public Uri Url { + get { + var baseUrl = new Uri(this == Latest ? "https://downloads.apache.org/ant/binaries/": "https://archive.apache.org/dist/ant/binaries/"); + return new(baseUrl, $"apache-ant-{Version}-bin.zip"); + } + } /// /// The version number. @@ -29,6 +40,23 @@ public partial class Release(Version version) { /// The version number. public Release(string version): this(Version.Parse(version)) {} + /// + /// Determines whether the two specified objects are equal. + /// + /// The first object. + /// The second object. + /// if object1 equals object2, otherwise . + public static bool operator ==(Release? object1, Release? object2) => + object1 is null ? object2 is null : (ReferenceEquals(object1, object2) || object1.Equals(object2)); + + /// + /// Determines whether the two specified objects are not equal. + /// + /// The first object. + /// The second object. + /// if object1 does not equal object2, otherwise . + public static bool operator !=(Release? object1, Release? object2) => !(object1 == object2); + /// /// Finds a release that matches the specified version constraint. /// @@ -36,20 +64,21 @@ public Release(string version): this(Version.Parse(version)) {} /// The release corresponding to the specified constraint, or if not found. /// The version constraint is invalid. public static Release? Find(string constraint) { - var opPattern = new Regex(@"^([^\d]+)\d"); + var match = Regex.Match(constraint, @"^([^\d]+)\d"); var (op, version) = true switch { - true when Regex.IsMatch(constraint, @"^(\*|latest)$", RegexOptions.IgnoreCase) => ("=", Latest.Version), - true when opPattern.IsMatch(constraint) => (opPattern.Match(constraint).Groups[1].Value, Version.Parse(Regex.Replace(constraint, @"^[^\d]+", ""))), - true when Regex.IsMatch(constraint, @"^\d") => ("=", Version.Parse(constraint)), + true when Regex.IsMatch(constraint, @"^(\*|latest)$", RegexOptions.IgnoreCase) => ("=", Latest.Version.ToString()), + true when match.Success => (match.Groups[1].Value, Regex.Replace(constraint, @"^[^\d]+", "")), + true when Regex.IsMatch(constraint, @"^\d") => (">=", constraint), _ => throw new FormatException("The version constraint is invalid.") }; + var semver = SemanticVersion.Parse(version); return data.FirstOrDefault(op switch { - ">" => release => release.Version > version, - ">=" => release => release.Version >= version, - "=" => release => release.Version == version, - "<=" => release => release.Version <= version, - "<" => release => release.Version < version, + ">" => release => new SemanticVersion(release.Version) > semver, + ">=" => release => new SemanticVersion(release.Version) >= semver, + "=" => release => new SemanticVersion(release.Version) == semver, + "<=" => release => new SemanticVersion(release.Version) <= semver, + "<" => release => new SemanticVersion(release.Version) < semver, _ => throw new FormatException("The version constraint is invalid.") }); } @@ -67,4 +96,24 @@ true when Regex.IsMatch(constraint, @"^\d") => ("=", Version.Parse(constraint)), /// The version number of a release. /// The release corresponding to the specified version, or if not found. public static Release? Get(Version version) => data.SingleOrDefault(release => release.Version == version); + + /// + /// Determines whether the specified object is equal to this object. + /// + /// An object to compare with this object. + /// if the specified object is equal to this object, otherwise . + public override bool Equals(object? other) => Equals(other as Release); + + /// + /// Determines whether the specified object is equal to this object. + /// + /// An object to compare with this object. + /// if the specified object is equal to this object, otherwise . + public bool Equals(Release? other) => other is not null && Version == other.Version; + + /// + /// Gets the hash code for this object. + /// + /// The hash code for this object. + public override int GetHashCode() => HashCode.Combine(Version); } diff --git a/src/SetupAnt.csproj b/src/SetupAnt.csproj index b21a348..b2b5ec9 100644 --- a/src/SetupAnt.csproj +++ b/src/SetupAnt.csproj @@ -10,6 +10,7 @@ false Belin.SetupAnt + true enable enable ../bin @@ -17,7 +18,7 @@ - + diff --git a/test/Release.Tests.cs b/test/Release.Tests.cs new file mode 100644 index 0000000..c4d9168 --- /dev/null +++ b/test/Release.Tests.cs @@ -0,0 +1,55 @@ +namespace Belin.SetupAnt; + +/// +/// Tests the features of the class. +/// +/// The test context. +[TestClass] +public sealed class ReleaseTests { + + /// + /// A release that exists. + /// + private readonly Release existingRelease = new("1.10.15"); + + /// + /// A release that does not exist. + /// + private readonly Release nonExistingRelease = new("666.6.6"); + + [TestMethod] + public void Exists() { + IsTrue(existingRelease.Exists); + IsFalse(nonExistingRelease.Exists); + } + + [TestMethod] + public void Url() { + AreEqual(new Uri("https://downloads.apache.org/ant/binaries/apache-ant-1.10.15-bin.zip"), existingRelease.Url); + AreEqual(new Uri("https://archive.apache.org/dist/ant/binaries/apache-ant-666.6.6-bin.zip"), nonExistingRelease.Url); + } + + [TestMethod] + public void Find() { + IsNull(Release.Find(nonExistingRelease.Version.ToString())); + IsNull(Release.Find("2")); + IsNull(Release.Find(">1.10.15")); + + AreEqual(Release.Latest, Release.Find("latest")); + AreEqual(Release.Latest, Release.Find("*")); + AreEqual(Release.Latest, Release.Find("1")); + + AreEqual(new Release("1.8.2"), Release.Find("=1.8.2")); + AreEqual(new Release("1.9.16"), Release.Find("<1.10")); + AreEqual(new Release("1.10.0"), Release.Find("<=1.10")); + + Throws(() => Release.Find("abc")); + Throws(() => Release.Find("?1.10")); + } + + [TestMethod] + public void Get() { + IsNull(Release.Get(nonExistingRelease.Version)); + AreEqual(Release.Get("1.8.2")?.Version, Version.Parse("1.8.2")); + } +} From 58fc6ca0a648bc5b65b0330e2602908e8848730d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belin?= Date: Tue, 2 Dec 2025 21:18:45 +0100 Subject: [PATCH 18/26] Port the `New-Release` cmdlet --- src/Cmdlets/Main.psm1 | 23 ----------------------- src/Cmdlets/New-Release.cs | 12 ++++++++++++ 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/src/Cmdlets/Main.psm1 b/src/Cmdlets/Main.psm1 index 369c68f..b15645e 100644 --- a/src/Cmdlets/Main.psm1 +++ b/src/Cmdlets/Main.psm1 @@ -2,29 +2,6 @@ using namespace System.Diagnostics.CodeAnalysis using module ./Release.psm1 using module ./Setup.psm1 -<# -.SYNOPSIS - Finds a release that matches the specified version constraint. -.PARAMETER Constraint - The version constraint. -.INPUTS - A string that contains a version constraint. -.OUTPUTS - The release corresponding to the specified constraint, or if not found. -#> -function Find-Release { - [CmdletBinding()] - [OutputType([Release])] - param ( - [Parameter(Mandatory, Position = 0, ValueFromPipeline)] - [string] $Constraint - ) - - process { - [Release]::Find($Constraint) - } -} - <# .SYNOPSIS Gets the release corresponding to the specified version. diff --git a/src/Cmdlets/New-Release.cs b/src/Cmdlets/New-Release.cs index 211f588..b9d9783 100644 --- a/src/Cmdlets/New-Release.cs +++ b/src/Cmdlets/New-Release.cs @@ -3,6 +3,18 @@ namespace Belin.SetupAnt.Cmdlets; /// /// Creates a new release. /// +[Cmdlet(VerbsCommon.New, "Release")] +[OutputType(typeof(Release))] public class NewReleaseCommand: Cmdlet { + /// + /// The version number. + /// + [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true)] + public required Version Version { get; set; } + + /// + /// Performs execution of this command. + /// + protected override void ProcessRecord() => WriteObject(new Release(Version)); } From 09633d96843599dd8028d77ee3102b8b1ce8f116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belin?= Date: Tue, 2 Dec 2025 21:24:11 +0100 Subject: [PATCH 19/26] Port the `Test-Release` cmdlet --- src/Cmdlets/Main.psm1 | 31 ------------------------------- src/Cmdlets/Test-Release.cs | 23 ++++++++++++++++++++++- 2 files changed, 22 insertions(+), 32 deletions(-) diff --git a/src/Cmdlets/Main.psm1 b/src/Cmdlets/Main.psm1 index b15645e..45b41a7 100644 --- a/src/Cmdlets/Main.psm1 +++ b/src/Cmdlets/Main.psm1 @@ -84,34 +84,3 @@ function New-Release { [Release]::new($Version) } } - -<# -.SYNOPSIS - Gets a value indicating whether a release with the specified version exists. -.PARAMETER Version - The version number of the release to be tested. -.PARAMETER InputObject - The instance of the release to be tested. -.INPUTS - [string] A string that contains a version number. -.INPUTS - [Release] An instance of the `Release` class to be tested. -.OUTPUTS - `$true` if a release with the specified version exists, otherwise `$false`. -#> -function Test-Release { - [CmdletBinding(DefaultParameterSetName = "Version")] - [OutputType([bool])] - param ( - [Parameter(Mandatory, ParameterSetName = "Version", Position = 0, ValueFromPipeline)] - [string] $Version, - - [Parameter(Mandatory, ParameterSetName = "InputObject", ValueFromPipeline)] - [Release] $InputObject - ) - - process { - $release = $PSCmdlet.ParameterSetName -eq "InputObject" ? $InputObject : [Release]::new($Version) - $release.Exists() - } -} diff --git a/src/Cmdlets/Test-Release.cs b/src/Cmdlets/Test-Release.cs index 0bb2239..0b98152 100644 --- a/src/Cmdlets/Test-Release.cs +++ b/src/Cmdlets/Test-Release.cs @@ -3,6 +3,27 @@ namespace Belin.SetupAnt.Cmdlets; /// /// Gets a value indicating whether a release with the specified version exists. /// -public class TestReleaseCommand: Cmdlet { +[Cmdlet(VerbsDiagnostic.Test, "Release", DefaultParameterSetName = "Version")] +[OutputType(typeof(bool))] +public class TestReleaseCommand: PSCmdlet { + /// + /// The version number of the release to be tested. + /// + [Parameter(Mandatory = true, ParameterSetName = "InputObject", ValueFromPipeline = true)] + public required Release InputObject { get; set; } + + /// + /// The version number of the release to be tested. + /// + [Parameter(Mandatory = true, ParameterSetName = "Version", Position = 0, ValueFromPipeline = true)] + public required Version Version { get; set; } + + /// + /// Performs execution of this command. + /// + protected override void ProcessRecord() { + var release = ParameterSetName == "InputObject" ? InputObject : new Release(Version); + WriteObject(release.Exists); + } } From 58ebf1e321cb8c2c5088e4742d1914e64ac26895 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belin?= Date: Tue, 2 Dec 2025 21:43:02 +0100 Subject: [PATCH 20/26] Port the `Get-Release` cmdlet --- src/Cmdlets/Get-Release.cs | 16 ++++++++++++++++ src/Cmdlets/Main.psm1 | 23 ----------------------- src/Release.cs | 15 +++++++++++---- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/Cmdlets/Get-Release.cs b/src/Cmdlets/Get-Release.cs index f816cf9..33f3646 100644 --- a/src/Cmdlets/Get-Release.cs +++ b/src/Cmdlets/Get-Release.cs @@ -1,8 +1,24 @@ namespace Belin.SetupAnt.Cmdlets; +using System.Data; +using System.Text.RegularExpressions; + /// /// Gets the release corresponding to the specified version. /// +[Cmdlet(VerbsCommon.Get, "Release")] +[OutputType(typeof(Release))] public class GetReleaseCommand: Cmdlet { + /// + /// The version number. Use `*` or `Latest` to get the latest release. + /// + [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true)] + public required string Version { get; set; } + + /// + /// Performs execution of this command. + /// + protected override void ProcessRecord() => + WriteObject(Release.LatestReleasePattern().IsMatch(Version) ? Release.Latest : Release.Get(Version)); } diff --git a/src/Cmdlets/Main.psm1 b/src/Cmdlets/Main.psm1 index 45b41a7..65aa691 100644 --- a/src/Cmdlets/Main.psm1 +++ b/src/Cmdlets/Main.psm1 @@ -2,29 +2,6 @@ using namespace System.Diagnostics.CodeAnalysis using module ./Release.psm1 using module ./Setup.psm1 -<# -.SYNOPSIS - Gets the release corresponding to the specified version. -.PARAMETER Version - The version number. Use `*` or `Latest` to get the latest release. -.INPUTS - A string that contains a version number. -.OUTPUTS - The release corresponding to the specified version, or if not found. -#> -function Get-Release { - [CmdletBinding()] - [OutputType([Release])] - param ( - [Parameter(Mandatory, Position = 0, ValueFromPipeline)] - [string] $Version - ) - - process { - $Version -in "*", "Latest" ? [Release]::Latest() : [Release]::Get($Version) - } -} - <# .SYNOPSIS Installs Apache Ant, after downloading it. diff --git a/src/Release.cs b/src/Release.cs index 862f44a..94965d9 100644 --- a/src/Release.cs +++ b/src/Release.cs @@ -14,6 +14,13 @@ public partial class Release(Version version): IEquatable { /// public static Release Latest => data.First(); + /// + /// Gets the regular expression used to check if a version number represents the latest release. + /// + /// The regular expression used to check if a version number represents the latest release. + [GeneratedRegex(@"^(\*|latest)$", RegexOptions.IgnoreCase)] + internal static partial Regex LatestReleasePattern(); + /// /// Value indicating whether this release exists. /// @@ -47,7 +54,7 @@ public Release(string version): this(Version.Parse(version)) {} /// The second object. /// if object1 equals object2, otherwise . public static bool operator ==(Release? object1, Release? object2) => - object1 is null ? object2 is null : (ReferenceEquals(object1, object2) || object1.Equals(object2)); + object1 is null ? object2 is null : ReferenceEquals(object1, object2) || object1.Equals(object2); /// /// Determines whether the two specified objects are not equal. @@ -64,10 +71,10 @@ public Release(string version): this(Version.Parse(version)) {} /// The release corresponding to the specified constraint, or if not found. /// The version constraint is invalid. public static Release? Find(string constraint) { - var match = Regex.Match(constraint, @"^([^\d]+)\d"); + var operatorMatch = Regex.Match(constraint, @"^([^\d]+)\d"); var (op, version) = true switch { - true when Regex.IsMatch(constraint, @"^(\*|latest)$", RegexOptions.IgnoreCase) => ("=", Latest.Version.ToString()), - true when match.Success => (match.Groups[1].Value, Regex.Replace(constraint, @"^[^\d]+", "")), + true when LatestReleasePattern().IsMatch(constraint) => ("=", Latest.Version.ToString()), + true when operatorMatch.Success => (operatorMatch.Groups[1].Value, Regex.Replace(constraint, @"^[^\d]+", "")), true when Regex.IsMatch(constraint, @"^\d") => (">=", constraint), _ => throw new FormatException("The version constraint is invalid.") }; From 356b15d10251553018bbb989f6b1291f037640b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belin?= Date: Tue, 2 Dec 2025 21:48:04 +0100 Subject: [PATCH 21/26] Port the `Find-Release` cmdlet --- src/Cmdlets/Find-Release.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Cmdlets/Find-Release.cs b/src/Cmdlets/Find-Release.cs index 5171f18..90d264a 100644 --- a/src/Cmdlets/Find-Release.cs +++ b/src/Cmdlets/Find-Release.cs @@ -3,6 +3,18 @@ namespace Belin.SetupAnt.Cmdlets; /// /// Finds a release that matches the specified version constraint. /// +[Cmdlet(VerbsCommon.Find, "Release")] +[OutputType(typeof(Release))] public class FindReleaseCommand: Cmdlet { + /// + /// The version constraint. + /// + [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true)] + public required string Constraint { get; set; } + + /// + /// Performs execution of this command. + /// + protected override void ProcessRecord() => WriteObject(Release.Find(Constraint)); } From f3ff8c30778f279dbbf8afc446ea623506640359 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belin?= Date: Tue, 2 Dec 2025 21:51:36 +0100 Subject: [PATCH 22/26] Port the `Install-Release` cmdlet --- src/Cmdlets/Install-Release.cs | 29 +++++++++++++++- src/Cmdlets/Main.psm1 | 63 ---------------------------------- 2 files changed, 28 insertions(+), 64 deletions(-) delete mode 100644 src/Cmdlets/Main.psm1 diff --git a/src/Cmdlets/Install-Release.cs b/src/Cmdlets/Install-Release.cs index eea0224..209ed1b 100644 --- a/src/Cmdlets/Install-Release.cs +++ b/src/Cmdlets/Install-Release.cs @@ -3,6 +3,33 @@ namespace Belin.SetupAnt.Cmdlets; /// /// Installs Apache Ant, after downloading it. /// -public class InstallReleaseCommand: Cmdlet { +[Cmdlet(VerbsLifecycle.Install, "Release")] +[OutputType(typeof(string))] +public class InstallReleaseCommand: PSCmdlet { + /// + /// The instance of the release to be installed. + /// + [Parameter(Mandatory = true, ParameterSetName = "InputObject", ValueFromPipeline = true)] + public required Release InputObject { get; set; } + + /// + /// Value indicating whether to fetch the Ant optional tasks. + /// + [Parameter] + public SwitchParameter OptionalTasks { get; set; } + + /// + /// The version number of the release to be installed. + /// + [Parameter(Mandatory = true, ParameterSetName = "Version", Position = 0, ValueFromPipeline = true)] + public required Version Version { get; set; } + + /// + /// Performs execution of this command. + /// + protected override void ProcessRecord() { + var release = ParameterSetName == "InputObject" ? InputObject : new Release(Version); + // TODO WriteObject(new Setup(release).Install(OptionalTasks)); + } } diff --git a/src/Cmdlets/Main.psm1 b/src/Cmdlets/Main.psm1 deleted file mode 100644 index 65aa691..0000000 --- a/src/Cmdlets/Main.psm1 +++ /dev/null @@ -1,63 +0,0 @@ -using namespace System.Diagnostics.CodeAnalysis -using module ./Release.psm1 -using module ./Setup.psm1 - -<# -.SYNOPSIS - Installs Apache Ant, after downloading it. -.PARAMETER Version - The version number of the release to be installed. -.PARAMETER InputObject - The instance of the release to be installed. -.PARAMETER OptionalTasks - Value indicating whether to fetch the Ant optional tasks. -.INPUTS - [string] A string that contains a version number. -.INPUTS - [Release] An instance of the `Release` class to be installed. -.OUTPUTS - The path to the installation directory. -#> -function Install-Release { - [CmdletBinding(DefaultParameterSetName = "Version")] - [OutputType([string])] - param ( - [Parameter(Mandatory, ParameterSetName = "Version", Position = 0, ValueFromPipeline)] - [string] $Version, - - [Parameter(Mandatory, ParameterSetName = "InputObject", ValueFromPipeline)] - [Release] $InputObject, - - [Parameter()] - [switch] $OptionalTasks - ) - - process { - $release = $PSCmdlet.ParameterSetName -eq "InputObject" ? $InputObject : [Release]::new($Version) - [Setup]::new($release).Install($OptionalTasks) - } -} - -<# -.SYNOPSIS - Creates a new release. -.PARAMETER Version - The version number. -.INPUTS - A string that contains a version number. -.OUTPUTS - The newly created release. -#> -function New-Release { - [CmdletBinding()] - [OutputType([Release])] - [SuppressMessage("PSUseShouldProcessForStateChangingFunctions", "")] - param ( - [Parameter(Mandatory, Position = 0, ValueFromPipeline)] - [string] $Version - ) - - process { - [Release]::new($Version) - } -} From 63cb86491607a0fef6fb345221825f12ea47e5dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belin?= Date: Tue, 2 Dec 2025 22:34:33 +0100 Subject: [PATCH 23/26] Add the cmdlet tests --- test/Cmdlets/BeforeAll.ps1 | 5 ++ test/Cmdlets/Find-Release.Tests.ps1 | 28 +++++++++++ test/Cmdlets/Get-Release.Tests.ps1 | 17 +++++++ test/Cmdlets/Main.Tests.ps1 | 68 --------------------------- test/Cmdlets/Test-Release.Tests.ps1 | 24 ++++++++++ test/Release.Tests.ps1 | 73 ----------------------------- tool/Test.ps1 | 2 +- 7 files changed, 75 insertions(+), 142 deletions(-) create mode 100644 test/Cmdlets/BeforeAll.ps1 create mode 100644 test/Cmdlets/Find-Release.Tests.ps1 create mode 100644 test/Cmdlets/Get-Release.Tests.ps1 delete mode 100644 test/Cmdlets/Main.Tests.ps1 create mode 100644 test/Cmdlets/Test-Release.Tests.ps1 delete mode 100644 test/Release.Tests.ps1 diff --git a/test/Cmdlets/BeforeAll.ps1 b/test/Cmdlets/BeforeAll.ps1 new file mode 100644 index 0000000..8820445 --- /dev/null +++ b/test/Cmdlets/BeforeAll.ps1 @@ -0,0 +1,5 @@ +Import-Module "$PSScriptRoot/../../SetupAnt.psd1" + +$existingRelease = New-AntRelease "1.10.15" +$latestRelease = Get-AntRelease "Latest" +$nonExistingRelease = New-AntRelease "666.6.6" diff --git a/test/Cmdlets/Find-Release.Tests.ps1 b/test/Cmdlets/Find-Release.Tests.ps1 new file mode 100644 index 0000000..f67f6bd --- /dev/null +++ b/test/Cmdlets/Find-Release.Tests.ps1 @@ -0,0 +1,28 @@ +<# +.SYNOPSIS + Tests the features of the `Find-Release` cmdlet. +#> +Describe "Find-Release" { + BeforeAll { + . "$PSScriptRoot/BeforeAll.ps1" + } + + It "should return `$null if no release matches the version constraint" { + Find-AntRelease $nonExistingRelease.Version | Should -Be $null + } + + It "should return the release corresponding to the version constraint if it exists" { + Find-AntRelease "latest" | Should -Be $latestRelease + Find-AntRelease "*" | Should -Be $latestRelease + Find-AntRelease "1" | Should -Be $latestRelease + Find-AntRelease "2" | Should -Be $null + (Find-AntRelease ">1.10.15")?.Version | Should -Be $null + (Find-AntRelease "=1.8.2")?.Version | Should -Be "1.8.2" + (Find-AntRelease "<1.10")?.Version | Should -Be "1.9.16" + (Find-AntRelease "<=1.10")?.Version | Should -Be "1.10.0" + } + + It "should throw if the version constraint is invalid" -TestCases @{ Version = "abc" }, @{ Version = "?1.10" } { + { Find-AntRelease $version } | Should -Throw + } +} diff --git a/test/Cmdlets/Get-Release.Tests.ps1 b/test/Cmdlets/Get-Release.Tests.ps1 new file mode 100644 index 0000000..badcd49 --- /dev/null +++ b/test/Cmdlets/Get-Release.Tests.ps1 @@ -0,0 +1,17 @@ +<# +.SYNOPSIS + Tests the features of the `Get-Release` cmdlet. +#> +Describe "Get-Release" { + BeforeAll { + . "$PSScriptRoot/BeforeAll.ps1" + } + + It "should return `$null if no release matches to the version number" { + Get-AntRelease $nonExistingRelease.Version | Should -Be $null + } + + It "should return the release corresponding to the version number if it exists" { + (Get-AntRelease "1.8.2")?.Version | Should -Be "1.8.2" + } +} diff --git a/test/Cmdlets/Main.Tests.ps1 b/test/Cmdlets/Main.Tests.ps1 deleted file mode 100644 index 435fb1b..0000000 --- a/test/Cmdlets/Main.Tests.ps1 +++ /dev/null @@ -1,68 +0,0 @@ -using namespace System.Diagnostics.CodeAnalysis - -<# -.SYNOPSIS - Tests the features of the `Main` module. -#> -Describe "Main" { - BeforeAll { - Import-Module "$PSScriptRoot/../SetupAnt.psd1" - - [SuppressMessage("PSUseDeclaredVarsMoreThanAssignments", "")] - $existingRelease = New-AntRelease "1.10.15" - - [SuppressMessage("PSUseDeclaredVarsMoreThanAssignments", "")] - $latestRelease = Get-AntRelease "Latest" - - [SuppressMessage("PSUseDeclaredVarsMoreThanAssignments", "")] - $nonExistingRelease = New-AntRelease "666.6.6" - } - - Context "Find-Release" { - It "should return `$null if no release matches the version constraint" { - Find-AntRelease $nonExistingRelease.Version | Should -Be $null - } - - It "should return the release corresponding to the version constraint if it exists" { - Find-AntRelease "latest" | Should -Be $latestRelease - Find-AntRelease "*" | Should -Be $latestRelease - Find-AntRelease "1" | Should -Be $latestRelease - Find-AntRelease "2" | Should -Be $null - (Find-AntRelease ">1.10.15")?.Version | Should -Be $null - (Find-AntRelease "=1.8.2")?.Version | Should -Be "1.8.2" - (Find-AntRelease "<1.10")?.Version | Should -Be "1.9.16" - (Find-AntRelease "<=1.10")?.Version | Should -Be "1.10.0" - } - - It "should throw if the version constraint is invalid" -TestCases @{ Version = "abc" }, @{ Version = "?1.10" } { - { Find-AntRelease $version } | Should -Throw - } - } - - Context "Get-Release" { - It "should return `$null if no release matches to the version number" { - Get-AntRelease $nonExistingRelease.Version | Should -Be $null - } - - It "should return the release corresponding to the version number if it exists" { - (Get-AntRelease "1.8.2")?.Version | Should -Be "1.8.2" - } - } - - Context "Test-Release" { - It "should return `$true for the latest release" { - Test-AntRelease $latestRelease.Version | Should -BeTrue - $latestRelease | Test-AntRelease | Should -BeTrue - } - - It "should return `$true if the release exists" { - Test-AntRelease $existingRelease.Version | Should -BeTrue - $existingRelease | Test-AntRelease | Should -BeTrue - } - - It "should return `$false if the release does not exist" { - Test-AntRelease $nonExistingRelease.Version | Should -BeFalse - $nonExistingRelease | Test-AntRelease | Should -BeFalse - } - } -} diff --git a/test/Cmdlets/Test-Release.Tests.ps1 b/test/Cmdlets/Test-Release.Tests.ps1 new file mode 100644 index 0000000..030327c --- /dev/null +++ b/test/Cmdlets/Test-Release.Tests.ps1 @@ -0,0 +1,24 @@ +<# +.SYNOPSIS + Tests the features of the `Test-Release` cmdlet. +#> +Describe "Test-Release" { + BeforeAll { + . "$PSScriptRoot/BeforeAll.ps1" + } + + It "should return `$true for the latest release" { + Test-AntRelease $latestRelease.Version | Should -BeTrue + $latestRelease | Test-AntRelease | Should -BeTrue + } + + It "should return `$true if the release exists" { + Test-AntRelease $existingRelease.Version | Should -BeTrue + $existingRelease | Test-AntRelease | Should -BeTrue + } + + It "should return `$false if the release does not exist" { + Test-AntRelease $nonExistingRelease.Version | Should -BeFalse + $nonExistingRelease | Test-AntRelease | Should -BeFalse + } +} diff --git a/test/Release.Tests.ps1 b/test/Release.Tests.ps1 deleted file mode 100644 index 880f1a7..0000000 --- a/test/Release.Tests.ps1 +++ /dev/null @@ -1,73 +0,0 @@ -using namespace System.Diagnostics.CodeAnalysis -using module ../src/Release.psm1 - -<# -.SYNOPSIS - Tests the features of the `Release` module. -#> -Describe "Release" { - BeforeAll { - [SuppressMessage("PSUseDeclaredVarsMoreThanAssignments", "")] - $existingRelease = [Release] "1.10.15" - - [SuppressMessage("PSUseDeclaredVarsMoreThanAssignments", "")] - $latestRelease = [Release]::Latest() - - [SuppressMessage("PSUseDeclaredVarsMoreThanAssignments", "")] - $nonExistingRelease = [Release] "666.6.6" - } - - Context "Exists" { - It "should return `$true if the release exists" { - $existingRelease.Exists() | Should -BeTrue - } - - It "should return `$false if the release does not exist" { - $nonExistingRelease.Exists() | Should -BeFalse - } - } - - Context "Url" { - It "should return the URL of the Ant archive" { - $existingRelease.Url() | Should -BeExactly "https://archive.apache.org/dist/ant/binaries/apache-ant-1.10.15-bin.zip" - $nonExistingRelease.Url() | Should -BeExactly "https://archive.apache.org/dist/ant/binaries/apache-ant-666.6.6-bin.zip" - } - } - - Context "Find" { - It "should return `$null if no release matches the version constraint" { - [Release]::Find($nonExistingRelease.Version) | Should -Be $null - } - - It "should return the release corresponding to the version constraint if it exists" { - [Release]::Find("latest") | Should -Be $latestRelease - [Release]::Find("*") | Should -Be $latestRelease - [Release]::Find("1") | Should -Be $latestRelease - [Release]::Find("2") | Should -Be $null - [Release]::Find(">1.10.15")?.Version | Should -Be $null - [Release]::Find("=1.8.2")?.Version | Should -Be "1.8.2" - [Release]::Find("<1.10")?.Version | Should -Be "1.9.16" - [Release]::Find("<=1.10")?.Version | Should -Be "1.10.0" - } - - It "should throw if the version constraint is invalid" -TestCases @{ Version = "abc" }, @{ Version = "?1.10" } { - { [Release]::Find($version) } | Should -Throw - } - } - - Context "Get" { - It "should return `$null if no release matches to the version number" { - [Release]::Get($nonExistingRelease.Version) | Should -Be $null - } - - It "should return the release corresponding to the version number if it exists" { - [Release]::Get("1.8.2")?.Version | Should -Be "1.8.2" - } - } - - Context "Latest" { - It "should exist" { - $latestRelease | Should -Not -Be $null - } - } -} diff --git a/tool/Test.ps1 b/tool/Test.ps1 index e102065..95b5db9 100644 --- a/tool/Test.ps1 +++ b/tool/Test.ps1 @@ -2,6 +2,6 @@ dotnet test --settings .runsettings pwsh -Command { Import-Module Pester - # Invoke-Pester test + Invoke-Pester test exit $LASTEXITCODE } From bec78da14b7ce3036e3301d75a767adf7ac99317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belin?= Date: Tue, 2 Dec 2025 22:35:00 +0100 Subject: [PATCH 24/26] Add the skeleton of the `Setup` class --- src/Setup.cs | 25 ++++++++++++++++++++++++ test/Setup.Tests.cs | 17 ++++++++++++++++ test/{Setup.Tests.ps1 => SetupTests.ps1} | 0 3 files changed, 42 insertions(+) create mode 100644 test/Setup.Tests.cs rename test/{Setup.Tests.ps1 => SetupTests.ps1} (100%) diff --git a/src/Setup.cs b/src/Setup.cs index 96416a9..583b146 100644 --- a/src/Setup.cs +++ b/src/Setup.cs @@ -1 +1,26 @@ namespace Belin.SetupAnt; + +/// +/// Manages the download and installation of Apache Ant. +/// +/// The release to download and install. +public class Setup(Release release) { + + /// + /// Downloads and extracts the ZIP archive of Apache Ant. + /// + /// Value indicating whether to fetch the Ant optional tasks. + /// The path to the extracted directory. + public string Download(bool optionalTasks = false) { + return "TODO"; + } + + /// + /// Installs Apache Ant, after downloading it. + /// + /// Value indicating whether to fetch the Ant optional tasks. + /// The path to the installation directory. + public string Install(bool optionalTasks = false) { + return "TODO"; + } +} diff --git a/test/Setup.Tests.cs b/test/Setup.Tests.cs new file mode 100644 index 0000000..5cb2fc6 --- /dev/null +++ b/test/Setup.Tests.cs @@ -0,0 +1,17 @@ +namespace Belin.SetupAnt; + +/// +/// Tests the features of the class. +/// +/// The test context. +[TestClass] +public sealed class SetupTests(TestContext testContext) { + + [TestMethod] + public void Download() { + } + + [TestMethod] + public void Install() { + } +} diff --git a/test/Setup.Tests.ps1 b/test/SetupTests.ps1 similarity index 100% rename from test/Setup.Tests.ps1 rename to test/SetupTests.ps1 From 32619cd03a38c4a3022fefcb3a32a4620a46ca31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belin?= Date: Wed, 3 Dec 2025 09:34:01 +0100 Subject: [PATCH 25/26] Port the `Setup` class --- Start.ps1 | 6 +-- src/Setup.cs | 57 ++++++++++++++++++++++-- src/Setup.psm1 | 116 ------------------------------------------------- 3 files changed, 56 insertions(+), 123 deletions(-) delete mode 100644 src/Setup.psm1 diff --git a/Start.ps1 b/Start.ps1 index d215dad..485e5a3 100755 --- a/Start.ps1 +++ b/Start.ps1 @@ -1,11 +1,11 @@ #!/usr/bin/env pwsh -using module ./src/Release.psm1 -using module ./src/Setup.psm1 +using assembly ./bin/Belin.SetupAnt.dll +using namespace Belin.SetupAnt $ErrorActionPreference = "Stop" $PSNativeCommandUseErrorActionPreference = $true Set-StrictMode -Version Latest -if (-not (Test-Path Env:SETUP_ANT_VERSION)) { $Env:SETUP_ANT_VERSION = "latest" } +if (-not (Test-Path Env:SETUP_ANT_VERSION)) { $Env:SETUP_ANT_VERSION = "Latest" } $release = [Release]::Find($Env:SETUP_ANT_VERSION) if (-not $release) { throw "No release matching the version constraint." } diff --git a/src/Setup.cs b/src/Setup.cs index 583b146..2bf22f0 100644 --- a/src/Setup.cs +++ b/src/Setup.cs @@ -1,5 +1,10 @@ namespace Belin.SetupAnt; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Threading; + /// /// Manages the download and installation of Apache Ant. /// @@ -10,17 +15,61 @@ public class Setup(Release release) { /// Downloads and extracts the ZIP archive of Apache Ant. /// /// Value indicating whether to fetch the Ant optional tasks. + /// The token to cancel the operation. /// The path to the extracted directory. - public string Download(bool optionalTasks = false) { - return "TODO"; + public async Task Download(bool optionalTasks = false, CancellationToken cancellationToken = default) { + using var httpClient = new HttpClient(); + var version = GetType().Assembly.GetName().Version!; + httpClient.DefaultRequestHeaders.Add("User-Agent", $".NET/{Environment.Version.ToString(3)} | SetupAnt/{version.ToString(3)}"); + + var bytes = await httpClient.GetByteArrayAsync(release.Url, cancellationToken); + var file = Path.GetTempFileName(); + await File.WriteAllBytesAsync(file, bytes, cancellationToken); + + var directory = Path.Join(Path.GetTempPath(), Guid.NewGuid().ToString()); + // TODO (.NET 10) await ZipFile.ExtractToDirectoryAsync(file, directory, cancellationToken); + ZipFile.ExtractToDirectory(file, directory); + + var antHome = Path.Join(directory, Path.GetFileName(Directory.EnumerateDirectories(directory).Single())); + if (optionalTasks) await FetchOptionalTasks(antHome); + return antHome; } /// /// Installs Apache Ant, after downloading it. /// /// Value indicating whether to fetch the Ant optional tasks. + /// The token to cancel the operation. /// The path to the installation directory. - public string Install(bool optionalTasks = false) { - return "TODO"; + public async Task Install(bool optionalTasks = false, CancellationToken cancellationToken = default) { + var antHome = await Download(optionalTasks, cancellationToken); + + var binFolder = Path.Join(antHome, "bin"); + Environment.SetEnvironmentVariable("PATH", $"{Environment.GetEnvironmentVariable("PATH")}{Path.PathSeparator}{binFolder}"); + await File.AppendAllTextAsync(Environment.GetEnvironmentVariable("GITHUB_PATH")!, binFolder, cancellationToken); + + Environment.SetEnvironmentVariable("ANT_HOME", antHome); + await File.AppendAllTextAsync(Environment.GetEnvironmentVariable("GITHUB_ENV")!, $"ANT_HOME={antHome}", cancellationToken); + return antHome; + } + + /// + /// Fetches the external libraries required by Ant optional tasks. + /// + /// The path to the Ant directory. + /// Completes when the external libraries have been fetched. + /// An error occurred while fetching the external libraries. + private static async Task FetchOptionalTasks(string antHome) { + var startInfo = new ProcessStartInfo { + Arguments = "-jar lib/ant-launcher.jar -buildfile fetch.xml -noinput -silent -Ddest=system", + CreateNoWindow = true, + EnvironmentVariables = { ["ANT_HOME"] = antHome }, + FileName = "java", + WorkingDirectory = antHome + }; + + using var process = Process.Start(startInfo) ?? throw new ApplicationFailedException(startInfo.FileName); + await process.WaitForExitAsync(); + if (process.ExitCode != 0) throw new ApplicationFailedException(startInfo.FileName); } } diff --git a/src/Setup.psm1 b/src/Setup.psm1 deleted file mode 100644 index bedf164..0000000 --- a/src/Setup.psm1 +++ /dev/null @@ -1,116 +0,0 @@ -using namespace System.Diagnostics.CodeAnalysis -using namespace System.IO -using module ./Release.psm1 - -<# -.SYNOPSIS - Manages the download and installation of Apache Ant. -#> -class Setup { - - <# - .SYNOPSIS - The release to download and install. - #> - [ValidateNotNull()] - hidden [Release] $Release - - <# - .SYNOPSIS - Creates a new setup. - .PARAMETER Release - The release to download and install. - #> - Setup([Release] $Release) { - $this.Release = $Release - } - - <# - .SYNOPSIS - Downloads and extracts the ZIP archive of Apache Ant. - .OUTPUTS - The path to the extracted directory. - #> - [string] Download() { - return $this.Download($false) - } - - <# - .SYNOPSIS - Downloads and extracts the ZIP archive of Apache Ant. - .PARAMETER OptionalTasks - Value indicating whether to fetch the Ant optional tasks. - .OUTPUTS - The path to the extracted directory. - #> - [string] Download([bool] $OptionalTasks) { - $file = New-TemporaryFile - Invoke-WebRequest $this.Release.Url() -OutFile $file - - $directory = Join-Path ([Path]::GetTempPath()) (New-Guid) - Expand-Archive $file $directory -Force - - $antHome = Join-Path $directory $this.FindSubfolder($directory) - if ($OptionalTasks) { $this.FetchOptionalTasks($antHome) } - return $antHome - } - - <# - .SYNOPSIS - Installs Apache Ant, after downloading it. - .OUTPUTS - The path to the installation directory. - #> - [string] Install() { - return $this.Install($false) - } - - <# - .SYNOPSIS - Installs Apache Ant, after downloading it. - .PARAMETER OptionalTasks - Value indicating whether to fetch the Ant optional tasks. - .OUTPUTS - The path to the installation directory. - #> - [string] Install([bool] $OptionalTasks) { - $antHome = $this.Download($OptionalTasks) - - $binFolder = Join-Path $antHome "bin" - $Env:PATH += "$([Path]::PathSeparator)$binFolder" - Add-Content $Env:GITHUB_PATH $binFolder - - $Env:ANT_HOME = $antHome - Add-Content $Env:GITHUB_ENV "ANT_HOME=$Env:ANT_HOME" - return $antHome - } - - <# - .SYNOPSIS - Fetches the external libraries required by Ant optional tasks. - .PARAMETER AntHome - The path to the Ant directory. - #> - hidden [void] FetchOptionalTasks([string] $AntHome) { - $options = "-jar lib/ant-launcher.jar -buildfile fetch.xml -noinput -silent -Ddest=system" - Start-Process java $options -Environment @{ ANT_HOME = $AntHome } -NoNewWindow -Wait -WorkingDirectory $AntHome - } - - <# - .SYNOPSIS - Determines the name of the single subfolder in the specified directory. - .PARAMETER Directory - The directory path. - .OUTPUTS - The name of the single subfolder in the specified directory. - #> - [SuppressMessage("PSUseDeclaredVarsMoreThanAssignments", "")] - hidden [string] FindSubfolder([string] $Directory) { - $folders = Get-ChildItem $Directory -Directory - return $discard = switch ($folders.Count) { - 0 { throw [DirectoryNotFoundException] "No subfolder found in: $Directory." } - 1 { $folders[0].BaseName; break } - default { throw [DirectoryNotFoundException] "Multiple subfolders found in: $Directory." } - } - } -} From 948a855d8c9709340bda3811bdc4fcc6b60dd957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belin?= Date: Wed, 3 Dec 2025 10:02:13 +0100 Subject: [PATCH 26/26] Add the tests of the `Setup` class --- src/Cmdlets/Install-Release.cs | 2 +- test/Setup.Tests.cs | 18 ++++++++++++++++-- test/SetupTests.ps1 | 34 ---------------------------------- 3 files changed, 17 insertions(+), 37 deletions(-) delete mode 100644 test/SetupTests.ps1 diff --git a/src/Cmdlets/Install-Release.cs b/src/Cmdlets/Install-Release.cs index 209ed1b..ca0f9f2 100644 --- a/src/Cmdlets/Install-Release.cs +++ b/src/Cmdlets/Install-Release.cs @@ -30,6 +30,6 @@ public class InstallReleaseCommand: PSCmdlet { /// protected override void ProcessRecord() { var release = ParameterSetName == "InputObject" ? InputObject : new Release(Version); - // TODO WriteObject(new Setup(release).Install(OptionalTasks)); + WriteObject(new Setup(release).Install(OptionalTasks).GetAwaiter().GetResult()); } } diff --git a/test/Setup.Tests.cs b/test/Setup.Tests.cs index 5cb2fc6..181b570 100644 --- a/test/Setup.Tests.cs +++ b/test/Setup.Tests.cs @@ -7,11 +7,25 @@ namespace Belin.SetupAnt; [TestClass] public sealed class SetupTests(TestContext testContext) { + [ClassInitialize] + public static void ClassInitialize(TestContext testContext) { + if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("GITHUB_ENV"))) Environment.SetEnvironmentVariable("GITHUB_ENV", "var/GitHub-Env.txt"); + if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("GITHUB_PATH"))) Environment.SetEnvironmentVariable("GITHUB_PATH", "var/GitHub-Path.txt"); + } + [TestMethod] - public void Download() { + public async Task Download() { + var path = await new Setup(Release.Latest).Download(optionalTasks: true, testContext.CancellationToken); + IsTrue(File.Exists(Path.Join(path, "bin", OperatingSystem.IsWindows() ? "ant.cmd" : "ant"))); + + var jars = Directory.EnumerateFiles(Path.Join(path, "lib"), "*.jar"); + HasCount(1, jars.Where(jar => Path.GetFileName(jar).StartsWith("ivy-"))); } [TestMethod] - public void Install() { + public async Task Install() { + var path = await new Setup(Release.Latest).Install(optionalTasks: false, testContext.CancellationToken); + AreEqual(path, Environment.GetEnvironmentVariable("ANT_HOME")); + Contains(path, Environment.GetEnvironmentVariable("PATH")!); } } diff --git a/test/SetupTests.ps1 b/test/SetupTests.ps1 deleted file mode 100644 index 374337a..0000000 --- a/test/SetupTests.ps1 +++ /dev/null @@ -1,34 +0,0 @@ -using namespace System.Diagnostics.CodeAnalysis -using module ../src/Release.psm1 -using module ../src/Setup.psm1 - -<# -.SYNOPSIS - Tests the features of the `Setup` module. -#> -Describe "Setup" { - BeforeAll { - [SuppressMessage("PSUseDeclaredVarsMoreThanAssignments", "")] - $latestRelease = [Release]::Latest() - - if (-not (Test-Path Env:GITHUB_ENV)) { $Env:GITHUB_ENV = "var/GitHub-Env.txt" } - if (-not (Test-Path Env:GITHUB_PATH)) { $Env:GITHUB_PATH = "var/GitHub-Path.txt" } - } - - Context "Download" { - It "should properly download and extract Apache Ant" { - $path = [Setup]::new($latestRelease).Download($true) - Join-Path $path "bin/$($IsWindows ? "ant.cmd" : "ant")" | Should -Exist - $jars = Get-Item (Join-Path $path "lib/*.jar") - $jars.Where{ $_.BaseName.StartsWith("ivy-") } | Should -HaveCount 1 - } - } - - Context "Install" { - It "should add the Ant directory to the PATH environment variable" { - $path = [Setup]::new($latestRelease).Install($false) - $Env:ANT_HOME | Should -BeExactly $path - $Env:PATH | Should -BeLikeExactly "*$path*" - } - } -}