diff --git a/.build/Directory.Build.props b/.build/Directory.Build.props
new file mode 100644
index 0000000..9df28c7
--- /dev/null
+++ b/.build/Directory.Build.props
@@ -0,0 +1,10 @@
+
+
+
+
+
+
diff --git a/.build/Directory.Build.targets b/.build/Directory.Build.targets
new file mode 100644
index 0000000..abfb0e3
--- /dev/null
+++ b/.build/Directory.Build.targets
@@ -0,0 +1,10 @@
+
+
+
+
+
+
diff --git a/.build/README.md b/.build/README.md
deleted file mode 100644
index 75a9cf5..0000000
--- a/.build/README.md
+++ /dev/null
@@ -1,152 +0,0 @@
-Excellent question. This is a critical part of any real-world build system.
-
-The answer is **yes, Taskfiles can handle secrets, but with one absolute rule: Never store secrets directly in the Taskfile or any other file that gets committed to Git.**
-
-The correct and secure way to handle this is to pass the secret to your task via an **environment variable**. Your Taskfile will read the environment variable, but it won't know or care where the value came from. This keeps the secret itself completely separate from your project's code.
-
-Let's add a `push:nuget` task to your system that securely uses a NuGet API key.
-
-### 1. The Plan
-
-1. **Create a `push:nuget` Task**: This new task in your root `Taskfile.yml` will depend on `pack:nuget` and will call a new `push-nuget` task in the `dotnet` submodule.
-2. **Define a `.env` File**: We'll create a new, separate file for secrets. This file **must be git-ignored**.
-3. **Update the Root `Taskfile.yml`**: We will tell it to load our new secrets file.
-4. **Update the `dotnet` Submodule**: The new `push-nuget` task will use the `NUGET_API_KEY` variable.
-
----
-
-### 1. Create a `.env.example` and Update `.gitignore`
-
-First, let's create a template for our secrets file.
-
-**File: `.env.example`**
-
-```ini
-# This file contains secrets for the build system.
-# Copy it to .env and fill in the values.
-# IMPORTANT: .env is ignored by git and should NEVER be committed.
-
-NUGET_API_KEY="your_nuget_api_key_here"
-NUGET_SOURCE_URL="https://api.nuget.org/v3/index.json"
-```
-
-Now, add `.env` to your `.gitignore` file. This is the most important step.
-
-**File: `.gitignore`**
-
-```
-# ... other ignores
-.env
-.env
-```
-
-### 2. Update the `init.ps1` Script
-
-Let's add the new secrets template to our init script.
-
-**File: `.build/init.ps1` (Updated)**
-
-```powershell
-# ... (top part of the script is the same)
-$SourceEnvExample = Join-Path -Path $ScriptDir -ChildPath "templates/.env.example.template"
-$SourceSecretExample = Join-Path -Path $ScriptDir -ChildPath "templates/.env.example.template" # <-- New
-
-$DestEnvExample = Join-Path -Path $RepoRoot -ChildPath ".env.example"
-$DestSecretExample = Join-Path -Path $RepoRoot -ChildPath ".env.example" # <-- New
-
-# ... (Copy-TemplateFile and Copy-TemplateDirectory functions are the same)
-
-# --- Execute the copy operations ---
-# ... (existing copy operations)
-Copy-TemplateFile -SourcePath $SourceSecretExample -DestinationPath $DestSecretExample -ShouldForce $Force # <-- New
-
-# --- Provide next steps ---
-# ... (update next steps)
-Write-Host " Next steps:"
-Write-Host " 1. Run 'task setup' to restore local tools."
-Write-Host " 2. Copy '.env.example' to '.env' and customize it."
-Write-Host " 3. Copy '.env.example' to '.env' and add your API keys."
-# ...
-```
-
-*(You'll need to create the `.build/templates/.env.example.template` file for this to work).*
-
-### 3. Update the `dotnet` Submodule Taskfile
-
-Let's add the actual `push-nuget` task.
-
-**File: `.build/dotnet/Taskfile.yml` (Updated)**
-
-```yaml
-# ... (existing tasks are unchanged)
-
- push-nuget:
- desc: "Pushes the NuGet package to a feed"
- # This task requires these variables to be set in the environment
- vars:
- NUGET_PACKAGE_PATH: '{{.NUGET_OUTPUT_DIR}}/{{.PROJECT_TO_PACK | replace "csproj" "nupkg" | replace "/" "."}}'
- NUGET_API_KEY: "{{.NUGET_API_KEY}}"
- NUGET_SOURCE_URL: "{{.NUGET_SOURCE_URL}}"
- cmds:
- - >
- dotnet nuget push "{{.NUGET_PACKAGE_PATH}}"
- -k {{.NUGET_API_KEY}}
- -s {{.NUGET_SOURCE_URL}}
- --skip-duplicate
-```
-
-*Note: The `NUGET_PACKAGE_PATH` logic is a bit of a guess at the package name format. You may need to adjust it if your project/package naming is different.*
-
-### 4. Update the Root `Taskfile.yml`
-
-Finally, let's orchestrate the new push task and tell Task to load the secrets file.
-
-**File: `Taskfile.yml` (Updated)**
-
-```yaml
-# https://taskfile.dev
-version: "3"
-
-# Tell Task to load variables from both .env and .env if they exist.
-dotenv: [".env", ".env"]
-
-# ... (vars and includes are unchanged)
-
-tasks:
- # ... (default, clean, build, test, setup, get-version are unchanged)
- # ... (publish-app, pack:nuget, pack:velopack are unchanged)
-
- push:nuget:
- desc: "Packs and pushes the library to a NuGet feed"
- deps: [pack:nuget]
- preconditions:
- # Precondition to ensure the API key is set
- - sh: '[ -n "$NUGET_API_KEY" ]'
- msg: "NUGET_API_KEY is not set. Please add it to .env."
- cmds:
- - task: dotnet:push-nuget
-```
-
-### How It All Works
-
-1. **Local Development**:
- * You run `init.ps1`.
- * You copy `.env.example` to `.env` and paste your real NuGet API key into it.
- * You run `task push:nuget`.
- * Task reads your `Taskfile.yml` and sees `dotenv: [".env", ".env"]`. It loads all variables from both files.
- * The `push:nuget` task runs, checks that `NUGET_API_KEY` exists, and then calls the submodule task, which uses the secret key in the `dotnet nuget push` command.
- * Your `.env` file is safely on your local machine, ignored by Git.
-
-2. **CI/CD (e.g., GitHub Actions)**:
- * You store your NuGet API key as a "Repository Secret" in GitHub settings.
- * In your workflow YAML, you pass this secret to the task as an environment variable.
-
- ```yaml
- - name: Push NuGet Package
- env:
- NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
- run: task push:nuget
- ```
- * The `task push:nuget` command runs, and Task automatically sees that `NUGET_API_KEY` is already in the environment. It uses it just as if it came from the `.env` file.
-
-This is the standard, secure, and flexible way to manage secrets for automation.
\ No newline at end of file
diff --git a/.build/dotnet/Taskfile.yml b/.build/dotnet/Taskfile.yml
deleted file mode 100644
index fb9dbd5..0000000
--- a/.build/dotnet/Taskfile.yml
+++ /dev/null
@@ -1,81 +0,0 @@
-# ./.build/dotnet.yml
-version: "3"
-
-vars:
- SOLUTION_FILE: "{{.SOLUTION_FILE}}"
- CONFIGURATION: '{{.CONFIGURATION | default "Release"}}'
- OUTPUT_DIR: '{{.OUTPUT_DIR | default "dist"}}'
- NUGET_OUTPUT_DIR: "{{.OUTPUT_DIR}}/nuget"
-
-tasks:
- # ... (clean, restore, build, test are unchanged) ...
- clean:
- desc: "Runs 'dotnet clean' on the solution"
- cmds:
- - dotnet clean "{{.SOLUTION_FILE}}"
-
- restore:
- desc: "Restores NuGet packages for the solution"
- cmds:
- - dotnet restore "{{.SOLUTION_FILE}}"
-
- build:
- desc: "Builds the solution in {{.CONFIGURATION}} configuration"
- deps: [restore]
- cmds:
- - dotnet build "{{.SOLUTION_FILE}}" -c {{.CONFIGURATION}} --no-restore
-
- test:
- desc: "Runs all tests in the solution"
- deps: [build]
- cmds:
- - dotnet test "{{.SOLUTION_FILE}}" -c {{.CONFIGURATION}} --no-build -tl:off
-
- lint:
- desc: "Runs the linter on the solution"
- cmds:
- - dotnet csharpier check .
- format:
- desc: "Formats the codebase using CSharpier"
- cmds:
- - dotnet csharpier format .
-
- # This task is specifically for publishing a runnable application's files
- publish-app:
- desc: "Publishes a runnable application to a directory"
- deps: [clean]
- vars:
- PROJECT_TO_PUBLISH: "{{.PROJECT_TO_PUBLISH}}"
- OS: "{{.OS}}"
- ARCH: "{{.ARCH}}"
- RID: "{{.OS}}-{{.ARCH}}"
- PUBLISH_DIR: "{{.OUTPUT_DIR}}/publish/{{.RID}}"
- cmds:
- - echo "Publishing {{.PROJECT_TO_PUBLISH}} for {{.OS}}/{{.ARCH}} to {{.PUBLISH_DIR}}"
- - dotnet publish "src/{{.PROJECT_TO_PUBLISH}}" -c {{.CONFIGURATION}} -r {{.RID}} -o "{{.PUBLISH_DIR}}"
- preconditions:
- - '{{ ne .OS "" }}'
- - '{{ ne .ARCH "" }}'
- - '{{ ne .PROJECT_TO_PUBLISH "" }}'
-
- # This task is specifically for creating a NuGet package from a library
- pack-nuget:
- desc: "Packs the project as a NuGet package"
- vars:
- PROJECT_TO_PACK: "{{.PROJECT_TO_PACK}}"
- cmds:
- - dotnet pack "src/{{.PROJECT_TO_PACK}}" -c {{.CONFIGURATION}} --no-build -o {{.NUGET_OUTPUT_DIR}}
-
- push-nuget:
- desc: "Pushes the NuGet package to a feed"
- dir: "{{.NUGET_OUTPUT_DIR}}"
- # This task requires these variables to be set in the environment
- preconditions:
- - '{{ ne .NUGET_SOURCE_URL "" }}'
- - msg: "NUGET_SOURCE_URL is not set in your .env/public.env file."
- cmds:
- - >
- dotnet nuget push "*.nupkg"
- -k {{.NUGET_API_KEY}}
- -s {{.NUGET_SOURCE_URL}}
- --skip-duplicate
diff --git a/.build/init.ps1 b/.build/init.ps1
deleted file mode 100644
index 12b9e22..0000000
--- a/.build/init.ps1
+++ /dev/null
@@ -1,77 +0,0 @@
-# .build/init.ps1
-[CmdletBinding()]
-param (
- # Use the -Force switch to overwrite existing files in the project root.
- [Switch]$Force
-)
-
-# --- Function to safely copy a file ---
-function Copy-TemplateFile {
- param(
- [string]$SourcePath,
- [string]$DestinationPath,
- [bool]$ShouldForce
- )
- $FileName = Split-Path -Path $DestinationPath -Leaf
- if ((Test-Path $DestinationPath) -and -not $ShouldForce) {
- Write-Host "File '$FileName' already exists in the project root. Use -Force to overwrite." -ForegroundColor Yellow
- }
- else {
- Write-Host "Creating root '$FileName'..." -ForegroundColor Green
- Copy-Item -Path $SourcePath -Destination $DestinationPath -Force
- }
-}
-
-# --- Function to safely copy a directory ---
-function Copy-TemplateDirectory {
- param(
- [string]$SourcePath,
- [string]$DestinationPath,
- [bool]$ShouldForce
- )
- $DirName = Split-Path -Path $DestinationPath -Leaf
- if ((Test-Path $DestinationPath) -and -not $ShouldForce) {
- Write-Host "Directory '$DirName' already exists in the project root. Use -Force to overwrite." -ForegroundColor Yellow
- }
- else {
- Write-Host "Creating root '$DirName' directory..." -ForegroundColor Green
- Copy-Item -Path $SourcePath -Destination $DestinationPath -Recurse -Force
- }
-}
-
-# Get the directory of the script itself (e.g., '.build/')
-$ScriptDir = $PSScriptRoot
-# Get the root directory of the repository (which is one level up)
-$RepoRoot = Resolve-Path (Join-Path -Path $ScriptDir -ChildPath "..")
-
-Write-Host "Initializing build system in '$RepoRoot'..." -ForegroundColor Cyan
-
-# --- Define Source and Destination Paths ---
-$SourceTaskfile = Join-Path -Path $ScriptDir -ChildPath "templates/Taskfile.yml.template"
-$SourcePublicEnv = Join-Path -Path $ScriptDir -ChildPath "templates/public.env.example"
-$SourceEnvExample = Join-Path -Path $ScriptDir -ChildPath "templates/.env.example"
-$SourceBuildProps = Join-Path -Path $ScriptDir -ChildPath "templates/Directory.Build.props.template"
-$SourceToolConfig = Join-Path -Path $ScriptDir -ChildPath "dotnet/.config"
-
-$DestTaskfile = Join-Path -Path $RepoRoot -ChildPath "Taskfile.yml"
-$DestSecretExample = Join-Path -Path $RepoRoot -ChildPath "public.env"
-$DestEnvExample = Join-Path -Path $RepoRoot -ChildPath ".env.example"
-$DestBuildProps = Join-Path -Path $RepoRoot -ChildPath "Directory.Build.props"
-$DestToolConfig = Join-Path -Path $RepoRoot -ChildPath ".config"
-
-# --- Execute the copy operations ---
-Copy-TemplateFile -SourcePath $SourceTaskfile -DestinationPath $DestTaskfile -ShouldForce $Force
-Copy-TemplateFile -SourcePath $SourceEnvExample -DestinationPath $DestEnvExample -ShouldForce $Force
-Copy-TemplateFile -SourcePath $SourcePublicEnv -DestinationPath $DestSecretExample -ShouldForce $Force
-Copy-TemplateFile -SourcePath $SourceBuildProps -DestinationPath $DestBuildProps -ShouldForce $Force # <-- New
-Copy-TemplateDirectory -SourcePath $SourceToolConfig -DestinationPath $DestToolConfig -ShouldForce $Force # <-- New
-
-dotnet tool restore
-
-# --- Provide next steps ---
-Write-Host "`n✅ Initialization complete." -ForegroundColor White
-Write-Host " Next steps:"
-Write-Host " 1. Run 'task setup' to restore local tools." -ForegroundColor Yellow
-Write-Host " 2. Copy '.env.example' to '.env' and customize it"
-Write-Host " 3. Create your first Git tag (e.g., git tag v0.1.0)"
-Write-Host " 4. Run 'task build' to build with the new version"
\ No newline at end of file
diff --git a/.build/targets.cs b/.build/targets.cs
new file mode 100644
index 0000000..1f08581
--- /dev/null
+++ b/.build/targets.cs
@@ -0,0 +1,156 @@
+#!/usr/bin/dotnet run
+
+#:package McMaster.Extensions.CommandLineUtils@4.1.1
+#:package Bullseye@6.0.0
+#:package SimpleExec@12.0.0
+
+using Bullseye;
+using McMaster.Extensions.CommandLineUtils;
+using static Bullseye.Targets;
+using static SimpleExec.Command;
+
+using var app = new CommandLineApplication { UsePagerForHelpText = false };
+app.HelpOption();
+
+var solutionOption = app.Option(
+ "-s|--solution ",
+ "The solution file to operate on.",
+ CommandOptionType.SingleValue,
+ opts => opts.DefaultValue = "DotNetPathUtils.slnx"
+);
+
+var packProjectOption = app.Option(
+ "--packProject ",
+ "The project file to pack into a NuGet package.",
+ CommandOptionType.SingleValue,
+ opts => opts.DefaultValue = "src/DotNetPathUtils/DotNetPathUtils.csproj"
+);
+var configurationOption = app.Option(
+ "-c|--configuration ",
+ "The build configuration.",
+ CommandOptionType.SingleValue,
+ opts => opts.DefaultValue = "Release"
+);
+
+var versionOption = app.Option(
+ "--version ",
+ "The version to use for packing.",
+ CommandOptionType.SingleValue
+);
+app.Argument(
+ "targets",
+ "A list of targets to run or list. If not specified, the \"default\" target will be run, or all targets will be listed.",
+ true
+);
+foreach (var (aliases, description) in Options.Definitions)
+{
+ _ = app.Option(string.Join("|", aliases), description, CommandOptionType.NoValue);
+}
+
+app.OnExecuteAsync(async _ =>
+{
+ var root = Directory.GetCurrentDirectory();
+ var configuration = configurationOption.Value();
+ var solution = solutionOption.Value();
+
+ var targets = app.Arguments[0].Values.OfType();
+ var options = new Options(
+ Options.Definitions.Select(d =>
+ (
+ d.Aliases[0],
+ app.Options.Single(o => d.Aliases.Contains($"--{o.LongName}")).HasValue()
+ )
+ )
+ );
+
+ Target(
+ "clean",
+ () =>
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(solution);
+ ArgumentException.ThrowIfNullOrWhiteSpace(configuration);
+ return RunAsync("dotnet", $"clean {solution} --configuration {configuration}");
+ }
+ );
+
+ Target(
+ "restore",
+ () =>
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(solution);
+ return RunAsync("dotnet", $"restore {solution}");
+ }
+ );
+
+ Target(
+ "build",
+ ["restore"],
+ () =>
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(solution);
+ ArgumentException.ThrowIfNullOrWhiteSpace(configuration);
+ return RunAsync(
+ "dotnet",
+ $"build {solution} --configuration {configuration} --no-restore"
+ );
+ }
+ );
+
+ Target(
+ "test",
+ ["build"],
+ async () =>
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(solution);
+ ArgumentException.ThrowIfNullOrWhiteSpace(configuration);
+
+ // Note: Code coverage requires .NET 10 SDK or later, and
+ // the extension package Microsoft.Testing.Extensions.CodeCoverage
+ var testResultFolder = "TestResults";
+ var coverageFileName = "coverage.xml";
+ var testResultPath = Directory.CreateDirectory(Path.Combine(root, testResultFolder));
+ await RunAsync(
+ "dotnet",
+ $"test --solution {solution} --configuration {configuration} --no-build --coverage --coverage-output {Path.Combine(testResultPath.FullName, coverageFileName)} --coverage-output-format xml --ignore-exit-code 8"
+ );
+ }
+ );
+
+ Target(
+ "default",
+ ["build"],
+ () => Console.WriteLine("Default target ran, which depends on 'build'.")
+ );
+
+ Target(
+ "pack",
+ dependsOn: ["build"],
+ async () =>
+ {
+ var packProject = packProjectOption.Value();
+
+ ArgumentException.ThrowIfNullOrWhiteSpace(packProject);
+
+ var nugetOutputDir = Path.Combine(root, "dist", "nuget");
+
+ await RunAsync(
+ "dotnet",
+ $"pack {packProject} -c {configuration} -o {nugetOutputDir} --no-build"
+ );
+
+ var files = Directory.GetFiles(nugetOutputDir, "*.nupkg");
+ if (files.Length == 0)
+ {
+ throw new InvalidOperationException("No NuGet package was created.");
+ }
+ foreach (var file in files)
+ {
+ Console.WriteLine($"NuGet package created: {file}");
+ }
+ }
+ );
+
+ await RunTargetsAndExitAsync(targets, options);
+});
+
+return await app.ExecuteAsync(args);
diff --git a/.build/templates/.env.example b/.build/templates/.env.example
deleted file mode 100644
index 477906c..0000000
--- a/.build/templates/.env.example
+++ /dev/null
@@ -1,2 +0,0 @@
- NUGET_API_KEY="your_nuget_api_key_here"
-NUGET_SOURCE_URL="https://api.nuget.org/v3/index.json"
\ No newline at end of file
diff --git a/.build/templates/Directory.Build.props.template b/.build/templates/Directory.Build.props.template
deleted file mode 100644
index 4f093ba..0000000
--- a/.build/templates/Directory.Build.props.template
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
-
- v
-
-
- normal
-
-
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
-
diff --git a/.build/templates/Taskfile.yml.template b/.build/templates/Taskfile.yml.template
deleted file mode 100644
index 6a2d280..0000000
--- a/.build/templates/Taskfile.yml.template
+++ /dev/null
@@ -1,122 +0,0 @@
-# https://taskfile.dev
-version: "3"
-
-dotenv: ["public.env", ".env"]
-
-# This Taskfile sources its configuration from a .env file in the root directory.
-# To get started, copy .env.example to .env and customize it for your project.
-vars:
- MAIN_EXE: "{{if .PROJECT_TO_PUBLISH}}{{.APP_NAME}}.exe{{end}}"
-
-includes:
- dotnet: ./.build/dotnet/Taskfile.yml
- velopack:
- taskfile: ./.build/velopack/Taskfile.yml
- optional: true
- internal: true
-
-tasks:
- default:
- desc: "Builds the entire solution (run this first!)"
- preconditions:
- - sh: |
- {{if eq .OS "windows"}}
- powershell -Command "exit !(Test-Path .env -PathType Leaf)"
- {{else}}
- [ -f .env ]
- {{end}}
- msg: "Configuration file not found. Please copy '.env.example' to '.env' and fill it out."
- cmds:
- - task: build
-
- clean:
- desc: "Cleans the solution"
- cmds:
- - task: dotnet:clean
-
- build:
- desc: "Builds the solution"
- cmds:
- - task: dotnet:build
-
- test:
- desc: "Runs all tests"
- cmds:
- - task: dotnet:test
-
- # --- Publishing and Packing Tasks ---
- publish-app:
- desc: "Publishes the main executable project files"
- preconditions:
- # Precondition now directly checks if the variable is set.
- - sh: |
- {{if eq .OS "windows"}}
- powershell -Command "exit !($env:PROJECT_TO_PUBLISH)"
- {{else}}
- [ -n "$PROJECT_TO_PUBLISH" ]
- {{end}}
- msg: "Not an executable application. Set PROJECT_TO_PUBLISH in .env to enable."
- cmds:
- - task: dotnet:publish-app
-
- setup:
- desc: "Restores local .NET tools defined in the manifest"
- cmds:
- - dotnet tool restore
- preconditions:
- - sh: "[ -f .config/dotnet-tools.json ]"
- msg: "Tool manifest '.config/dotnet-tools.json' not found."
-
- get-version:
- # This internal task gets the version from MinVer and makes it available
- # to other tasks as {{.GET_VERSION}}.
- cmds:
- - dotnet minver --tag-prefix v
- deps: [setup]
-
- pack:nuget:
- desc: "Packs the project as a NuGet package (for libraries)"
- preconditions:
- # Precondition now directly checks if the variable is set.
- - sh: |
- {{if eq .OS "windows"}}
- powershell -Command "exit !($env:PROJECT_TO_PACK)"
- {{else}}
- [ -n "$PROJECT_TO_PACK" ]
- {{end}}
- msg: "Not a library project. Set PROJECT_TO_PACK in .env to enable."
- cmds:
- - task: dotnet:pack-nuget
-
- push:nuget:
- desc: "Packs and pushes the library to a NuGet feed"
- deps: [pack:nuget]
- preconditions:
- # Precondition to ensure the API key is set
- - sh: |
- {{if eq .OS "windows"}}
- powershell -Command "exit !($env:NUGET_API_KEY)"
- {{else}}
- [ -n "$NUGET_API_KEY" ]
- {{end}}
- msg: "NUGET_API_KEY is not set. Please add it to .env"
- cmds:
- - task: dotnet:push-nuget
-
- pack:velopack:
- desc: "Packages the executable application using Velopack"
- deps: [publish-app]
- vars:
- APP_VERSION:
- sh: task get-version
- preconditions:
- # Precondition remains the same, checking for PROJECT_TO_PUBLISH.
- - sh: |
- {{if eq .OS "windows"}}
- powershell -Command "exit !($env:PROJECT_TO_PUBLISH)"
- {{else}}
- [ -n "$PROJECT_TO_PUBLISH" ]
- {{end}}
- msg: "Not an executable application. Set PROJECT_TO_PUBLISH in .env to enable."
- cmds:
- - task: velopack:pack
diff --git a/.build/templates/public.env.example b/.build/templates/public.env.example
deleted file mode 100644
index 228abe6..0000000
--- a/.build/templates/public.env.example
+++ /dev/null
@@ -1,13 +0,0 @@
- # --- General Project Details ---
- SOLUTION_FILE=YourSolutionName.slnx
- OUTPUT_DIR=dist
-
- # --- Scenario=Configure for an Executable Application (for Velopack) ---
- # If this is a class library, leave PROJECT_TO_PUBLISH blank.
- PROJECT_TO_PUBLISH=Your.Project.Name/Your.Project.Name.csproj # relative to src
- PROJECT_TO_PACK=Your.Project.Name # relative to src
- RID=win-x64 # (e.g., win-x64, linux-x64, osx-arm64)
- APP_NAME=sampleApp
- APP_VERSION=1.0.0
- COMPANY_NAME='henry-js'
- ICON_PATH='assets/icon.ico'
\ No newline at end of file
diff --git a/.build/velopack/Taskfile.yml b/.build/velopack/Taskfile.yml
deleted file mode 100644
index f692d8e..0000000
--- a/.build/velopack/Taskfile.yml
+++ /dev/null
@@ -1,33 +0,0 @@
-# ./.build/velopack.yml
-version: "3"
-
-tasks:
- pack:
- vars:
- OS: "{{.OS}}"
- ARCH: "{{.ARCH }}"
- RID: "{{.OS}}-{{.ARCH}}"
- PUBLISH_DIR: "{{.OUTPUT_DIR}}/publish/{{.RID}}"
- RELEASES_DIR: "{{.OUTPUT_DIR}}/releases/{{.RID}}"
- desc: "Packages the published application using Velopack"
- preconditions:
- - sh: |
- {{if eq .OS "windows"}}
- powershell -Command "exit !(Test-Path '{{.ICON_PATH}}' -PathType Leaf)"
- {{else}}
- [ -f '{{.ICON_PATH}}' ]
- {{end}}
- msg: "Velopack icon not found at '{{.ICON_PATH}}'. Check ICON_PATH in .env."
- cmds:
- - echo "Packing application for {{.OS}}/{{.ARCH}}"
- - |
- vpk [{{.OS}}] pack \
- --packId {{.APP_NAME}} \
- --packVersion {{.APP_VERSION}} \
- --packDir "{{.PUBLISH_DIR}}" \
- --mainExe {{if eq .OS "win"}}{{.APP_NAME}}.exe{{else}}{{.APP_NAME}}{{end}} \
- --packAuthors "{{.COMPANY_NAME}}" \
- --packTitle "{{.APP_NAME}}" \
- --outputDir "{{.RELEASES_DIR}}" \
- --runtime {{.RID}} \
- --icon "{{.ICON_PATH}}"
diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index 2d7eb7b..e1cd212 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -2,17 +2,10 @@
"version": 1,
"isRoot": true,
"tools": {
- "minver-cli": {
- "version": "6.0.0",
+ "dotnet-reportgenerator-globaltool": {
+ "version": "5.4.12",
"commands": [
- "minver"
- ],
- "rollForward": false
- },
- "csharpier": {
- "version": "1.0.3",
- "commands": [
- "csharpier"
+ "reportgenerator"
],
"rollForward": false
}
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..7b5424c
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,383 @@
+root = true
+
+# All files
+[*]
+indent_style = space
+
+# Xml files
+[*.xml]
+indent_size = 2
+
+# C# files
+[*.cs]
+
+#### Core EditorConfig Options ####
+
+# Indentation and spacing
+indent_size = 4
+tab_width = 4
+
+# New line preferences
+insert_final_newline = false
+
+#### .NET Coding Conventions ####
+[*.{cs,vb}]
+
+# Organize usings
+dotnet_separate_import_directive_groups = true
+dotnet_sort_system_directives_first = true
+file_header_template = unset
+
+# this. and Me. preferences
+dotnet_style_qualification_for_event = false:silent
+dotnet_style_qualification_for_field = false:silent
+dotnet_style_qualification_for_method = false:silent
+dotnet_style_qualification_for_property = false:silent
+
+# Language keywords vs BCL types preferences
+dotnet_style_predefined_type_for_locals_parameters_members = true:silent
+dotnet_style_predefined_type_for_member_access = true:silent
+
+# Parentheses preferences
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
+
+# Modifier preferences
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
+
+# Expression-level preferences
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_explicit_tuple_names = true:suggestion
+dotnet_style_namespace_match_folder = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+dotnet_style_prefer_auto_properties = true:suggestion
+dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion
+dotnet_style_prefer_compound_assignment = true:suggestion
+dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
+dotnet_style_prefer_conditional_expression_over_return = true:suggestion
+dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed:suggestion
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
+dotnet_style_prefer_inferred_tuple_names = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
+dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
+dotnet_style_prefer_simplified_interpolation = true:suggestion
+
+# Field preferences
+dotnet_style_readonly_field = true:warning
+
+# Parameter preferences
+dotnet_code_quality_unused_parameters = all:suggestion
+
+# Suppression preferences
+dotnet_remove_unnecessary_suppression_exclusions = none
+
+#### C# Coding Conventions ####
+[*.cs]
+
+# var preferences
+csharp_style_var_elsewhere = false:silent
+csharp_style_var_for_built_in_types = false:silent
+csharp_style_var_when_type_is_apparent = false:silent
+
+# Expression-bodied members
+csharp_style_expression_bodied_accessors = true:silent
+csharp_style_expression_bodied_constructors = false:silent
+csharp_style_expression_bodied_indexers = true:silent
+csharp_style_expression_bodied_lambdas = true:suggestion
+csharp_style_expression_bodied_local_functions = false:silent
+csharp_style_expression_bodied_methods = false:silent
+csharp_style_expression_bodied_operators = false:silent
+csharp_style_expression_bodied_properties = true:silent
+
+# Pattern matching preferences
+csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
+csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
+csharp_style_prefer_extended_property_pattern = true:suggestion
+csharp_style_prefer_not_pattern = true:suggestion
+csharp_style_prefer_pattern_matching = true:silent
+csharp_style_prefer_switch_expression = true:suggestion
+
+# Null-checking preferences
+csharp_style_conditional_delegate_call = true:suggestion
+
+# Modifier preferences
+csharp_prefer_static_anonymous_function = true:suggestion
+csharp_prefer_static_local_function = true:warning
+csharp_preferred_modifier_order = public,private,protected,internal,file,const,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async:suggestion
+csharp_style_prefer_readonly_struct = true:suggestion
+csharp_style_prefer_readonly_struct_member = true:suggestion
+
+# Code-block preferences
+csharp_prefer_braces = true:silent
+csharp_prefer_simple_using_statement = true:suggestion
+csharp_style_namespace_declarations = file_scoped:suggestion
+csharp_style_prefer_method_group_conversion = true:silent
+csharp_style_prefer_primary_constructors = true:suggestion
+csharp_style_prefer_top_level_statements = true:silent
+
+# Expression-level preferences
+csharp_prefer_simple_default_expression = true:suggestion
+csharp_style_deconstructed_variable_declaration = true:suggestion
+csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
+csharp_style_inlined_variable_declaration = true:suggestion
+csharp_style_prefer_index_operator = true:suggestion
+csharp_style_prefer_local_over_anonymous_function = true:suggestion
+csharp_style_prefer_null_check_over_type_check = true:suggestion
+csharp_style_prefer_range_operator = true:suggestion
+csharp_style_prefer_tuple_swap = true:suggestion
+csharp_style_prefer_utf8_string_literals = true:suggestion
+csharp_style_throw_expression = true:suggestion
+csharp_style_unused_value_assignment_preference = discard_variable:suggestion
+csharp_style_unused_value_expression_statement_preference = discard_variable:silent
+
+# 'using' directive preferences
+csharp_using_directive_placement = outside_namespace:silent
+
+#### C# Formatting Rules ####
+
+# New line preferences
+csharp_new_line_before_catch = true
+csharp_new_line_before_else = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_open_brace = all
+csharp_new_line_between_query_expression_clauses = true
+
+# Indentation preferences
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = true
+csharp_indent_labels = one_less_than_current
+csharp_indent_switch_labels = true
+
+# Space preferences
+csharp_space_after_cast = false
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_comma = true
+csharp_space_after_dot = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_around_declaration_statements = false
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_before_comma = false
+csharp_space_before_dot = false
+csharp_space_before_open_square_brackets = false
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_between_square_brackets = false
+
+# Wrapping preferences
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = true
+csharp_prefer_system_threading_lock = true:suggestion
+csharp_style_prefer_implicitly_typed_lambda_expression = true:suggestion
+
+#### Naming styles ####
+[*.{cs,vb}]
+
+# Naming rules
+
+dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.symbols = types_and_namespaces
+dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.interfaces_should_be_ipascalcase.severity = suggestion
+dotnet_naming_rule.interfaces_should_be_ipascalcase.symbols = interfaces
+dotnet_naming_rule.interfaces_should_be_ipascalcase.style = ipascalcase
+
+dotnet_naming_rule.type_parameters_should_be_tpascalcase.severity = suggestion
+dotnet_naming_rule.type_parameters_should_be_tpascalcase.symbols = type_parameters
+dotnet_naming_rule.type_parameters_should_be_tpascalcase.style = tpascalcase
+
+dotnet_naming_rule.methods_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.methods_should_be_pascalcase.symbols = methods
+dotnet_naming_rule.methods_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.properties_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.properties_should_be_pascalcase.symbols = properties
+dotnet_naming_rule.properties_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.events_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.events_should_be_pascalcase.symbols = events
+dotnet_naming_rule.events_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.local_variables_should_be_camelcase.severity = suggestion
+dotnet_naming_rule.local_variables_should_be_camelcase.symbols = local_variables
+dotnet_naming_rule.local_variables_should_be_camelcase.style = camelcase
+
+dotnet_naming_rule.local_constants_should_be_camelcase.severity = suggestion
+dotnet_naming_rule.local_constants_should_be_camelcase.symbols = local_constants
+dotnet_naming_rule.local_constants_should_be_camelcase.style = camelcase
+
+dotnet_naming_rule.parameters_should_be_camelcase.severity = suggestion
+dotnet_naming_rule.parameters_should_be_camelcase.symbols = parameters
+dotnet_naming_rule.parameters_should_be_camelcase.style = camelcase
+
+dotnet_naming_rule.public_fields_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.public_fields_should_be_pascalcase.symbols = public_fields
+dotnet_naming_rule.public_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.private_fields_should_be__camelcase.severity = suggestion
+dotnet_naming_rule.private_fields_should_be__camelcase.symbols = private_fields
+dotnet_naming_rule.private_fields_should_be__camelcase.style = _camelcase
+
+dotnet_naming_rule.private_static_fields_should_be_s_camelcase.severity = suggestion
+dotnet_naming_rule.private_static_fields_should_be_s_camelcase.symbols = private_static_fields
+dotnet_naming_rule.private_static_fields_should_be_s_camelcase.style = s_camelcase
+
+dotnet_naming_rule.public_constant_fields_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.public_constant_fields_should_be_pascalcase.symbols = public_constant_fields
+dotnet_naming_rule.public_constant_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.private_constant_fields_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.private_constant_fields_should_be_pascalcase.symbols = private_constant_fields
+dotnet_naming_rule.private_constant_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.symbols = public_static_readonly_fields
+dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.symbols = private_static_readonly_fields
+dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.enums_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.enums_should_be_pascalcase.symbols = enums
+dotnet_naming_rule.enums_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.local_functions_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.local_functions_should_be_pascalcase.symbols = local_functions
+dotnet_naming_rule.local_functions_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.non_field_members_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.non_field_members_should_be_pascalcase.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascalcase.style = pascalcase
+
+# Symbol specifications
+
+dotnet_naming_symbols.interfaces.applicable_kinds = interface
+dotnet_naming_symbols.interfaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.interfaces.required_modifiers =
+
+dotnet_naming_symbols.enums.applicable_kinds = enum
+dotnet_naming_symbols.enums.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.enums.required_modifiers =
+
+dotnet_naming_symbols.events.applicable_kinds = event
+dotnet_naming_symbols.events.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.events.required_modifiers =
+
+dotnet_naming_symbols.methods.applicable_kinds = method
+dotnet_naming_symbols.methods.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.methods.required_modifiers =
+
+dotnet_naming_symbols.properties.applicable_kinds = property
+dotnet_naming_symbols.properties.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.properties.required_modifiers =
+
+dotnet_naming_symbols.public_fields.applicable_kinds = field
+dotnet_naming_symbols.public_fields.applicable_accessibilities = public, internal
+dotnet_naming_symbols.public_fields.required_modifiers =
+
+dotnet_naming_symbols.private_fields.applicable_kinds = field
+dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
+dotnet_naming_symbols.private_fields.required_modifiers =
+
+dotnet_naming_symbols.private_static_fields.applicable_kinds = field
+dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
+dotnet_naming_symbols.private_static_fields.required_modifiers = static
+
+dotnet_naming_symbols.types_and_namespaces.applicable_kinds = namespace, class, struct, interface, enum
+dotnet_naming_symbols.types_and_namespaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.types_and_namespaces.required_modifiers =
+
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
+dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.non_field_members.required_modifiers =
+
+dotnet_naming_symbols.type_parameters.applicable_kinds = namespace
+dotnet_naming_symbols.type_parameters.applicable_accessibilities = *
+dotnet_naming_symbols.type_parameters.required_modifiers =
+
+dotnet_naming_symbols.private_constant_fields.applicable_kinds = field
+dotnet_naming_symbols.private_constant_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
+dotnet_naming_symbols.private_constant_fields.required_modifiers = const
+
+dotnet_naming_symbols.local_variables.applicable_kinds = local
+dotnet_naming_symbols.local_variables.applicable_accessibilities = local
+dotnet_naming_symbols.local_variables.required_modifiers =
+
+dotnet_naming_symbols.local_constants.applicable_kinds = local
+dotnet_naming_symbols.local_constants.applicable_accessibilities = local
+dotnet_naming_symbols.local_constants.required_modifiers = const
+
+dotnet_naming_symbols.parameters.applicable_kinds = parameter
+dotnet_naming_symbols.parameters.applicable_accessibilities = *
+dotnet_naming_symbols.parameters.required_modifiers =
+
+dotnet_naming_symbols.public_constant_fields.applicable_kinds = field
+dotnet_naming_symbols.public_constant_fields.applicable_accessibilities = public, internal
+dotnet_naming_symbols.public_constant_fields.required_modifiers = const
+
+dotnet_naming_symbols.public_static_readonly_fields.applicable_kinds = field
+dotnet_naming_symbols.public_static_readonly_fields.applicable_accessibilities = public, internal
+dotnet_naming_symbols.public_static_readonly_fields.required_modifiers = readonly, static
+
+dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field
+dotnet_naming_symbols.private_static_readonly_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
+dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = readonly, static
+
+dotnet_naming_symbols.local_functions.applicable_kinds = local_function
+dotnet_naming_symbols.local_functions.applicable_accessibilities = *
+dotnet_naming_symbols.local_functions.required_modifiers =
+
+# Naming styles
+
+dotnet_naming_style.pascalcase.required_prefix =
+dotnet_naming_style.pascalcase.required_suffix =
+dotnet_naming_style.pascalcase.word_separator =
+dotnet_naming_style.pascalcase.capitalization = pascal_case
+
+dotnet_naming_style.ipascalcase.required_prefix = I
+dotnet_naming_style.ipascalcase.required_suffix =
+dotnet_naming_style.ipascalcase.word_separator =
+dotnet_naming_style.ipascalcase.capitalization = pascal_case
+
+dotnet_naming_style.tpascalcase.required_prefix = T
+dotnet_naming_style.tpascalcase.required_suffix =
+dotnet_naming_style.tpascalcase.word_separator =
+dotnet_naming_style.tpascalcase.capitalization = pascal_case
+
+dotnet_naming_style._camelcase.required_prefix = _
+dotnet_naming_style._camelcase.required_suffix =
+dotnet_naming_style._camelcase.word_separator =
+dotnet_naming_style._camelcase.capitalization = camel_case
+
+dotnet_naming_style.camelcase.required_prefix =
+dotnet_naming_style.camelcase.required_suffix =
+dotnet_naming_style.camelcase.word_separator =
+dotnet_naming_style.camelcase.capitalization = camel_case
+
+dotnet_naming_style.s_camelcase.required_prefix = s_
+dotnet_naming_style.s_camelcase.required_suffix =
+dotnet_naming_style.s_camelcase.word_separator =
+dotnet_naming_style.s_camelcase.capitalization = camel_case
+tab_width = 4
+indent_size = 4
+end_of_line = crlf
+
diff --git a/.env.example b/.env.example
deleted file mode 100644
index 4f86eab..0000000
--- a/.env.example
+++ /dev/null
@@ -1 +0,0 @@
-NUGET_API_KEY="your_nuget_api_key_here"
\ No newline at end of file
diff --git a/.github/workflows/build-prerelease.yml b/.github/workflows/build-prerelease.yml
index b230663..552a3e6 100644
--- a/.github/workflows/build-prerelease.yml
+++ b/.github/workflows/build-prerelease.yml
@@ -1,70 +1,42 @@
name: Build Pre-release
on:
- # This makes the workflow completely manual.
+ push:
+ branches:
+ - main
workflow_dispatch:
- # You can optionally add inputs for the person running the workflow.
- inputs:
- reason:
- description: 'Reason for building the pre-release'
- required: true
- default: 'Testing release candidate'
jobs:
- check-config:
- name: Check Project Configuration
- runs-on: ubuntu-latest
- outputs:
- is_library: ${{ steps.check_file.outputs.is_library }}
- is_app: ${{ steps.check_file.outputs.is_app }}
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
- - name: cat public.env
- run: cat public.env
- - name: Read configuration from public.env
- id: check_file
- run: |
- IS_LIBRARY="false"
- IS_APP="false"
- while IFS= read -r line || [[ -n "$line" ]]; do
- case "$line" in
- PROJECT_TO_PACK=*)
- value="${line#*=}"
- if [[ -n "$value" ]]; then IS_LIBRARY="true"; fi
- ;;
- PROJECT_TO_PUBLISH=*)
- value="${line#*=}"
- if [[ -n "$value" ]]; then IS_APP="true"; fi
- ;;
- esac
- done < public.env
- echo "is_library=${IS_LIBRARY}" >> $GITHUB_OUTPUT
- echo "is_app=${IS_APP}" >> $GITHUB_OUTPUT
-
build-library:
name: Build NuGet Pre-release
- needs: check-config
- if: needs.check-config.outputs.is_library == 'true'
+ if: vars.PROJECT_TO_PACK != ''
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with: { fetch-depth: 0 }
+
- name: Setup .NET
uses: actions/setup-dotnet@v4
+ with:
+ global-json-file: global.json
- name: Restore .NET tools
run: dotnet tool restore
- - name: Setup Taskfile
- uses: arduino/setup-task@v2
- with:
- repo-token: ${{ secrets.GITHUB_TOKEN }}
- - name: Get pre-release version and set ENV
- run: echo "APP_VERSION=$(task get-version)" >> $GITHUB_ENV
+
+ - name: Get pre-release version
+ id: version
+ run: |
+ # Run the new dnx command and pipe to tail to get only the last line (the version).
+ VERSION=$(dnx minver-cli -y --default-pre-release-identifiers preview | tail -n 1)
+ echo "Discovered version: $VERSION"
+ echo "APP_VERSION=$VERSION" >> $GITHUB_ENV
+
- name: Run Tests
- run: task test
+ run: dotnet run .build/targets.cs test
+
- name: Pack NuGet Pre-release Package
- run: task pack-nuget
+ run: dotnet run .build/targets.cs pack --version ${{ env.APP_VERSION }}
+
- name: Upload NuGet Artifact
uses: actions/upload-artifact@v4
with:
@@ -73,11 +45,10 @@ jobs:
build-application:
name: Build App for ${{ matrix.os }}-${{ matrix.arch }}
- needs: check-config
- if: needs.check-config.outputs.is_app == 'true'
+ if: vars.PROJECT_TO_PUBLISH != ''
runs-on: ${{ matrix.runner_os }}
strategy:
- fail-fast: false
+ fail-fast: false
matrix:
include:
- { runner_os: windows-latest, os: win, arch: x64 }
@@ -85,39 +56,31 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
- with: { fetch-depth: 0 }
+ with: { fetch-depth: 0 }
+
- name: Setup .NET
uses: actions/setup-dotnet@v4
- - name: Setup Taskfile
- uses: arduino/setup-task@v2
with:
- repo-token: ${{ secrets.GITHUB_TOKEN }}
- - name: Add .NET tools to PATH
- if: runner.os != 'Windows'
- run: echo "$HOME/.dotnet/tools" >> $GITHUB_PATH
- - name: Get pre-release version and set ENV
- id: version # Keep the id for the artifact name
- shell: bash # Use bash for consistency on all runners
- run: |
- # 1. Capture the output
- VERSION_OUTPUT=$(task get-version)
+ global-json-file: global.json
- # 2. Validate the output
- if [ -z "$VERSION_OUTPUT" ]; then
- echo "::error::'task get-version' failed or produced an empty string."
- exit 1
- fi
+ - name: Restore .NET tools
+ run: dotnet tool restore
+
+ - name: Get pre-release version
+ id: version
+ run: |
+ VERSION=$(dnx minver-cli -y -p preview | tail -n 1)
+ echo "Discovered version: $VERSION"
+ echo "APP_VERSION=$VERSION" >> $GITHUB_ENV
- # 3. If validation passes, set the environment and output variables
- echo "Discovered version: ${VERSION_OUTPUT}"
- echo "APP_VERSION=${VERSION_OUTPUT}" >> $GITHUB_ENV
- echo "version=${VERSION_OUTPUT}" >> $GITHUB_OUTPUT
- name: Run Tests
- run: task test
- - name: Build and Pack Pre-release for ${{ matrix.os }}-${{ matrix.arch }}
- run: task build-platform-release OS=${{ matrix.os }} ARCH=${{ matrix.arch }} APP_VERSION=${{ env.APP_VERSION }}
+ run: dotnet run .build/targets.cs test
+
+ - name: Build and Publish Pre-release for ${{ matrix.os }}-${{ matrix.arch }}
+ run: dotnet run .build/targets.cs publish --os ${{ matrix.os }} --arch ${{ matrix.arch }} --publishProject ${{ vars.PROJECT_TO_PUBLISH }} --version ${{ env.APP_VERSION }}
+
- name: Upload Application Artifact
uses: actions/upload-artifact@v4
with:
name: app-artifact-${{ matrix.os }}-${{ matrix.arch }}-${{ env.APP_VERSION }}
- path: dist/releases/
\ No newline at end of file
+ path: dist/publish/
\ No newline at end of file
diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml
index 2eedf6b..d7e348a 100644
--- a/.github/workflows/pr-validation.yml
+++ b/.github/workflows/pr-validation.yml
@@ -1,12 +1,16 @@
name: Pull Request Validation
on:
-
pull_request:
branches:
- 'main'
types: [opened, synchronize, reopened]
+permissions:
+ contents: read
+ pull-requests: write
+ actions: write
+
jobs:
validate:
name: Build & Test PR
@@ -22,22 +26,37 @@ jobs:
with:
global-json-file: global.json
- - name: Add .NET tools to PATH
- run: echo "$HOME/.dotnet/tools" >> $GITHUB_PATH
-
- name: Restore .NET tools
run: dotnet tool restore
- - name: Setup Taskfile
- uses: arduino/setup-task@v2
- with:
- repo-token: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Check Environment Variables
- run: 'echo "HOME variable is: [$HOME]" && echo "XDG_CONFIG_HOME is: [$XDG_CONFIG_HOME]"'
+ - name: Run Tests
+ run: dotnet run .build/targets.cs test
- - name: Run Linter
- run: task lint
+ - name: ReportGenerator
+ uses: danielpalme/ReportGenerator-GitHub-Action@5.4.12
+ with:
+ reports: TestResults/coverage.xml
+ targetdir: coveragereport
+ reporttypes: 'Html;MarkdownSummaryGithub;Badges'
- - name: Run Tests
- run: task test
\ No newline at end of file
+ - name: Upload full coverage report artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: coverage-report
+ path: coveragereport/
+
+ - name: Add coverage summary to pull request
+ # This step only runs for pull requests
+ if: github.event_name == 'pull_request'
+ run: |
+ # Use the official GitHub CLI to post a comment
+ # It will find and update its own previous comment, or create a new one
+ gh pr comment ${{ github.event.number }} --body-file coveragereport/SummaryGithub.md
+ env:
+ # The GITHUB_TOKEN is automatically provided by GitHub Actions
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Add coverage summary to job summary
+ # This adds the same markdown report to the GitHub Actions summary page
+ run: cat coveragereport/SummaryGithub.md >> $GITHUB_STEP_SUMMARY
+ shell: bash
\ No newline at end of file
diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml
index 17ad00e..85c7767 100644
--- a/.github/workflows/publish-release.yml
+++ b/.github/workflows/publish-release.yml
@@ -9,83 +9,28 @@ permissions:
contents: write
jobs:
- check-config:
- name: Check Project Configuration
- runs-on: ubuntu-latest
- outputs:
- is_library: ${{ steps.check_file.outputs.is_library }}
- is_app: ${{ steps.check_file.outputs.is_app }}
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
- - name: Read configuration from public.env
- id: check_file
- run: |
- # Initialize variables to a known default state.
- IS_LIBRARY="false"
- IS_APP="false"
-
- echo "--- Reading public.env ---"
- # This robust loop reads the file line by line, ignoring line endings.
- while IFS= read -r line || [[ -n "$line" ]]; do
- # Use case statement for clean, exact matching.
- case "$line" in
- # Match lines that start with "PROJECT_TO_PACK="
- PROJECT_TO_PACK=*)
- # Get the value after the '='
- value="${line#*=}"
- # Check if the value is not an empty string.
- if [[ -n "$value" ]]; then
- echo "Found non-empty PROJECT_TO_PACK"
- IS_LIBRARY="true"
- fi
- ;;
- # Match lines that start with "PROJECT_TO_PUBLISH="
- PROJECT_TO_PUBLISH=*)
- value="${line#*=}"
- if [[ -n "$value" ]]; then
- echo "Found non-empty PROJECT_TO_PUBLISH"
- IS_APP="true"
- fi
- ;;
- esac
- done < public.env
-
- echo "--- Setting outputs ---"
- echo "is_library=${IS_LIBRARY}" >> $GITHUB_OUTPUT
- echo "is_app=${IS_APP}" >> $GITHUB_OUTPUT
-
- # Add extra logging for easy debugging in the Actions UI
- echo "is_library is ${IS_LIBRARY}"
- echo "is_app is ${IS_APP}"
-
release-nuget:
name: Publish NuGet Package
- needs: check-config
- if: needs.check-config.outputs.is_library == 'true'
+ if: vars.PROJECT_TO_PACK != ''
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
- - name: Restore .NET tools
- run: dotnet tool restore
- - name: Setup Taskfile
- uses: arduino/setup-task@v2
- - name: Create .env file with secrets
- run: echo "NUGET_API_KEY=${{ secrets.NUGET_API_KEY }}" > .env
- env:
- NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
+ with:
+ global-json-file: global.json
- name: Run Tests
- run: task test
- - name: Pack and Push NuGet Package
- run: task push-nuget
+ run: dotnet run .build/targets.cs test
+ - name: Pack NuGet Package
+ run: dotnet run .build/targets.cs pack --packProject ${{ vars.PROJECT_TO_PACK }}
+ - name: Push NuGet Package
+ run: dotnet nuget push "dist/nuget/*.nupkg" --api-key ${{ secrets.NUGET_API_KEY }} --source "https://api.nuget.org/v3/index.json" --skip-duplicate
+
release-velopack:
name: Prepare Velopack Release
- needs: check-config
- if: needs.check-config.outputs.is_app == 'true'
+ if: vars.PROJECT_TO_PUBLISH != ''
runs-on: ubuntu-latest
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
@@ -93,18 +38,15 @@ jobs:
- name: Create GitHub Release
id: create_release
uses: actions/create-release@v1
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref_name }}
release_name: Release ${{ github.ref_name }}
- # Job 4: Build the application on a matrix.
build-and-publish-velopack:
name: Build & Publish for ${{ matrix.os }}-${{ matrix.arch }}
needs: release-velopack # Depends on the release placeholder being created.
- # The 'if' condition is inherited from the 'needs' dependency.
- # If release-velopack is skipped, this job will also be skipped.
runs-on: ${{ matrix.runner_os }}
strategy:
matrix:
@@ -116,40 +58,33 @@ jobs:
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
- - name: Setup Taskfile
- uses: arduino/setup-task@v2
with:
- repo-token: ${{ secrets.GITHUB_TOKEN }}
- - name: Add .NET tools to PATH
- if: runner.os != 'Windows'
- run: echo "$HOME/.dotnet/tools" >> $GITHUB_PATH
+ global-json-file: global.json
- name: Run Tests
- run: task test
- - name: Build and Pack for ${{ matrix.os }}-${{ matrix.arch }}
- run: task build-platform-release OS=${{ matrix.os }} ARCH=${{ matrix.arch }} APP_VERSION=${{ github.ref_name }}
+ run: dotnet run .build/targets.cs test
+ - name: Build and Publish for ${{ matrix.os }}-${{ matrix.arch }}
+ run: dotnet run .build/targets.cs publish --os ${{ matrix.os }} --arch ${{ matrix.arch }} --publishProject ${{ vars.PROJECT_TO_PUBLISH }}
- name: Upload Artifacts
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
RID="${{ matrix.os }}-${{ matrix.arch }}"
- SEARCH_PATH="./dist/releases/$RID"
+ SEARCH_PATH="./dist/publish/$RID"
echo "Searching for release asset in: $SEARCH_PATH"
- # Use find to build a list of all files we want to upload.
- # The -o flag means "OR".
+ # WARNING: Your build script runs a standard 'dotnet publish'. This upload step
+ # is looking for specific Velopack installers which are not created by default.
+ # This step will likely fail unless your .csproj is configured to produce
+ # these files (e.g., -Setup.exe, .AppImage) during the publish step.
if [[ "${{ matrix.os }}" == "win" ]]; then
- # For Windows, find the -Setup.exe AND the -Portable.zip
FILES_TO_UPLOAD=$(find "$SEARCH_PATH" \( -name "*-Setup.exe" -o -name "*-Portable.zip" \))
elif [[ "${{ matrix.os }}" == "linux" ]]; then
- # For Linux, find the .AppImage AND the -Portable.zip
FILES_TO_UPLOAD=$(find "$SEARCH_PATH" \( -name "*.AppImage" -o -name "*-Portable.zip" \))
elif [[ "${{ matrix.os }}" == "osx" ]]; then
- # For macOS, find ALL .zip files (which includes the main app and portable)
FILES_TO_UPLOAD=$(find "$SEARCH_PATH" -name "*.zip")
fi
- # Check if we found any files before trying to upload.
if [[ -z "$FILES_TO_UPLOAD" ]]; then
echo "::error::Could not find any release assets to upload in $SEARCH_PATH"
exit 1
@@ -159,5 +94,4 @@ jobs:
echo "$FILES_TO_UPLOAD"
echo "------------------------------"
- # Use xargs to pass the list of files to the gh command.
- echo "$FILES_TO_UPLOAD" | xargs gh release upload ${{ github.ref_name }} --clobber
+ echo "$FILES_TO_UPLOAD" | xargs gh release upload ${{ github.ref_name }} --clobber
\ No newline at end of file
diff --git a/Taskfile.yml b/Taskfile.yml
deleted file mode 100644
index f996f84..0000000
--- a/Taskfile.yml
+++ /dev/null
@@ -1,93 +0,0 @@
-# https://taskfile.dev
-version: "3"
-
-dotenv: ["public.env", ".env"]
-
-vars:
- # These are now the ONLY variables expected from the CI/CD workflow.
- APP_VERSION: "{{.APP_VERSION }}"
- GITHUB_REF_NAME: "{{.GITHUB_REF_NAME }}"
-
- MAIN_EXE: "{{if .PROJECT_TO_PUBLISH}}{{.APP_NAME}}.exe{{end}}"
-
-includes:
- dotnet: { taskfile: ./.build/dotnet/Taskfile.yml, internal: true }
- velopack:
- { taskfile: ./.build/velopack/Taskfile.yml, optional: true, internal: true }
-
-tasks:
- default:
- desc: "Runs the default build task"
- cmds: [{ task: build }]
- clean:
- desc: "Cleans the solution"
- cmds: [{ task: dotnet:clean }]
- build:
- desc: "Builds the solution in Release configuration"
- cmds: [{ task: dotnet:build }]
- test:
- desc: "Runs all tests in the solution"
- cmds: [{ task: dotnet:test }]
- lint:
- desc: "Runs the linter at the root of the project"
- cmds: [{ task: dotnet:lint }]
- format:
- desc: "Formats the codebase using CSharpier"
- cmds: [{ task: dotnet:format }]
- setup:
- silent: true
- desc: "Restores local .NET tools defined in the manifest"
- preconditions:
- - "[ -f .config/dotnet-tools.json ]"
- cmds:
- - cmd: dotnet tool restore > /dev/null
- get-version:
- desc: "Calculates the version from git history (for pre-release builds)"
- deps: ["setup"]
- cmds: ["dotnet minver --default-pre-release-identifiers preview.0 -v error"]
-
- # --- Simplified Release Task (For Local Testing Only) ---
- release:
- desc: "(LOCAL ONLY) Builds a single platform release for testing."
- vars:
- PRE_RELEASE_VERSION: { sh: task get-version }
- APP_VERSION: "{{.APP_VERSION | default .PRE_RELEASE_VERSION}}"
- cmds:
- - task: build-platform-release
- vars:
- OS: win
- ARCH: x64 # Hardcoded for simple local testing
- APP_VERSION: "{{.APP_VERSION}}"
-
- # --- NuGet Publishing Logic (Unchanged) ---
- pack-nuget:
- preconditions: ['{{ ne .PROJECT_TO_PACK "" }}']
- cmds: [{ task: dotnet:pack-nuget }]
- push-nuget:
- deps: ["pack-nuget"]
- preconditions:
- - '{{ ne .NUGET_API_KEY "" }}'
- - sh: git describe --tags --exact-match &>/dev/null
- msg: "Skipping NuGet push: This is a pre-release version (git height > 0)."
- cmds: [{ task: dotnet:push-nuget }]
-
- # --- CI Entrypoint for Building a Single Platform ---
- setup-velopack:
- desc: "Installs the Velopack CLI tool, if not already present"
- cmds: ["dotnet tool install --global vpk"]
- status: ["vpk --help"]
-
- build-platform-release:
- desc: "(CI USE) Builds and packs a release for a single target platform (RID)."
- deps: ["setup-velopack"]
- preconditions:
- - '{{ ne .PROJECT_TO_PUBLISH "" }}'
- - '{{ ne .OS "" }}'
- - '{{ ne .ARCH "" }}'
- cmds:
- - echo "Building platform release for {{.OS}}/{{.ARCH}} version {{.APP_VERSION}}"
- - task: dotnet:publish-app
- vars: { OS: "{{.OS}}", ARCH: "{{.ARCH}}" }
- - task: velopack:pack
- vars:
- { OS: "{{.OS}}", ARCH: "{{.ARCH}}", APP_VERSION: "{{.APP_VERSION}}" }
diff --git a/build.ps1 b/build.ps1
new file mode 100644
index 0000000..9445cf9
--- /dev/null
+++ b/build.ps1
@@ -0,0 +1 @@
+dotnet run .build/targets.cs -- $args
\ No newline at end of file
diff --git a/build.sh b/build.sh
new file mode 100644
index 0000000..8997513
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+# Exit immediately if a command exits with a non-zero status.
+set -e
+
+# This script is a simple wrapper around the C# build script.
+# It passes all command-line arguments directly to the script.
+# The '--' separates arguments for 'dotnet run' from arguments for the application.
+dotnet run .build/targets.cs -- "$@"
\ No newline at end of file
diff --git a/dotnet.config b/dotnet.config
new file mode 100644
index 0000000..da0410e
--- /dev/null
+++ b/dotnet.config
@@ -0,0 +1,2 @@
+[dotnet.test.runner]
+name = "Microsoft.Testing.Platform"
diff --git a/global.json b/global.json
index a24b744..fc142ff 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,7 @@
{
"sdk": {
- "version": "9.0.0",
- "rollForward": "latestFeature"
+ "version": "10.0.100-preview.7.25380.108",
+ "rollForward": "latestFeature",
+ "allowPrerelease": true
}
}
\ No newline at end of file
diff --git a/public.env b/public.env
deleted file mode 100644
index 6613377..0000000
--- a/public.env
+++ /dev/null
@@ -1,8 +0,0 @@
-SOLUTION_FILE=DotNetPathUtils.slnx
-OUTPUT_DIR=dist
-PROJECT_TO_PUBLISH=DotNetPathUtils.Console
-PROJECT_TO_PACK=DotNetPathUtils
-APP_NAME=sampleApp
-COMPANY_NAME='henry-js'
-ICON_PATH='.build/assets/icon.ico'
-NUGET_SOURCE_URL="https://api.nuget.org/v3/index.json"
diff --git a/src/DotNetPathUtils.Tests/DotNetPathUtils.Tests.csproj b/src/DotNetPathUtils.Tests/DotNetPathUtils.Tests.csproj
index e77b073..bd318b9 100644
--- a/src/DotNetPathUtils.Tests/DotNetPathUtils.Tests.csproj
+++ b/src/DotNetPathUtils.Tests/DotNetPathUtils.Tests.csproj
@@ -3,11 +3,12 @@
enable
enable
Exe
- net9.0
+ net10.0
+
-
+