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/.gitattributes b/.gitattributes index c510df3..c16a191 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,21 +1,30 @@ * text=auto .* text +*.cjs text *.config text *.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 +*.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 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/ 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/.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" ] } 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" 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" diff --git a/SetupAnt.slnx b/SetupAnt.slnx index 800db64..e1985c4 100644 --- a/SetupAnt.slnx +++ b/SetupAnt.slnx @@ -3,6 +3,7 @@ + @@ -21,24 +22,25 @@ + + + + + - - - - - - - + + + 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/Cmdlets/Find-Release.cs b/src/Cmdlets/Find-Release.cs new file mode 100644 index 0000000..90d264a --- /dev/null +++ b/src/Cmdlets/Find-Release.cs @@ -0,0 +1,20 @@ +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)); +} diff --git a/src/Cmdlets/Get-Release.cs b/src/Cmdlets/Get-Release.cs new file mode 100644 index 0000000..33f3646 --- /dev/null +++ b/src/Cmdlets/Get-Release.cs @@ -0,0 +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/Install-Release.cs b/src/Cmdlets/Install-Release.cs new file mode 100644 index 0000000..ca0f9f2 --- /dev/null +++ b/src/Cmdlets/Install-Release.cs @@ -0,0 +1,35 @@ +namespace Belin.SetupAnt.Cmdlets; + +/// +/// Installs Apache Ant, after downloading it. +/// +[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); + WriteObject(new Setup(release).Install(OptionalTasks).GetAwaiter().GetResult()); + } +} diff --git a/src/Cmdlets/New-Release.cs b/src/Cmdlets/New-Release.cs new file mode 100644 index 0000000..b9d9783 --- /dev/null +++ b/src/Cmdlets/New-Release.cs @@ -0,0 +1,20 @@ +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)); +} diff --git a/src/Cmdlets/Test-Release.cs b/src/Cmdlets/Test-Release.cs new file mode 100644 index 0000000..0b98152 --- /dev/null +++ b/src/Cmdlets/Test-Release.cs @@ -0,0 +1,29 @@ +namespace Belin.SetupAnt.Cmdlets; + +/// +/// Gets a value indicating whether a release with the specified version exists. +/// +[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); + } +} 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/Main.psm1 b/src/Main.psm1 deleted file mode 100644 index e70392c..0000000 --- a/src/Main.psm1 +++ /dev/null @@ -1,140 +0,0 @@ -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 `$null` 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. -.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 `$null` 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. -.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) - } -} - -<# -.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/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") + ]; +} diff --git a/src/Release.cs b/src/Release.cs new file mode 100644 index 0000000..94965d9 --- /dev/null +++ b/src/Release.cs @@ -0,0 +1,126 @@ +namespace Belin.SetupAnt; + +using System.Linq; +using System.Text.RegularExpressions; + +/// +/// Represents an Apache Ant release. +/// +/// The version number. +public partial class Release(Version version): IEquatable { + + /// + /// The latest release. + /// + 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. + /// + public bool Exists => data.Any(release => release == this); + + /// + /// The download URL. + /// + 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. + /// + public Version Version => version; + + /// + /// Creates a new release. + /// + /// 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. + /// + /// 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 operatorMatch = Regex.Match(constraint, @"^([^\d]+)\d"); + var (op, version) = true switch { + 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.") + }; + + var semver = SemanticVersion.Parse(version); + return data.FirstOrDefault(op switch { + ">" => 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.") + }); + } + + /// + /// 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); + + /// + /// 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/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] - } -} diff --git a/src/Setup.cs b/src/Setup.cs new file mode 100644 index 0000000..2bf22f0 --- /dev/null +++ b/src/Setup.cs @@ -0,0 +1,75 @@ +namespace Belin.SetupAnt; + +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Threading; + +/// +/// 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 token to cancel the operation. + /// The path to the extracted directory. + 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 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." } - } - } -} diff --git a/src/SetupAnt.csproj b/src/SetupAnt.csproj new file mode 100644 index 0000000..b2b5ec9 --- /dev/null +++ b/src/SetupAnt.csproj @@ -0,0 +1,27 @@ + + + Cedric-Belin.fr + © Cédric Belin + Set up your GitHub Actions workflow with a specific version of Apache Ant. + Setup Ant + 5.1.0 + + + + false + Belin.SetupAnt + true + 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/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/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/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/Main.Tests.ps1 b/test/Main.Tests.ps1 deleted file mode 100644 index e923c6a..0000000 --- a/test/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 ./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/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")); + } +} 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/test/Setup.Tests.cs b/test/Setup.Tests.cs new file mode 100644 index 0000000..181b570 --- /dev/null +++ b/test/Setup.Tests.cs @@ -0,0 +1,31 @@ +namespace Belin.SetupAnt; + +/// +/// Tests the features of the class. +/// +/// The test context. +[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 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 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/Setup.Tests.ps1 b/test/Setup.Tests.ps1 deleted file mode 100644 index 374337a..0000000 --- a/test/Setup.Tests.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*" - } - } -} diff --git a/test/SetupAnt.Tests.csproj b/test/SetupAnt.Tests.csproj new file mode 100644 index 0000000..e283fc1 --- /dev/null +++ b/test/SetupAnt.Tests.csproj @@ -0,0 +1,30 @@ + + + Cedric-Belin.fr + © Cédric Belin + Setup Ant + 5.1.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 - - 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 diff --git a/tool/Test.ps1 b/tool/Test.ps1 index 226e684..95b5db9 100644 --- a/tool/Test.ps1 +++ b/tool/Test.ps1 @@ -1,4 +1,5 @@ "Running the test suite..." +dotnet test --settings .runsettings pwsh -Command { Import-Module Pester Invoke-Pester test 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