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/.vscode/launch.json b/.vscode/launch.json
index 93c907c..ec38296 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -3,6 +3,7 @@
"configurations": [
{
"name": "Script",
+ "preLaunchTask": "Build",
"request": "launch",
"type": "PowerShell",
"script": "${workspaceFolder}/Debug.ps1"
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 0000000..b3a0bfc
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,11 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "Build",
+ "type": "shell",
+ "command": "./Invoke.ps1",
+ "args": ["Build"]
+ }
+ ]
+}
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/SetupHashLink.psd1 b/SetupHashLink.psd1
index e3a99be..4551ba7 100644
--- a/SetupHashLink.psd1
+++ b/SetupHashLink.psd1
@@ -1,8 +1,8 @@
@{
DefaultCommandPrefix = "HashLink"
- ModuleVersion = "7.1.0"
+ ModuleVersion = "8.0.0"
PowerShellVersion = "7.4"
- RootModule = "src/Main.psm1"
+ RootModule = "bin/Belin.SetupHashLink.dll"
Author = "Cédric Belin "
CompanyName = "Cedric-Belin.fr"
@@ -11,10 +11,10 @@
GUID = "6bb1e481-9f7c-4dd0-922c-fdf44f2c0e78"
AliasesToExport = @()
- CmdletsToExport = @()
+ FunctionsToExport = @()
VariablesToExport = @()
- FunctionsToExport = @(
+ CmdletsToExport = @(
"Find-Release"
"Get-Release"
"Install-Release"
@@ -23,12 +23,6 @@
"Test-Release"
)
- NestedModules = @(
- "src/Platform.psm1"
- "src/Release.psm1"
- "src/Setup.psm1"
- )
-
PrivateData = @{
PSData = @{
LicenseUri = "https://github.com/cedx/setup-hashlink/blob/main/License.md"
diff --git a/SetupHashLink.slnx b/SetupHashLink.slnx
index 5a8f7f4..28aa23e 100644
--- a/SetupHashLink.slnx
+++ b/SetupHashLink.slnx
@@ -3,6 +3,7 @@
+
@@ -21,24 +22,25 @@
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
diff --git a/Start.ps1 b/Start.ps1
index de15d30..ea94d3f 100755
--- a/Start.ps1
+++ b/Start.ps1
@@ -1,14 +1,14 @@
#!/usr/bin/env pwsh
-using module ./src/Release.psm1
-using module ./src/Setup.psm1
+using namespace Belin.SetupHashLink
+Add-Type -Path "$PSScriptRoot/bin/Belin.SetupHashLink.dll"
$ErrorActionPreference = "Stop"
$PSNativeCommandUseErrorActionPreference = $true
Set-StrictMode -Version Latest
-if (-not (Test-Path Env:SETUP_HASHLINK_VERSION)) { $Env:SETUP_HASHLINK_VERSION = "latest" }
+if (-not (Test-Path Env:SETUP_HASHLINK_VERSION)) { $Env:SETUP_HASHLINK_VERSION = "Latest" }
$release = [Release]::Find($Env:SETUP_HASHLINK_VERSION)
-if (-not $release) { throw "No release matching the version constraint." }
+if (-not $release) { throw "No release matches the specified version constraint." }
-$path = [Setup]::new($release).Install()
+$path = [Setup]::new($release).Install().GetAwaiter().GetResult()
"HashLink $($release.Version) successfully installed in ""$path""."
diff --git a/example/workflow.yaml b/example/workflow.yaml
index 0dc3a8b..a449ab2 100644
--- a/example/workflow.yaml
+++ b/example/workflow.yaml
@@ -17,7 +17,7 @@ jobs:
- name: Set up Lix
uses: lix-pm/setup-lix@master
- name: Set up HashLink
- uses: cedx/setup-hashlink@v7
+ uses: cedx/setup-hashlink@v8
with:
version: latest
- name: Install dependencies
diff --git a/src/Cmdlets/Find-Release.cs b/src/Cmdlets/Find-Release.cs
new file mode 100644
index 0000000..b9f3d10
--- /dev/null
+++ b/src/Cmdlets/Find-Release.cs
@@ -0,0 +1,20 @@
+namespace Belin.SetupHashLink.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-Platform.cs b/src/Cmdlets/Get-Platform.cs
new file mode 100644
index 0000000..edd681e
--- /dev/null
+++ b/src/Cmdlets/Get-Platform.cs
@@ -0,0 +1,14 @@
+namespace Belin.SetupHashLink.Cmdlets;
+
+///
+/// Gets the current platform.
+///
+[Cmdlet(VerbsCommon.Get, "Platform")]
+[OutputType(typeof(Platform))]
+public class GetPlatformCommand: Cmdlet {
+
+ ///
+ /// Performs execution of this command.
+ ///
+ protected override void ProcessRecord() => WriteObject(PlatformExtensions.GetCurrent());
+}
diff --git a/src/Cmdlets/Get-Release.cs b/src/Cmdlets/Get-Release.cs
new file mode 100644
index 0000000..01c17f4
--- /dev/null
+++ b/src/Cmdlets/Get-Release.cs
@@ -0,0 +1,21 @@
+namespace Belin.SetupHashLink.Cmdlets;
+
+///
+/// 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..f76f613
--- /dev/null
+++ b/src/Cmdlets/Install-Release.cs
@@ -0,0 +1,33 @@
+namespace Belin.SetupHashLink.Cmdlets;
+
+///
+/// Installs the HashLink VM, after downloading it.
+///
+[Cmdlet(VerbsLifecycle.Install, "Release", DefaultParameterSetName = "Constraint")]
+[OutputType(typeof(string))]
+public class InstallReleaseCommand: PSCmdlet {
+
+ ///
+ /// The version constraint of the release to be installed.
+ ///
+ [Parameter(Mandatory = true, ParameterSetName = "Constraint", Position = 0, ValueFromPipeline = true)]
+ public required string Constraint { get; set; }
+
+ ///
+ /// The release to be installed.
+ ///
+ [Parameter(Mandatory = true, ParameterSetName = "InputObject", ValueFromPipeline = true)]
+ public required Release InputObject { get; set; }
+
+ ///
+ /// Performs execution of this command.
+ ///
+ protected override void ProcessRecord() {
+ var release = ParameterSetName == "InputObject" ? InputObject : Release.Find(Constraint);
+ if (release?.Exists ?? false) WriteObject(new Setup(release).Install().GetAwaiter().GetResult());
+ else {
+ var exception = new InvalidOperationException("No release matches the specified version constraint.");
+ WriteError(new ErrorRecord(exception, "ReleaseNotFound", ErrorCategory.ObjectNotFound, null));
+ }
+ }
+}
diff --git a/src/Cmdlets/New-Release.cs b/src/Cmdlets/New-Release.cs
new file mode 100644
index 0000000..b032137
--- /dev/null
+++ b/src/Cmdlets/New-Release.cs
@@ -0,0 +1,26 @@
+namespace Belin.SetupHashLink.Cmdlets;
+
+///
+/// Creates a new release.
+///
+[Cmdlet(VerbsCommon.New, "Release")]
+[OutputType(typeof(Release))]
+public class NewReleaseCommand: Cmdlet {
+
+ ///
+ /// The associated assets.
+ ///
+ [Parameter(Position = 1)]
+ public Release.Asset[] Assets { get; set; } = [];
+
+ ///
+ /// 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, Assets));
+}
diff --git a/src/Cmdlets/New-ReleaseAsset.cs b/src/Cmdlets/New-ReleaseAsset.cs
new file mode 100644
index 0000000..91602e2
--- /dev/null
+++ b/src/Cmdlets/New-ReleaseAsset.cs
@@ -0,0 +1,26 @@
+namespace Belin.SetupHashLink.Cmdlets;
+
+///
+/// Creates a new release asset.
+///
+[Cmdlet(VerbsCommon.New, "ReleaseAsset")]
+[OutputType(typeof(Release.Asset))]
+public class NewReleaseAssetCommand: Cmdlet {
+
+ ///
+ /// The target file.
+ ///
+ [Parameter(Mandatory = true, Position = 1)]
+ public required string File { get; set; }
+
+ ///
+ /// The target platform.
+ ///
+ [Parameter(Mandatory = true, Position = 0)]
+ public required Platform Platform { get; set; }
+
+ ///
+ /// Performs execution of this command.
+ ///
+ protected override void ProcessRecord() => WriteObject(new Release.Asset(Platform, File));
+}
diff --git a/src/Cmdlets/Test-Release.cs b/src/Cmdlets/Test-Release.cs
new file mode 100644
index 0000000..e512593
--- /dev/null
+++ b/src/Cmdlets/Test-Release.cs
@@ -0,0 +1,29 @@
+namespace Belin.SetupHashLink.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 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 152b252..0000000
--- a/src/Data.psd1
+++ /dev/null
@@ -1,20 +0,0 @@
-@{
- Releases = @(
- @{ Version = "1.15.0"; Assets = , @{ Platform = "Windows"; File = "hashlink-1.15.0-win.zip" } }
- @{ Version = "1.14.0"; Assets = , @{ Platform = "Windows"; File = "hashlink-1.14.0-win.zip" } }
- @{ Version = "1.13.0"; Assets = , @{ Platform = "Windows"; File = "hashlink-1.13.0-win.zip" } }
- @{ Version = "1.12.0"; Assets = , @{ Platform = "Windows"; File = "hl-1.12.0-win.zip" } }
- @{ Version = "1.11.0"; Assets = , @{ Platform = "Windows"; File = "hl-1.11.0-win.zip" } }
- @{ Version = "1.10.0"; Assets = , @{ Platform = "Windows"; File = "hl-1.10.0-win.zip" } }
- @{ Version = "1.9.0"; Assets = , @{ Platform = "Windows"; File = "hl-1.9.0-win.zip" } }
- @{ Version = "1.8.0"; Assets = , @{ Platform = "Windows"; File = "hl-1.8.0-win.zip" } }
- @{ Version = "1.7.0"; Assets = , @{ Platform = "Windows"; File = "hl-1.7.0-win.zip" } }
- @{ Version = "1.6.0"; Assets = @{ Platform = "Windows"; File = "hl-1.6.0-win.zip" }, @{ Platform = "Linux"; File = "hl-1.6.0-linux.tgz" } }
- @{ Version = "1.5.0"; Assets = @{ Platform = "Windows"; File = "hl-1.5.0-win.zip" }, @{ Platform = "Linux"; File = "hl-1.5.0-linux.tgz" } }
- @{ Version = "1.4.0"; Assets = , @{ Platform = "Windows"; File = "hl-1.4-win.zip" } }
- @{ Version = "1.3.0"; Assets = @{ Platform = "Windows"; File = "hl-1.3-win32.zip" }, @{ Platform = "MacOS"; File = "hl-1.3-osx32.zip" } }
- @{ Version = "1.2.0"; Assets = @{ Platform = "Windows"; File = "hl-1.2-win32.zip" }, @{ Platform = "MacOS"; File = "hl-1.2-osx.zip" } }
- @{ Version = "1.1.0"; Assets = , @{ Platform = "Windows"; File = "hl-1.1-win32.zip" } }
- @{ Version = "1.0.0"; Assets = , @{ Platform = "Windows"; File = "hl-1.0-win32.zip" } }
- )
-}
diff --git a/src/Main.psm1 b/src/Main.psm1
deleted file mode 100644
index 522a58c..0000000
--- a/src/Main.psm1
+++ /dev/null
@@ -1,166 +0,0 @@
-using namespace System.Diagnostics.CodeAnalysis
-using module ./Platform.psm1
-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 the HashLink VM, after downloading it.
-.PARAMETER Version
- The version number of the release to be installed.
-.PARAMETER InputObject
- The instance of the release to be installed.
-.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
- )
-
- process {
- $release = $PSCmdlet.ParameterSetName -eq "InputObject" ? $InputObject : [Release]::new($Version)
- [Setup]::new($release).Install()
- }
-}
-
-<#
-.SYNOPSIS
- Creates a new release.
-.PARAMETER Version
- The version number.
-.PARAMETER Assets
- The associated assets.
-.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,
-
- [Parameter(Position = 1)]
- [ReleaseAsset[]] $Assets = @()
- )
-
- process {
- [Release]::new($Version, $Assets)
- }
-}
-
-<#
-.SYNOPSIS
- Creates a new release asset.
-.PARAMETER Platform
- The target platform.
-.PARAMETER File
- The target file.
-.OUTPUTS
- The newly created release asset.
-#>
-function New-ReleaseAsset {
- [CmdletBinding()]
- [OutputType([ReleaseAsset])]
- [SuppressMessage("PSUseShouldProcessForStateChangingFunctions", "")]
- param (
- [Parameter(Mandatory, Position = 0)]
- [Platform] $Platform,
-
- [Parameter(Mandatory, Position = 1)]
- [string] $File
- )
-
- [ReleaseAsset]::new($Platform, $File)
-}
-
-<#
-.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/Platform.cs b/src/Platform.cs
new file mode 100644
index 0000000..8e3ca32
--- /dev/null
+++ b/src/Platform.cs
@@ -0,0 +1,39 @@
+namespace Belin.SetupHashLink;
+
+///
+/// Identifies an operating system or platform.
+///
+public enum Platform {
+
+ ///
+ /// Specifies a Linux platform.
+ ///
+ Linux,
+
+ ///
+ /// Specifies a macOS platform.
+ ///
+ MacOS,
+
+ ///
+ /// Specifies a Windows platform.
+ ///
+ Windows
+}
+
+///
+/// Provides extension members for platforms.
+///
+public static class PlatformExtensions {
+ // TODO (.NET 10) extension(Platform)
+
+ ///
+ /// Gets the current platform.
+ ///
+ /// The current platform.
+ public static Platform GetCurrent() => true switch {
+ true when OperatingSystem.IsLinux() => Platform.Linux,
+ true when OperatingSystem.IsMacOS() => Platform.MacOS,
+ _ => Platform.Windows
+ };
+}
diff --git a/src/Platform.psm1 b/src/Platform.psm1
deleted file mode 100644
index 4824147..0000000
--- a/src/Platform.psm1
+++ /dev/null
@@ -1,41 +0,0 @@
-<#
-.SYNOPSIS
- Identifies an operating system or platform.
-#>
-enum Platform {
-
- <#
- .SYNOPSIS
- Specifies a Linux platform.
- #>
- Linux
-
- <#
- .SYNOPSIS
- Specifies a macOS platform.
- #>
- MacOS
-
- <#
- .SYNOPSIS
- Specifies a Windows platform.
- #>
- Windows
-}
-
-<#
-.SYNOPSIS
- Gets the current platform.
-.OUTPUTS
- The current platform.
-#>
-function Get-Platform {
- [OutputType([Platform])]
- param ()
-
- switch ($true) {
- ($IsLinux) { [Platform]::Linux; break }
- ($IsMacOS) { [Platform]::MacOS; break }
- default { [Platform]::Windows }
- }
-}
diff --git a/src/Release.Asset.cs b/src/Release.Asset.cs
new file mode 100644
index 0000000..a32c578
--- /dev/null
+++ b/src/Release.Asset.cs
@@ -0,0 +1,14 @@
+namespace Belin.SetupHashLink;
+
+///
+/// Represents a HashLink release.
+///
+public partial class Release {
+
+ ///
+ /// Represents an asset of a HashLink release.
+ ///
+ /// The target platform.
+ /// The target file.
+ public sealed record Asset(Platform Platform, string File);
+}
diff --git a/src/Release.Data.cs b/src/Release.Data.cs
new file mode 100644
index 0000000..9e7eea6
--- /dev/null
+++ b/src/Release.Data.cs
@@ -0,0 +1,29 @@
+namespace Belin.SetupHashLink;
+
+///
+/// Represents a HashLink release.
+///
+public partial class Release {
+
+ ///
+ /// The list of all releases.
+ ///
+ private static readonly Release[] data = [
+ new Release("1.15.0", [new Asset(Platform.Windows, "hashlink-1.15.0-win.zip")]),
+ new Release("1.14.0", [new Asset(Platform.Windows, "hashlink-1.14.0-win.zip")]),
+ new Release("1.13.0", [new Asset(Platform.Windows, "hashlink-1.13.0-win.zip")]),
+ new Release("1.12.0", [new Asset(Platform.Windows, "hl-1.12.0-win.zip")]),
+ new Release("1.11.0", [new Asset(Platform.Windows, "hl-1.11.0-win.zip")]),
+ new Release("1.10.0", [new Asset(Platform.Windows, "hl-1.10.0-win.zip")]),
+ new Release("1.9.0", [new Asset(Platform.Windows, "hl-1.9.0-win.zip")]),
+ new Release("1.8.0", [new Asset(Platform.Windows, "hl-1.8.0-win.zip")]),
+ new Release("1.7.0", [new Asset(Platform.Windows, "hl-1.7.0-win.zip")]),
+ new Release("1.6.0", [new Asset(Platform.Windows, "hl-1.6.0-win.zip"), new Asset(Platform.Linux, "hl-1.6.0-linux.tgz")]),
+ new Release("1.5.0", [new Asset(Platform.Windows, "hl-1.5.0-win.zip"), new Asset(Platform.Linux, "hl-1.5.0-linux.tgz")]),
+ new Release("1.4.0", [new Asset(Platform.Windows, "hl-1.4-win.zip")]),
+ new Release("1.3.0", [new Asset(Platform.Windows, "hl-1.3-win32.zip"), new Asset(Platform.MacOS, "hl-1.3-osx32.zip")]),
+ new Release("1.2.0", [new Asset(Platform.Windows, "hl-1.2-win32.zip"), new Asset(Platform.MacOS, "hl-1.2-osx.zip")]),
+ new Release("1.1.0", [new Asset(Platform.Windows, "hl-1.1-win32.zip")]),
+ new Release("1.0.0", [new Asset(Platform.Windows, "hl-1.0-win32.zip")])
+ ];
+}
diff --git a/src/Release.cs b/src/Release.cs
new file mode 100644
index 0000000..3663875
--- /dev/null
+++ b/src/Release.cs
@@ -0,0 +1,151 @@
+namespace Belin.SetupHashLink;
+
+using System.Linq;
+using System.Text.RegularExpressions;
+
+///
+/// Represents a HashLink release.
+///
+/// The version number.
+/// The associated assets.
+public partial class Release(Version version, IEnumerable? assets = null): 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();
+
+ ///
+ /// The associated assets.
+ ///
+ public IEnumerable Assets => assets ?? [];
+
+ ///
+ /// Value indicating whether this release exists.
+ ///
+ public bool Exists => data.Any(release => release == this);
+
+ ///
+ /// Value indicating whether this release is provided as source code.
+ ///
+ public bool IsSource => GetAsset(PlatformExtensions.GetCurrent()) is null;
+
+ ///
+ /// The associated Git tag.
+ ///
+ public string Tag => Version.ToString(Version.Build > 0 ? 3 : 2);
+
+ ///
+ /// The download URL.
+ ///
+ public Uri Url {
+ get {
+ var asset = GetAsset(PlatformExtensions.GetCurrent());
+ var baseUrl = new Uri("https://github.com/HaxeFoundation/hashlink/");
+ return new(baseUrl, asset is null ? $"archive/refs/tags/{Tag}.zip" : $"releases/download/{Tag}/{asset.File}");
+ }
+ }
+
+ ///
+ /// The version number.
+ ///
+ public Version Version => version;
+
+ ///
+ /// Creates a new release.
+ ///
+ /// The version number.
+ /// The associated assets.
+ public Release(string version, IEnumerable? assets = null): this(Version.Parse(version), assets) {}
+
+ ///
+ /// 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 asset corresponding to the specified platform.
+ ///
+ /// The target platform.
+ /// The asset corresponding to the specified platform, or if not found.
+ public Asset? GetAsset(Platform platform) => Assets.SingleOrDefault(asset => asset.Platform == platform);
+
+ ///
+ /// 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 0b2fd34..0000000
--- a/src/Release.psm1
+++ /dev/null
@@ -1,202 +0,0 @@
-using namespace System.Diagnostics.CodeAnalysis
-using module ./Platform.psm1
-
-<#
-.SYNOPSIS
- Represents a HashLink release.
-#>
-class Release {
-
- <#
- .SYNOPSIS
- The list of all releases.
- #>
- hidden static [Release[]] $Data
-
- <#
- .SYNOPSIS
- The associated assets.
- #>
- [ValidateNotNull()]
- [ReleaseAsset[]] $Assets
-
- <#
- .SYNOPSIS
- The version number.
- #>
- [ValidateNotNull()]
- [semver] $Version
-
- <#
- .SYNOPSIS
- Creates a new release.
- .PARAMETER Version
- The version number.
- #>
- Release([string] $Version) {
- $this.Assets = @()
- $this.Version = $Version
- }
-
- <#
- .SYNOPSIS
- Creates a new release.
- .PARAMETER Version
- The version number.
- .PARAMETER Assets
- The associated assets.
- #>
- Release([string] $Version, [ReleaseAsset[]] $Assets) {
- $this.Assets = $Assets
- $this.Version = $Version
- }
-
- <#
- .SYNOPSIS
- Initializes the class.
- #>
- static Release() {
- [Release]::Data = (Import-PowerShellDataFile "$PSScriptRoot/Data.psd1").Releases.ForEach{
- [Release]::new($_.Version, $_.Assets.ForEach{ [ReleaseAsset]::new($_.Platform, $_.File) })
- }
- }
-
- <#
- .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 asset corresponding to the specified platform.
- .PARAMETER Platform
- The target platform.
- .OUTPUTS
- The asset corresponding to the specified platform, or `$null` if not found.
- #>
- [ReleaseAsset] GetAsset([Platform] $Platform) {
- return $this.Assets.Where({ $_.Platform -eq $Platform }, "First")[0]
- }
-
- <#
- .SYNOPSIS
- Gets a value indicating whether this release is provided as source code.
- .OUTPUTS
- `$true` if this release is provided as source code, otherwise `$false`.
- #>
- [bool] IsSource() {
- return -not $this.GetAsset((Get-Platform))
- }
-
- <#
- .SYNOPSIS
- Gets the associated Git tag.
- .OUTPUTS
- The associated Git tag.
- #>
- [string] Tag() {
- $major, $minor, $patch = $this.Version.Major, $this.Version.Minor, $this.Version.Patch
- return $patch -gt 0 ? "$major.$minor.$patch" : "$major.$minor"
- }
-
- <#
- .SYNOPSIS
- Gets the download URL.
- .OUTPUTS
- The download URL.
- #>
- [uri] Url() {
- $asset = $this.GetAsset((Get-Platform))
- $baseUrl = [uri] "https://github.com/HaxeFoundation/hashlink/"
- return [uri]::new($baseUrl, $asset ? "releases/download/$($this.Tag())/$($asset.File)" : "archive/refs/tags/$($this.Tag()).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]
- }
-}
-
-<#
-.SYNOPSIS
- Represents an asset of a HashLink release.
-#>
-class ReleaseAsset {
-
- <#
- .SYNOPSIS
- The target file.
- #>
- [ValidateNotNullOrWhiteSpace()]
- [string] $File
-
- <#
- .SYNOPSIS
- The target platform.
- #>
- [ValidateNotNull()]
- [Platform] $Platform
-
- <#
- .SYNOPSIS
- Creates a new release asset.
- .PARAMETER Platform
- The target platform.
- .PARAMETER File
- The target file.
- #>
- ReleaseAsset([Platform] $Platform, [string] $File) {
- $this.File = $File
- $this.Platform = $Platform
- }
-}
diff --git a/src/Setup.cs b/src/Setup.cs
new file mode 100644
index 0000000..90ac66e
--- /dev/null
+++ b/src/Setup.cs
@@ -0,0 +1,135 @@
+namespace Belin.SetupHashLink;
+
+using System.Diagnostics;
+using System.IO;
+using System.IO.Compression;
+using System.Threading;
+
+///
+/// Manages the download and installation of the HashLink VM.
+///
+/// The release to download and install.
+public class Setup(Release release) {
+
+ ///
+ /// The release to download and install.
+ ///
+ public Release Release => release;
+
+ ///
+ /// Downloads and extracts the ZIP archive of the HashLink VM.
+ ///
+ /// The token to cancel the operation.
+ /// The path to the extracted directory.
+ public async Task Download(CancellationToken cancellationToken = default) {
+ using var httpClient = new HttpClient();
+ var version = GetType().Assembly.GetName().Version!;
+ httpClient.DefaultRequestHeaders.Add("User-Agent", $".NET/{Environment.Version.ToString(3)} | SetupHashLink/{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);
+ return Path.Join(directory, Path.GetFileName(Directory.EnumerateDirectories(directory).Single()));
+ }
+
+ ///
+ /// Installs the HashLink VM, after downloading it.
+ ///
+ /// The token to cancel the operation.
+ /// The path to the installation directory.
+ public async Task Install(CancellationToken cancellationToken = default) {
+ var directory = await Download(cancellationToken);
+ if (Release.IsSource && Environment.GetEnvironmentVariable("CI") is not null) await Compile(directory, cancellationToken);
+
+ var binFolder = Release.IsSource ? Path.Join(directory, "bin") : directory;
+ Environment.SetEnvironmentVariable("PATH", $"{Environment.GetEnvironmentVariable("PATH")}{Path.PathSeparator}{binFolder}");
+ await File.AppendAllTextAsync(Environment.GetEnvironmentVariable("GITHUB_PATH")!, binFolder, cancellationToken);
+ return directory;
+ }
+
+ ///
+ /// Compiles the sources of the HashLink VM located in the specified directory.
+ ///
+ /// The path to the directory containing the HashLink sources.
+ /// The token to cancel the operation.
+ /// The path to the output directory.
+ /// The compilation is not supported on Windows platform.
+ private async Task Compile(string directory, CancellationToken cancellationToken) {
+ var platform = PlatformExtensions.GetCurrent();
+ if (platform == Platform.Windows) throw new PlatformNotSupportedException("Compilation is not supported on Windows platform.");
+
+ var workingDirectory = Environment.CurrentDirectory;
+ Environment.CurrentDirectory = directory;
+ var path = platform == Platform.MacOS ? await CompileMacOS(cancellationToken) : await CompileLinux(cancellationToken);
+ Environment.CurrentDirectory = workingDirectory;
+ return path;
+ }
+
+ ///
+ /// Compiles the HashLink sources on the Linux platform.
+ ///
+ /// The path to the output directory.
+ /// An error occurred while executing a native command.
+ private static async Task CompileLinux(CancellationToken cancellationToken) {
+ var dependencies = new[] {
+ "libglu1-mesa-dev",
+ "libmbedtls-dev",
+ "libopenal-dev",
+ "libpng-dev",
+ "libsdl2-dev",
+ "libsqlite3-dev",
+ "libturbojpeg-dev",
+ "libuv1-dev",
+ "libvorbis-dev"
+ };
+
+ var commands = new[] {
+ ("sudo", "apt-get update"),
+ ("sudo", $"apt-get install --assume-yes --no-install-recommends {string.Join(" ", dependencies)}"),
+ ("make", ""),
+ ("sudo", "make install"),
+ ("sudo", "ldconfig")
+ };
+
+ foreach (var (fileName, arguments) in commands) {
+ using var process = Process.Start(fileName, arguments) ?? throw new ApplicationFailedException(fileName);
+ await process.WaitForExitAsync(cancellationToken);
+ if (process.ExitCode != 0) throw new ApplicationFailedException(fileName);
+ }
+
+ var prefix = "/usr/local";
+ var ldLibraryPath = $"{Environment.GetEnvironmentVariable("LD_LIBRARY_PATH")}{Path.PathSeparator}{prefix}/bin";
+ Environment.SetEnvironmentVariable("LD_LIBRARY_PATH", ldLibraryPath);
+ await File.AppendAllTextAsync(Environment.GetEnvironmentVariable("GITHUB_ENV")!, $"LD_LIBRARY_PATH={ldLibraryPath}", cancellationToken);
+ return prefix;
+ }
+
+ ///
+ /// Compiles the HashLink sources on the macOS platform.
+ ///
+ /// The token to cancel the operation.
+ /// The path to the output directory.
+ /// An error occurred while executing a native command.
+ private static async Task CompileMacOS(CancellationToken cancellationToken) {
+ var prefix = "/usr/local";
+ var commands = new[] {
+ ("brew", "bundle"),
+ ("make", ""),
+ ("sudo", "make codesign_osx"),
+ ("sudo", "make install"),
+ ("sudo", $"install_name_tool -change libhl.dylib {prefix}/lib/libhl.dylib {prefix}/bin/hl")
+ };
+
+ foreach (var (fileName, arguments) in commands) {
+ using var process = Process.Start(fileName, arguments) ?? throw new ApplicationFailedException(fileName);
+ await process.WaitForExitAsync(cancellationToken);
+ if (process.ExitCode != 0) throw new ApplicationFailedException(fileName);
+ }
+
+ return prefix;
+ }
+}
diff --git a/src/Setup.psm1 b/src/Setup.psm1
deleted file mode 100644
index 35b8395..0000000
--- a/src/Setup.psm1
+++ /dev/null
@@ -1,146 +0,0 @@
-using namespace System.Diagnostics.CodeAnalysis
-using namespace System.IO
-using module ./Platform.psm1
-using module ./Release.psm1
-
-<#
-.SYNOPSIS
- Manages the download and installation of the HashLink VM.
-#>
-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 the HashLink VM.
- .OUTPUTS
- The path to the extracted directory.
- #>
- [string] Download() {
- $file = New-TemporaryFile
- Invoke-WebRequest $this.Release.Url() -OutFile $file
- $directory = Join-Path ([Path]::GetTempPath()) (New-Guid)
- Expand-Archive $file $directory -Force
- return Join-Path $directory $this.FindSubfolder($directory)
- }
-
- <#
- .SYNOPSIS
- Installs the HashLink VM, after downloading it.
- .OUTPUTS
- The path to the installation directory.
- #>
- [string] Install() {
- $directory = $this.Download()
- $isSource = $this.Release.IsSource()
- if ($isSource -and $Env:CI) { $this.Compile($directory) }
-
- $binFolder = $isSource ? (Join-Path $directory "bin") : $directory
- $Env:PATH += "$([Path]::PathSeparator)$binFolder"
- Add-Content $Env:GITHUB_PATH $binFolder
- return $directory
- }
-
- <#
- .SYNOPSIS
- Compiles the sources of the HashLink VM located in the specified directory.
- .PARAMETER Directory
- The path to the directory containing the HashLink sources.
- .OUTPUTS
- The path to the output directory.
- #>
- hidden [string] Compile([string] $Directory) {
- $platform = Get-Platform
- if ($platform -eq [Platform]::Windows) { throw [PlatformNotSupportedException] "Compilation is not supported on Windows platform." }
-
- $workingDirectory = Get-Location
- Set-Location $Directory
- $path = $platform -eq [Platform]::MacOS ? $this.CompileMacOS() : $this.CompileLinux()
- Set-Location $workingDirectory
- return $path
- }
-
- <#
- .SYNOPSIS
- Compiles the HashLink sources on the Linux platform.
- .OUTPUTS
- The path to the output directory.
- #>
- hidden [string] CompileLinux() {
- $dependencies = @(
- "libglu1-mesa-dev"
- "libmbedtls-dev"
- "libopenal-dev"
- "libpng-dev"
- "libsdl2-dev"
- "libsqlite3-dev"
- "libturbojpeg-dev"
- "libuv1-dev"
- "libvorbis-dev"
- )
-
- sudo apt-get update
- sudo apt-get install --assume-yes --no-install-recommends @dependencies
- make
- sudo make install
- sudo ldconfig
-
- $prefix = "/usr/local"
- $binFolder = Join-Path $prefix "bin"
- $Env:LD_LIBRARY_PATH += "$([Path]::PathSeparator)$binFolder"
- Add-Content $Env:GITHUB_ENV "LD_LIBRARY_PATH=$Env:LD_LIBRARY_PATH"
- return $prefix
- }
-
- <#
- .SYNOPSIS
- Compiles the HashLink sources on the macOS platform.
- .OUTPUTS
- The path to the output directory.
- #>
- hidden [string] CompileMacOS() {
- $prefix = "/usr/local"
-
- brew bundle
- make
- sudo make codesign_osx
- sudo make install
- sudo install_name_tool -change libhl.dylib $prefix/lib/libhl.dylib $prefix/bin/hl
-
- return $prefix
- }
-
- <#
- .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/SetupHashLink.csproj b/src/SetupHashLink.csproj
new file mode 100644
index 0000000..425debc
--- /dev/null
+++ b/src/SetupHashLink.csproj
@@ -0,0 +1,27 @@
+
+
+ Cedric-Belin.fr
+ © Cédric Belin
+ Set up your GitHub Actions workflow with a specific version of the HashLink VM.
+ Setup HashLink VM
+ 8.0.0
+
+
+
+ false
+ Belin.SetupHashLink
+ true
+ enable
+ enable
+ ../bin
+ net8.0
+
+
+
+
+
+
+
+
+
+
diff --git a/src/SetupHashLink.esproj b/src/SetupHashLink.esproj
deleted file mode 100644
index a2c426d..0000000
--- a/src/SetupHashLink.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..c838295
--- /dev/null
+++ b/test/Cmdlets/BeforeAll.ps1
@@ -0,0 +1,15 @@
+using namespace System.Diagnostics.CodeAnalysis
+Import-Module "$PSScriptRoot/../../SetupHashLink.psd1"
+
+[SuppressMessage("PSUseDeclaredVarsMoreThanAssignments", "")]
+$existingRelease = New-HashLinkRelease "1.15.0" @(
+ New-HashLinkReleaseAsset Linux "hashlink-1.15.0.zip"
+ New-HashLinkReleaseAsset MacOS "hashlink-1.15.0.zip"
+ New-HashLinkReleaseAsset Windows "hashlink-1.15.0.zip"
+)
+
+[SuppressMessage("PSUseDeclaredVarsMoreThanAssignments", "")]
+$latestRelease = Get-HashLinkRelease "Latest"
+
+[SuppressMessage("PSUseDeclaredVarsMoreThanAssignments", "")]
+$nonExistingRelease = New-HashLinkRelease "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..f9010e4
--- /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-HashLinkRelease $nonExistingRelease.Version | Should -Be $null
+ }
+
+ It "should return the release corresponding to the version constraint if it exists" {
+ Find-HashLinkRelease "latest" | Should -Be $latestRelease
+ Find-HashLinkRelease "*" | Should -Be $latestRelease
+ Find-HashLinkRelease "1" | Should -Be $latestRelease
+ Find-HashLinkRelease "2" | Should -Be $null
+ (Find-HashLinkRelease ">1.15")?.Version | Should -Be $null
+ (Find-HashLinkRelease "=1.8")?.Version | Should -Be "1.8.0"
+ (Find-HashLinkRelease "<1.10")?.Version | Should -Be "1.9.0"
+ (Find-HashLinkRelease "<=1.10")?.Version | Should -Be "1.10.0"
+ }
+
+ It "should throw if the version constraint is invalid" -TestCases @{ Version = "abc" }, @{ Version = "?1.10" } {
+ { Find-HashLinkRelease $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..4ac39af
--- /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-HashLinkRelease $nonExistingRelease.Version | Should -Be $null
+ }
+
+ It "should return the release corresponding to the version number if it exists" {
+ (Get-HashLinkRelease "1.8.0")?.Version | Should -Be "1.8.0"
+ }
+}
diff --git a/test/Cmdlets/Test-Release.Tests.ps1 b/test/Cmdlets/Test-Release.Tests.ps1
new file mode 100644
index 0000000..b3fb1c7
--- /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-HashLinkRelease $latestRelease.Version | Should -BeTrue
+ $latestRelease | Test-HashLinkRelease | Should -BeTrue
+ }
+
+ It "should return `$true if the release exists" {
+ Test-HashLinkRelease $existingRelease.Version | Should -BeTrue
+ $existingRelease | Test-HashLinkRelease | Should -BeTrue
+ }
+
+ It "should return `$false if the release does not exist" {
+ Test-HashLinkRelease $nonExistingRelease.Version | Should -BeFalse
+ $nonExistingRelease | Test-HashLinkRelease | Should -BeFalse
+ }
+}
diff --git a/test/Main.Tests.ps1 b/test/Main.Tests.ps1
deleted file mode 100644
index 8a494fa..0000000
--- a/test/Main.Tests.ps1
+++ /dev/null
@@ -1,72 +0,0 @@
-using namespace System.Diagnostics.CodeAnalysis
-
-<#
-.SYNOPSIS
- Tests the features of the `Main` module.
-#>
-Describe "Main" {
- BeforeAll {
- Import-Module ./SetupHashLink.psd1
-
- [SuppressMessage("PSUseDeclaredVarsMoreThanAssignments", "")]
- $existingRelease = New-HashLinkRelease "1.15.0" @(
- New-HashLinkReleaseAsset Linux "hashlink-1.15.0.zip"
- New-HashLinkReleaseAsset MacOS "hashlink-1.15.0.zip"
- New-HashLinkReleaseAsset Windows "hashlink-1.15.0.zip"
- )
-
- [SuppressMessage("PSUseDeclaredVarsMoreThanAssignments", "")]
- $latestRelease = Get-HashLinkRelease "Latest"
-
- [SuppressMessage("PSUseDeclaredVarsMoreThanAssignments", "")]
- $nonExistingRelease = New-HashLinkRelease "666.6.6"
- }
-
- Context "Find-Release" {
- It "should return `$null if no release matches the version constraint" {
- Find-HashLinkRelease $nonExistingRelease.Version | Should -Be $null
- }
-
- It "should return the release corresponding to the version constraint if it exists" {
- Find-HashLinkRelease "latest" | Should -Be $latestRelease
- Find-HashLinkRelease "*" | Should -Be $latestRelease
- Find-HashLinkRelease "1" | Should -Be $latestRelease
- Find-HashLinkRelease "2" | Should -Be $null
- (Find-HashLinkRelease ">1.15")?.Version | Should -Be $null
- (Find-HashLinkRelease "=1.8.0")?.Version | Should -Be "1.8.0"
- (Find-HashLinkRelease "<1.10")?.Version | Should -Be "1.9.0"
- (Find-HashLinkRelease "<=1.10")?.Version | Should -Be "1.10.0"
- }
-
- It "should throw if the version constraint is invalid" -TestCases @{ Version = "abc" }, @{ Version = "?1.10" } {
- { Find-HashLinkRelease $version } | Should -Throw
- }
- }
-
- Context "Get-Release" {
- It "should return `$null if no release matches to the version number" {
- Get-HashLinkRelease $nonExistingRelease.Version | Should -Be $null
- }
-
- It "should return the release corresponding to the version number if it exists" {
- (Get-HashLinkRelease "1.8.0")?.Version | Should -Be "1.8.0"
- }
- }
-
- Context "Test-Release" {
- It "should return `$true for the latest release" {
- Test-HashLinkRelease $latestRelease.Version | Should -BeTrue
- $latestRelease | Test-HashLinkRelease | Should -BeTrue
- }
-
- It "should return `$true if the release exists" {
- Test-HashLinkRelease $existingRelease.Version | Should -BeTrue
- $existingRelease | Test-HashLinkRelease | Should -BeTrue
- }
-
- It "should return `$false if the release does not exist" {
- Test-HashLinkRelease $nonExistingRelease.Version | Should -BeFalse
- $nonExistingRelease | Test-HashLinkRelease | Should -BeFalse
- }
- }
-}
diff --git a/test/Release.Tests.cs b/test/Release.Tests.cs
new file mode 100644
index 0000000..4f8a14c
--- /dev/null
+++ b/test/Release.Tests.cs
@@ -0,0 +1,77 @@
+namespace Belin.SetupHashLink;
+
+///
+/// Tests the features of the class.
+///
+/// The test context.
+[TestClass]
+public sealed class ReleaseTests {
+
+ ///
+ /// A release that exists.
+ ///
+ private readonly Release existingRelease = new("1.15.0", [
+ new Release.Asset(Platform.Linux, "hashlink-1.15.0.zip"),
+ new Release.Asset(Platform.MacOS, "hashlink-1.15.0.zip"),
+ new Release.Asset(Platform.Windows, "hashlink-1.15.0.zip")
+ ]);
+
+ ///
+ /// 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 IsSource() {
+ IsFalse(existingRelease.IsSource);
+ IsTrue(nonExistingRelease.IsSource);
+ }
+
+ [TestMethod]
+ public void Tag() {
+ AreEqual("1.15", existingRelease.Tag);
+ AreEqual("666.6.6", nonExistingRelease.Tag);
+ }
+
+ [TestMethod]
+ public void Url() {
+ AreEqual(new Uri("https://github.com/HaxeFoundation/hashlink/releases/download/1.15/hashlink-1.15.0.zip"), existingRelease.Url);
+ AreEqual(new Uri("https://github.com/HaxeFoundation/hashlink/archive/refs/tags/666.6.6.zip"), nonExistingRelease.Url);
+ }
+
+ [TestMethod]
+ public void Find() {
+ IsNull(Release.Find(nonExistingRelease.Version.ToString()));
+ IsNull(Release.Find("2"));
+ IsNull(Release.Find(">1.15"));
+
+ AreEqual(Release.Latest, Release.Find("latest"));
+ AreEqual(Release.Latest, Release.Find("*"));
+ AreEqual(Release.Latest, Release.Find("1"));
+
+ AreEqual(new Release("1.8.0"), Release.Find("=1.8"));
+ AreEqual(new Release("1.9.0"), 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(Version.Parse("1.8.0"), Release.Get("1.8.0")?.Version);
+ }
+
+ [TestMethod]
+ public void GetAsset() {
+ AreEqual("hashlink-1.15.0.zip", existingRelease.GetAsset(Platform.Windows)?.File);
+ IsNull(nonExistingRelease.GetAsset(Platform.Windows));
+ }
+}
diff --git a/test/Release.Tests.ps1 b/test/Release.Tests.ps1
deleted file mode 100644
index a96946b..0000000
--- a/test/Release.Tests.ps1
+++ /dev/null
@@ -1,111 +0,0 @@
-using namespace System.Diagnostics.CodeAnalysis
-using module ../src/Platform.psm1
-using module ../src/Release.psm1
-
-<#
-.SYNOPSIS
- Tests the features of the `Release` module.
-#>
-Describe "Release" {
- BeforeAll {
- [SuppressMessage("PSUseDeclaredVarsMoreThanAssignments", "")]
- $existingRelease = [Release]::new("1.15.0", @(
- [ReleaseAsset]::new([Platform]::Linux, "hashlink-1.15.0.zip")
- [ReleaseAsset]::new([Platform]::MacOS, "hashlink-1.15.0.zip")
- [ReleaseAsset]::new([Platform]::Windows, "hashlink-1.15.0.zip")
- ))
-
- [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 "GetAsset" {
- It "should return `$null if no asset matches the platform" {
- $nonExistingRelease.GetAsset([Platform]::Windows) | Should -Be $null
- }
-
- It "should return the asset corresponding to the platform number if it exists" {
- $existingRelease.GetAsset([Platform]::Windows)?.File | Should -BeExactly "hashlink-1.15.0.zip"
- }
- }
-
- Context "IsSource" {
- It "should return `$true if the release is provided as source code" {
- $nonExistingRelease.IsSource() | Should -BeTrue
- }
-
- It "should return `$false if the release is provided as binary" {
- $existingRelease.IsSource() | Should -BeFalse
- }
- }
-
- Context "Tag" {
- It "should not include the patch component if it's zero" {
- $existingRelease.Tag() | Should -Be "1.15"
- }
-
- It "should include the patch component if it's greater than zero" {
- $nonExistingRelease.Tag() | Should -Be $nonExistingRelease.Version
- }
- }
-
- Context "Url" {
- It "should point to a GitHub tag if the release is provided as source code" {
- $nonExistingRelease.Url() | Should -BeExactly "https://github.com/HaxeFoundation/hashlink/archive/refs/tags/666.6.6.zip"
- }
-
- It "should point to a GitHub release if the release is provided as binary" {
- $existingRelease.Url() | Should -BeExactly "https://github.com/HaxeFoundation/hashlink/releases/download/1.15/hashlink-1.15.0.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.15")?.Version | Should -Be $null
- [Release]::Find("=1.8.0")?.Version | Should -Be "1.8.0"
- [Release]::Find("<1.10")?.Version | Should -Be "1.9.0"
- [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.0")?.Version | Should -Be "1.8.0"
- }
- }
-
- 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..a4bf23e
--- /dev/null
+++ b/test/Setup.Tests.cs
@@ -0,0 +1,40 @@
+namespace Belin.SetupHashLink;
+
+///
+/// Tests the features of the class.
+///
+/// The test context.
+[TestClass]
+public sealed class SetupTests(TestContext testContext) {
+
+ ///
+ /// The current platform.
+ ///
+ private readonly Platform platform = PlatformExtensions.GetCurrent();
+
+ [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 setup = new Setup(Release.Latest);
+ var path = await setup.Download(testContext.CancellationToken);
+
+ var executable = $"hl{(setup.Release.IsSource ? ".vcxproj" : platform == Platform.Windows ? ".exe" : "")}";
+ IsTrue(File.Exists(Path.Join(path, executable)));
+
+ var dynamicLibrary = $"libhl{(setup.Release.IsSource ? ".vcxproj" : platform == Platform.MacOS ? ".dylib" : platform == Platform.Linux ? ".so" : ".dll")}";
+ IsTrue(File.Exists(Path.Join(path, dynamicLibrary)));
+ }
+
+ [TestMethod]
+ public async Task Install() {
+ var setup = new Setup(Release.Latest);
+ var path = await setup.Install(testContext.CancellationToken);
+ Contains(path, Environment.GetEnvironmentVariable("PATH")!);
+ if (platform == Platform.Linux && setup.Release.IsSource) Contains("/usr/local/bin", Environment.GetEnvironmentVariable("LD_LIBRARY_PATH")!);
+ }
+}
diff --git a/test/Setup.Tests.ps1 b/test/Setup.Tests.ps1
deleted file mode 100644
index 2266713..0000000
--- a/test/Setup.Tests.ps1
+++ /dev/null
@@ -1,47 +0,0 @@
-using namespace System.Diagnostics.CodeAnalysis
-using module ../src/Platform.psm1
-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()
-
- [SuppressMessage("PSUseDeclaredVarsMoreThanAssignments", "")]
- $platform = Get-Platform
-
- 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 the HashLink VM" {
- $setup = [Setup]::new($latestRelease)
- $isSource = $setup.Release.IsSource()
- $path = $setup.Download()
-
- $executable = "hl$($isSource ? ".vcxproj" : $platform -eq [Platform]::Windows ? ".exe" : [string]::Empty)"
- Join-Path $path $executable | Should -Exist
-
- $dynamicLib = "libhl$($isSource ? ".vcxproj" : $platform -eq [Platform]::MacOS ? ".dylib" : $platform -eq [Platform]::Linux ? ".so" : ".dll")"
- Join-Path $path $dynamicLib | Should -Exist
- }
- }
-
- Context "Install" {
- It "should add the HashLink VM binaries to the PATH environment variable" {
- $setup = [Setup]::new($latestRelease)
- $path = $setup.Install()
-
- $Env:PATH | Should -BeLikeExactly "*$path*"
- if (($platform -eq [Platform]::Linux) -and ($setup.Release.IsSource())) {
- $Env:LD_LIBRARY_PATH | Should -BeLikeExactly "*/usr/local/bin*"
- }
- }
- }
-}
diff --git a/test/SetupHashLink.Tests.csproj b/test/SetupHashLink.Tests.csproj
new file mode 100644
index 0000000..d264ead
--- /dev/null
+++ b/test/SetupHashLink.Tests.csproj
@@ -0,0 +1,30 @@
+
+
+ Cedric-Belin.fr
+ © Cédric Belin
+ Setup HashLink VM
+ 8.0.0
+
+
+
+ false
+ Belin.SetupHashLink.Tests
+ obj
+ enable
+ enable
+ ../bin
+ en
+ net8.0
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/SetupHashLink.Tests.esproj b/test/SetupHashLink.Tests.esproj
deleted file mode 100644
index a2c426d..0000000
--- a/test/SetupHashLink.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 1eb8128..fc73aae 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 SetupHashLink.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/Publish.ps1 b/tool/Publish.ps1
index fe45655..5e242e9 100644
--- a/tool/Publish.ps1
+++ b/tool/Publish.ps1
@@ -1,4 +1,9 @@
-& "$PSScriptRoot/Default.ps1"
+if ($Release) { & "$PSScriptRoot/Default.ps1" }
+else {
+ "The ""-Release"" switch must be set!"
+ exit 1
+}
+
"Publishing the package..."
$version = (Import-PowerShellDataFile "SetupHashLink.psd1").ModuleVersion
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..38844ff
--- /dev/null
+++ b/tool/Version.ps1
@@ -0,0 +1,5 @@
+"Updating the version number in the sources..."
+$version = (Import-PowerShellDataFile "SetupHashLink.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