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 + - +