From 5872f60390112580b608d685b26a4d34f4afa527 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Mon, 13 Apr 2026 15:08:28 -0700 Subject: [PATCH 1/4] Enhance GitHub workflows for code generation and auto-merge (#43) Improve security and prevent abuse in merge-bot workflows. Introduce a new workflow for running the codegen app and creating pull requests, along with a periodic workflow for weekly execution. Update documentation to include Git and commit rules for AI coding agents. Adjust date in the CodeGen class for accurate quote generation. --------- Signed-off-by: Pieter Viljoen --- .github/copilot-instructions.md | 9 +++ .github/workflows/merge-bot-pull-request.yml | 38 ++++++++++- .../run-codegen-app-pull-request-task.yml | 65 +++++++++++++++++++ .../run-codegen-pull-request-task.yml | 3 + .../run-periodic-codegen-app-pull-request.yml | 23 +++++++ .../run-periodic-codegen-pull-request.yml | 4 +- AGENTS.md | 9 +++ CodeGen/CodeGen.cs | 2 +- CodeGen/HttpClientFactory.cs | 17 +++-- README.md | 32 ++++++++- 10 files changed, 188 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/run-codegen-app-pull-request-task.yml create mode 100644 .github/workflows/run-periodic-codegen-app-pull-request.yml diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 8f4c0def..ce63fad2 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -309,6 +309,15 @@ The project includes comprehensive `.editorconfig` settings that enforce: **Always respect the .editorconfig settings** - these are verified by the build process. +## Git and Commit Rules + +**These rules are absolute — no exceptions:** + +- **Never make git commits.** All commits must be cryptographically signed (SSH/GPG). AI coding agents cannot produce signed commits. Stage changes with `git add` and leave `git commit` to the developer, who must run it in their own environment where signing keys are available. +- **Never force push.** Do not run `git push --force` or `git push --force-with-lease`. Force pushing rewrites shared branch history and is blocked by branch protection rules. +- **Never run destructive git commands** (`git reset --hard`, `git checkout .`, `git restore .`, `git clean -f`) without explicit developer instruction. +- **Staging is the limit.** Prepare changes and stage files; the developer handles all commits and pushes. + ## Workflow 1. **Before coding**: Run `dotnet tool restore` to ensure tools are installed diff --git a/.github/workflows/merge-bot-pull-request.yml b/.github/workflows/merge-bot-pull-request.yml index 39c1a658..fc836bc5 100644 --- a/.github/workflows/merge-bot-pull-request.yml +++ b/.github/workflows/merge-bot-pull-request.yml @@ -13,7 +13,10 @@ jobs: merge-dependabot: name: Merge dependabot pull request job runs-on: ubuntu-latest - if: github.actor == 'dependabot[bot]' && github.event.pull_request.head.repo.full_name == github.repository + # Must come from dependabot, and the PR must be from the same repository to prevent abuse + if: >- + github.actor == 'dependabot[bot]' && + github.event.pull_request.head.repo.full_name == github.repository permissions: contents: write pull-requests: write @@ -36,7 +39,38 @@ jobs: merge-codegen: name: Merge codegen pull request job runs-on: ubuntu-latest - if: github.event.pull_request.user.login == 'github-actions[bot]' && github.event.pull_request.head.ref == 'codegen' && github.event.pull_request.base.ref == 'main' && github.event.pull_request.head.repo.full_name == github.repository && ((github.event.action == 'reopened' && github.actor == 'ptr727') || (github.event.action != 'reopened' && github.actor == 'github-actions[bot]')) + # Must come from the codegen workflow, and the PR must be from the same repository to prevent abuse + if: >- + github.event.pull_request.user.login == 'github-actions[bot]' && + github.event.pull_request.head.ref == 'codegen' && + github.event.pull_request.base.ref == 'main' && + github.event.pull_request.head.repo.full_name == github.repository && + ( + (github.event.action == 'reopened' && github.actor == github.repository_owner) || + (github.event.action != 'reopened' && github.actor == 'github-actions[bot]') + ) + permissions: + contents: write + pull-requests: write + + steps: + + - name: Merge pull request step + run: gh pr merge --auto --squash "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GH_TOKEN: ${{secrets.GITHUB_TOKEN}} + + merge-codegen-app: + name: Merge codegen app pull request job + runs-on: ubuntu-latest + # Must come from the codegen app workflow, and the PR must be from the same repository to prevent abuse + if: >- + github.actor == 'ptr727-codegen[bot]' && + github.event.pull_request.user.login == 'ptr727-codegen[bot]' && + github.event.pull_request.head.ref == 'codegen' && + github.event.pull_request.base.ref == 'main' && + github.event.pull_request.head.repo.full_name == github.repository permissions: contents: write pull-requests: write diff --git a/.github/workflows/run-codegen-app-pull-request-task.yml b/.github/workflows/run-codegen-app-pull-request-task.yml new file mode 100644 index 00000000..060d8a0f --- /dev/null +++ b/.github/workflows/run-codegen-app-pull-request-task.yml @@ -0,0 +1,65 @@ +name: Run codegen app and pull request task + +on: + workflow_call: + secrets: + # GitHub App credentials to generate an installation token + CODEGEN_APP_ID: + required: true + CODEGEN_APP_PRIVATE_KEY: + required: true + +jobs: + + codegen: + name: Run codegen app and pull request job + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + + steps: + + - name: Generate GitHub App token step + uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ secrets.CODEGEN_APP_ID }} + private-key: ${{ secrets.CODEGEN_APP_PRIVATE_KEY }} + + - name: Setup .NET SDK step + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 10.x + + - name: Checkout code step + uses: actions/checkout@v6 + with: + ref: main + token: ${{ steps.app-token.outputs.token }} + + - name: Run codegen step + run: | + dotnet run --project ./CodeGen/CodeGen.csproj -- \ + --codepath ./CodeGen + + - name: Format code step + run: | + dotnet tool restore + dotnet husky install + dotnet csharpier format --log-level=debug . + git status + + - name: Create pull request step + uses: peter-evans/create-pull-request@v8 + id: cpr + with: + # Use app token: triggers pull_request workflow events directly, creates verified commits as the app + token: ${{ steps.app-token.outputs.token }} + base: main + branch: codegen + title: 'Update codegen files' + body: 'This PR updates the codegen files.' + commit-message: 'Update codegen files' + delete-branch: true + sign-commits: true diff --git a/.github/workflows/run-codegen-pull-request-task.yml b/.github/workflows/run-codegen-pull-request-task.yml index fdcd8dfc..730d274b 100644 --- a/.github/workflows/run-codegen-pull-request-task.yml +++ b/.github/workflows/run-codegen-pull-request-task.yml @@ -3,6 +3,7 @@ name: Run codegen and pull request task on: workflow_call: secrets: + # Use PAT to trigger workflows WORKFLOW_PAT: required: true @@ -43,6 +44,7 @@ jobs: uses: peter-evans/create-pull-request@v8 id: cpr with: + # Use GITHUB_TOKEN to sign the commit, but will not trigger workflows token: ${{ secrets.GITHUB_TOKEN }} base: main branch: codegen @@ -59,4 +61,5 @@ jobs: gh pr close "$PR" gh pr reopen "$PR" env: + # Use PAT to trigger workflows GH_TOKEN: ${{ secrets.WORKFLOW_PAT }} diff --git a/.github/workflows/run-periodic-codegen-app-pull-request.yml b/.github/workflows/run-periodic-codegen-app-pull-request.yml new file mode 100644 index 00000000..fcd687f1 --- /dev/null +++ b/.github/workflows/run-periodic-codegen-app-pull-request.yml @@ -0,0 +1,23 @@ +name: Run weekly CodeGen App and Pull Request action + +on: + workflow_dispatch: + schedule: + # Run weekly on Thursdays at 02:00 UTC (PAT workflow runs on Mondays) + - cron: '0 2 * * THU' + +concurrency: + group: codegen-${{ github.ref }} + cancel-in-progress: true + +jobs: + + run-codegen-app: + name: Run codegen app and pull request job + uses: ./.github/workflows/run-codegen-app-pull-request-task.yml + secrets: + CODEGEN_APP_ID: ${{ secrets.CODEGEN_APP_ID }} + CODEGEN_APP_PRIVATE_KEY: ${{ secrets.CODEGEN_APP_PRIVATE_KEY }} + permissions: + contents: write + pull-requests: write diff --git a/.github/workflows/run-periodic-codegen-pull-request.yml b/.github/workflows/run-periodic-codegen-pull-request.yml index cb16fe8c..701a7294 100644 --- a/.github/workflows/run-periodic-codegen-pull-request.yml +++ b/.github/workflows/run-periodic-codegen-pull-request.yml @@ -3,11 +3,11 @@ name: Run weekly CodeGen and Pull Request action on: workflow_dispatch: schedule: - # Run weekly on Mondays at 02:00 UTC + # Run weekly on Mondays at 02:00 UTC (PAT workflow; app workflow runs on Thursdays) - cron: '0 2 * * MON' concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: codegen-${{ github.ref }} cancel-in-progress: true jobs: diff --git a/AGENTS.md b/AGENTS.md index 7b31ff94..e5283834 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,6 +4,15 @@ For comprehensive coding standards and detailed conventions, refer to [`.github/copilot-instructions.md`](./.github/copilot-instructions.md) and [`CODESTYLE.md`](./CODESTYLE.md). +## Git and Commit Rules + +**These rules are absolute — no exceptions:** + +- **Never make git commits.** AI coding agents cannot produce cryptographically signed commits. All commits must be signed (SSH/GPG) and must be made by the developer. Stage changes with `git add` and leave the commit to the developer. +- **Never force push.** Do not run `git push --force` or `git push --force-with-lease` under any circumstances. Force pushing rewrites shared history and can cause data loss. +- **Never run destructive git commands** (`git reset --hard`, `git checkout .`, `git restore .`, `git clean -f`) without explicit developer instruction. +- **Staging is the limit.** Prepare and stage file changes; the developer runs `git commit` in their own environment where signing keys are available. + ## Key Requirements for All Projects Derived from This Template ### Build & Quality Standards diff --git a/CodeGen/CodeGen.cs b/CodeGen/CodeGen.cs index 7c6f39df..a283a0e3 100644 --- a/CodeGen/CodeGen.cs +++ b/CodeGen/CodeGen.cs @@ -7,7 +7,7 @@ internal static class CodeGen internal static void Quote() { - const string dateTime = "2026-03-30T03:00:04.9630307Z"; + const string dateTime = "2026-01-30T22:28:14.6290903Z"; Console.WriteLine($"{dateTime} : {QuoteOfTheDay}"); Log.Logger.Information("Quote of the Day: {DateTime} : {Quote}", dateTime, QuoteOfTheDay); } diff --git a/CodeGen/HttpClientFactory.cs b/CodeGen/HttpClientFactory.cs index 121098f8..c3b37f64 100644 --- a/CodeGen/HttpClientFactory.cs +++ b/CodeGen/HttpClientFactory.cs @@ -27,14 +27,10 @@ internal static class HttpClientFactory private static readonly TimeSpan s_httpClientTimeout = TimeSpan.FromSeconds(120); private static readonly Lazy s_httpClient = new(CreateHttpClient); - private static readonly Lazy s_resilienceHandler = new( - CreateResilienceHandler - ); + // Returns the shared singleton HttpClient; all callers share the connection pool and circuit breaker state. internal static HttpClient GetHttpClient() => s_httpClient.Value; - private static ResilienceHandler GetResilienceHandler() => s_resilienceHandler.Value; - private static ResilienceHandler CreateResilienceHandler() => new( new ResiliencePipelineBuilder() @@ -106,9 +102,16 @@ outcome.Exception is not null ? outcome.Exception is not (OperationCanceledException or BrokenCircuitException) : outcome.Result is not null && (int)outcome.Result.StatusCode is 408 or 429 or >= 500; - private static HttpClient CreateHttpClient() + // Creates a new HttpClient instance; each caller gets an independent resilience handler + // and circuit breaker state. Callers should store and reuse the returned instance. + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Reliability", + "CA2000:Dispose objects before losing scope", + Justification = "HttpClient takes ownership of the handler and disposes it when the client is disposed." + )] + internal static HttpClient CreateHttpClient() { - HttpClient httpClient = new(GetResilienceHandler()) { Timeout = s_httpClientTimeout }; + HttpClient httpClient = new(CreateResilienceHandler()) { Timeout = s_httpClientTimeout }; httpClient.DefaultRequestHeaders.UserAgent.Add( new ProductInfoHeaderValue(AssemblyInfo.AppName, AssemblyInfo.ReleaseVersion) ); diff --git a/README.md b/README.md index da04ae17..bda991e2 100644 --- a/README.md +++ b/README.md @@ -388,16 +388,44 @@ Licensed under the [MIT License][license-link]\ - Pull requests: Read & write — to close and reopen the PR, triggering `pull_request` workflow events under the PAT owner's identity - Workflows: Read & write — required for the PAT to trigger `pull_request` events in other workflows - Metadata: Read-only (auto-required) - - The codegen workflow uses `GITHUB_TOKEN` to create a signed commit and open the PR as `github-actions[bot]`. It then uses `WORKFLOW_PAT` to close and reopen the PR so the `pull_request` event fires under the PAT owner's identity (`ptr727`), which triggers the auto-merge workflow. PRs created or updated by `GITHUB_TOKEN` alone do not trigger other workflows, hence the close/reopen step. + - The codegen workflow uses `GITHUB_TOKEN` to create a signed commit and open the PR as `github-actions[bot]`. It then uses `WORKFLOW_PAT` to close and reopen the PR so the `pull_request` event fires under the repository owner's identity (`github.repository_owner`), which triggers the auto-merge workflow. PRs created or updated by `GITHUB_TOKEN` alone do not trigger other workflows, hence the close/reopen step. - The auto-merge condition in `merge-bot-pull-request.yml` requires all of the following to be true: - `github.event.pull_request.user.login == 'github-actions[bot]'` — the PR was created by the Actions bot (via `GITHUB_TOKEN`) - `github.event.pull_request.head.ref == 'codegen'` — the source branch is `codegen` - `github.event.pull_request.base.ref == 'main'` — the PR targets `main` - `github.event.pull_request.head.repo.full_name == github.repository` — the PR is from the same repository (not a fork) - - For `reopened` events: `github.actor == 'ptr727'` — the reopen was triggered by the PAT owner + - For `reopened` events: `github.actor == github.repository_owner` — the reopen was triggered by the repository owner account - For all other events: `github.actor == 'github-actions[bot]'` — triggered by normal workflow activity - Save the PAT as `WORKFLOW_PAT` in: - GitHub project security Settings / Secrets / Actions. +- Create a [GitHub App](https://github.com/settings/apps) as an alternative to the PAT workflow. + - App name: `ptr727-codegen` + - The app bot user will be `ptr727-codegen[bot]`. + - Permissions required: + - Repository permissions: + - Contents: Read & write — to push commits to the `codegen` branch + - Pull requests: Read & write — to open and update pull requests + - Metadata: Read-only (auto-required) + - Install the app on the repository. + - Note the App ID from the app's settings page. + - Generate a private key (downloads a `.pem` file). + - Save the App ID as `CODEGEN_APP_ID` and the private key contents as `CODEGEN_APP_PRIVATE_KEY` in: + - GitHub project security Settings / Secrets / Actions. + - Unlike the PAT workflow, the GitHub App token triggers `pull_request` workflow events directly when opening a PR. No close/reopen step is required. + - The auto-merge condition in `merge-bot-pull-request.yml` for the app workflow requires all of the following to be true: + - `github.actor == 'ptr727-codegen[bot]'` — the event was triggered by the app + - `github.event.pull_request.user.login == 'ptr727-codegen[bot]'` — the PR was created by the app + - `github.event.pull_request.head.ref == 'codegen'` — the source branch is `codegen` + - `github.event.pull_request.base.ref == 'main'` — the PR targets `main` + - `github.event.pull_request.head.repo.full_name == github.repository` — the PR is from the same repository (not a fork) + +**Codegen workflow schedule**: + +- The PAT-based codegen workflow (`run-periodic-codegen-pull-request.yml`) runs every **Monday** at 02:00 UTC. + - Uses `WORKFLOW_PAT` to close and reopen the PR after creation so that the `reopened` `pull_request` event is triggered by the repository owner account, which is required by the auto-merge condition (`github.actor == github.repository_owner`). Therefore, `WORKFLOW_PAT` must belong to the repository owner account. +- The App-based codegen workflow (`run-periodic-codegen-app-pull-request.yml`) runs every **Thursday** at 02:00 UTC. + - Uses `CODEGEN_APP_ID` and `CODEGEN_APP_PRIVATE_KEY` to generate a GitHub App installation token. The app token triggers `pull_request` events directly when the PR is opened — no close/reopen step needed. +- The two workflows alternate through the week as independent verification that both authentication paths continue to work. **GitHub project settings**: From 3a5faeb87de9d5e70ca863b1ba88a27e5f23c4b4 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Mon, 13 Apr 2026 16:10:15 -0700 Subject: [PATCH 2/4] Add central project configuration and update package references (#45) - Introduced Directory.Build.props and Directory.Packages.props for shared MSBuild properties and centralized NuGet package versions. - Updated various project files to remove specific properties and use centralized versions. - Enhanced GitHub workflows for code generation and pull requests. - Refactored launch configurations and tasks for better organization. Signed-off-by: Pieter Viljoen Signed-off-by: Pieter Viljoen --- .github/copilot-instructions.md | 12 +++ .../run-periodic-codegen-app-pull-request.yml | 47 ++++---- .../run-periodic-codegen-pull-request.yml | 3 +- .vscode/launch.json | 102 +++++++++--------- .vscode/tasks.json | 22 ++++ AGENTS.md | 9 ++ Benchmarks/Benchmarks.csproj | 7 +- Benchmarks/GlobalUsings.cs | 1 - CodeGen/CodeGen.cs | 2 +- CodeGen/CodeGen.csproj | 17 ++- CodeGen/GlobalUsings.cs | 6 -- Console/Console.csproj | 19 ++-- Console/GlobalUsings.cs | 5 - Directory.Build.props | 14 +++ Directory.Packages.props | 19 ++++ Library/GlobalUsings.cs | 1 - Library/Library.csproj | 12 +-- Library/LogOptions.cs | 2 - Tests/GlobalUsings.cs | 2 - Tests/Tests.csproj | 15 +-- 20 files changed, 175 insertions(+), 142 deletions(-) create mode 100644 Directory.Build.props create mode 100644 Directory.Packages.props diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index ce63fad2..f61f6468 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -228,6 +228,18 @@ Available VS Code tasks (use via `run_task` tool): ``` +5. **Directory.Build.props**: Common MSBuild properties shared across all projects + (`TargetFramework`, `Nullable`, `ImplicitUsings`, `AnalysisLevel`, `AnalysisMode`, + `EnableNETAnalyzers`, `ArtifactsPath`, `IsPackable`, `ManagePackageVersionsCentrally`) + live here at the solution root. Only add a property to a `.csproj` when it is + specific to that project or requires an explicit override of the shared default. + +6. **Directory.Packages.props**: All NuGet package versions are centralised here via + `PackageVersion` items. Individual `.csproj` files use `PackageReference Include="..."` + with no `Version` attribute. Asset metadata (`PrivateAssets`, `IncludeAssets`) stays + in the `.csproj` `PackageReference` element. Use `VersionOverride` only when a project + genuinely requires a different version from the central default. + ### Code Formatting Tools 1. **CSharpier**: Primary code formatter diff --git a/.github/workflows/run-periodic-codegen-app-pull-request.yml b/.github/workflows/run-periodic-codegen-app-pull-request.yml index fcd687f1..a08284f6 100644 --- a/.github/workflows/run-periodic-codegen-app-pull-request.yml +++ b/.github/workflows/run-periodic-codegen-app-pull-request.yml @@ -1,23 +1,24 @@ -name: Run weekly CodeGen App and Pull Request action - -on: - workflow_dispatch: - schedule: - # Run weekly on Thursdays at 02:00 UTC (PAT workflow runs on Mondays) - - cron: '0 2 * * THU' - -concurrency: - group: codegen-${{ github.ref }} - cancel-in-progress: true - -jobs: - - run-codegen-app: - name: Run codegen app and pull request job - uses: ./.github/workflows/run-codegen-app-pull-request-task.yml - secrets: - CODEGEN_APP_ID: ${{ secrets.CODEGEN_APP_ID }} - CODEGEN_APP_PRIVATE_KEY: ${{ secrets.CODEGEN_APP_PRIVATE_KEY }} - permissions: - contents: write - pull-requests: write +name: Run weekly CodeGen App and Pull Request action + +on: + workflow_dispatch: + schedule: + # Run weekly on Thursdays at 02:00 UTC (PAT workflow runs on Mondays) + - cron: '0 2 * * THU' + +concurrency: + # Workflow always checks out and targets main/codegen + group: codegen-main + cancel-in-progress: true + +jobs: + + run-codegen-app: + name: Run codegen app and pull request job + uses: ./.github/workflows/run-codegen-app-pull-request-task.yml + secrets: + CODEGEN_APP_ID: ${{ secrets.CODEGEN_APP_ID }} + CODEGEN_APP_PRIVATE_KEY: ${{ secrets.CODEGEN_APP_PRIVATE_KEY }} + permissions: + contents: write + pull-requests: write diff --git a/.github/workflows/run-periodic-codegen-pull-request.yml b/.github/workflows/run-periodic-codegen-pull-request.yml index 701a7294..2c01b9c3 100644 --- a/.github/workflows/run-periodic-codegen-pull-request.yml +++ b/.github/workflows/run-periodic-codegen-pull-request.yml @@ -7,7 +7,8 @@ on: - cron: '0 2 * * MON' concurrency: - group: codegen-${{ github.ref }} + # Workflow always checks out and targets main/codegen + group: codegen-main cancel-in-progress: true jobs: diff --git a/.vscode/launch.json b/.vscode/launch.json index b4087235..1aab653f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,51 +1,51 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "name": "Console Root", - "type": "coreclr", - "request": "launch", - "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/Console/bin/Debug/net10.0/Console.dll", - "args": [ - "--loglevel=Debug", - "--logfile=console.log", - "--logfile-clear=true" - ], - "cwd": "${workspaceFolder}/Console/bin/Debug/net10.0", - "console": "internalConsole", - "stopAtEntry": false - }, - { - "name": "Console Test", - "type": "coreclr", - "request": "launch", - "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/Console/bin/Debug/net10.0/Console.dll", - "args": [ - "--loglevel=Debug", - "--logfile=console.log", - "--logfile-clear=true", - "test", - "--test=test" - ], - "cwd": "${workspaceFolder}/Console/bin/Debug/net10.0", - "console": "internalConsole", - "stopAtEntry": false - }, - { - "name": "CodeGen", - "type": "coreclr", - "request": "launch", - "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/CodeGen/bin/Debug/net10.0/CodeGen.dll", - "args": [ - "--codepath", - "${workspaceFolder}/CodeGen" - ], - "cwd": "${workspaceFolder}/CodeGen/bin/Debug/net10.0", - "console": "internalConsole", - "stopAtEntry": false - } - ] -} +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Console Root", + "type": "coreclr", + "request": "launch", + "preLaunchTask": ".Net Build", + "program": "${workspaceFolder}/.artifacts/bin/Console/Debug/Console.dll", + "args": [ + "--loglevel=Debug", + "--logfile=console.log", + "--logfile-clear=true" + ], + "cwd": "${workspaceFolder}/.artifacts/bin/Console/Debug", + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": "Console Test", + "type": "coreclr", + "request": "launch", + "preLaunchTask": ".Net Build", + "program": "${workspaceFolder}/.artifacts/bin/Console/Debug/Console.dll", + "args": [ + "--loglevel=Debug", + "--logfile=console.log", + "--logfile-clear=true", + "test", + "--test=test" + ], + "cwd": "${workspaceFolder}/.artifacts/bin/Console/Debug", + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": "CodeGen", + "type": "coreclr", + "request": "launch", + "preLaunchTask": ".Net Build", + "program": "${workspaceFolder}/.artifacts/bin/CodeGen/Debug/CodeGen.dll", + "args": [ + "--codepath", + "${workspaceFolder}/CodeGen" + ], + "cwd": "${workspaceFolder}/.artifacts/bin/CodeGen/Debug", + "console": "internalConsole", + "stopAtEntry": false + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index f8ae84ae..15724458 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -93,6 +93,28 @@ "clear": false } }, + { + "label": ".Net Benchmark", + "type": "process", + "command": "dotnet", + "args": [ + "run", + "--project", + "${workspaceFolder}/Benchmarks/Benchmarks.csproj", + "-c", + "Release", + "--", + "--artifacts", + "${workspaceFolder}/.artifacts/benchmarks" + ], + "problemMatcher": [ + "$msCompile" + ], + "presentation": { + "showReuseMessage": false, + "clear": false + } + }, { "label": ".Net Outdated Upgrade", "type": "process", diff --git a/AGENTS.md b/AGENTS.md index e5283834..ac3a0281 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,6 +24,15 @@ For comprehensive coding standards and detailed conventions, refer to [`.github/ - `true` - `latest-all` +### Project Configuration + +- Common MSBuild properties (`TargetFramework`, `Nullable`, `ImplicitUsings`, `AnalysisLevel`, etc.) + live in `Directory.Build.props` at the solution root. Do not duplicate these in individual `.csproj` + files — only add a property to a `.csproj` when it is project-specific or overrides the shared default. +- All NuGet package versions are centralised in `Directory.Packages.props`. `PackageReference` elements + in `.csproj` files must not include a `Version` attribute. Asset metadata (`PrivateAssets`, + `IncludeAssets`) stays in the `.csproj` `PackageReference` element. + ### Development Environment - Target latest .NET SDK (currently .NET 10 with C# 14) diff --git a/Benchmarks/Benchmarks.csproj b/Benchmarks/Benchmarks.csproj index 84b9a369..ab6aa801 100644 --- a/Benchmarks/Benchmarks.csproj +++ b/Benchmarks/Benchmarks.csproj @@ -1,15 +1,10 @@ - latest-all - true - false - enable Exe ptr727.ProjectTemplate.Benchmarks - net10.0 - + diff --git a/Benchmarks/GlobalUsings.cs b/Benchmarks/GlobalUsings.cs index 4603ccb2..15ff6fb5 100644 --- a/Benchmarks/GlobalUsings.cs +++ b/Benchmarks/GlobalUsings.cs @@ -1,3 +1,2 @@ -global using System; global using BenchmarkDotNet.Attributes; global using BenchmarkDotNet.Running; diff --git a/CodeGen/CodeGen.cs b/CodeGen/CodeGen.cs index a283a0e3..f1f6f050 100644 --- a/CodeGen/CodeGen.cs +++ b/CodeGen/CodeGen.cs @@ -7,7 +7,7 @@ internal static class CodeGen internal static void Quote() { - const string dateTime = "2026-01-30T22:28:14.6290903Z"; + const string dateTime = "2026-04-13T22:37:43.5686751Z"; Console.WriteLine($"{dateTime} : {QuoteOfTheDay}"); Log.Logger.Information("Quote of the Day: {DateTime} : {Quote}", dateTime, QuoteOfTheDay); } diff --git a/CodeGen/CodeGen.csproj b/CodeGen/CodeGen.csproj index 8ff838f4..dc3bf7db 100644 --- a/CodeGen/CodeGen.csproj +++ b/CodeGen/CodeGen.csproj @@ -1,14 +1,9 @@ - + - latest-all - true 1.0.0-pre - false - enable Exe false ptr727.ProjectTemplate.CodeGen - net10.0 1.0.0 @@ -17,10 +12,10 @@ true - - - - - + + + + + diff --git a/CodeGen/GlobalUsings.cs b/CodeGen/GlobalUsings.cs index 88aa9dac..85c42612 100644 --- a/CodeGen/GlobalUsings.cs +++ b/CodeGen/GlobalUsings.cs @@ -1,9 +1,3 @@ -global using System; global using System.Collections.Frozen; global using System.Globalization; -global using System.IO; -global using System.Linq; -global using System.Net.Http; -global using System.Threading; -global using System.Threading.Tasks; global using Serilog; diff --git a/Console/Console.csproj b/Console/Console.csproj index 1e00bf4e..bc4ea6ae 100644 --- a/Console/Console.csproj +++ b/Console/Console.csproj @@ -1,14 +1,9 @@ - latest-all - true 1.0.0-pre - false - enable Exe false ptr727.ProjectTemplate.Console - net10.0 1.0.0 @@ -17,13 +12,13 @@ true - - - - - - - + + + + + + + diff --git a/Console/GlobalUsings.cs b/Console/GlobalUsings.cs index 89b47da4..17a0a025 100644 --- a/Console/GlobalUsings.cs +++ b/Console/GlobalUsings.cs @@ -1,10 +1,5 @@ -global using System; global using System.Collections.Frozen; global using System.Globalization; -global using System.IO; -global using System.Linq; -global using System.Threading; -global using System.Threading.Tasks; global using Microsoft.Extensions.Logging; global using Serilog; global using Serilog.Events; diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 00000000..da26fe38 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,14 @@ + + + net10.0 + enable + enable + latest-all + All + true + true + $(MSBuildThisFileDirectory).artifacts + false + true + + diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 00000000..2f77ae1b --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Library/GlobalUsings.cs b/Library/GlobalUsings.cs index 8aba491d..f52bcbcf 100644 --- a/Library/GlobalUsings.cs +++ b/Library/GlobalUsings.cs @@ -1,3 +1,2 @@ -global using System; global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.Logging.Abstractions; diff --git a/Library/Library.csproj b/Library/Library.csproj index 5623d4bf..6736346e 100644 --- a/Library/Library.csproj +++ b/Library/Library.csproj @@ -1,13 +1,11 @@ - latest-all 1.0.0.0 Pieter Viljoen Pieter Viljoen Pieter Viljoen C# .NET template project true - true 1.0.0.0 true true @@ -15,7 +13,6 @@ 1.0.0-pre true true - enable ptr727.ProjectTemplate.Library MIT https://github.com/ptr727/ProjectTemplate @@ -26,19 +23,14 @@ https://github.com/ptr727/ProjectTemplate ptr727.ProjectTemplate.Library snupkg - net10.0 1.0.0.0 true - - + + diff --git a/Library/LogOptions.cs b/Library/LogOptions.cs index 1de42803..9c63601c 100644 --- a/Library/LogOptions.cs +++ b/Library/LogOptions.cs @@ -1,5 +1,3 @@ -using System.Threading; - namespace ptr727.ProjectTemplate.Library; /// diff --git a/Tests/GlobalUsings.cs b/Tests/GlobalUsings.cs index 43d718ba..d85913ed 100644 --- a/Tests/GlobalUsings.cs +++ b/Tests/GlobalUsings.cs @@ -1,4 +1,2 @@ -global using System; -global using System.Collections.Generic; global using AwesomeAssertions; global using Xunit; diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 36ada6b2..8c53010d 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -1,22 +1,17 @@ - latest-all - true - false true - enable ptr727.ProjectTemplate.Tests - net10.0 - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + From 8d9e87ce02a1984fa51ca869ffb31ca742c96d6e Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Mon, 13 Apr 2026 16:51:54 -0700 Subject: [PATCH 3/4] Update build tasks to use property syntax for output paths (#47) Signed-off-by: Pieter Viljoen --------- Signed-off-by: Pieter Viljoen --- .github/workflows/build-executable-task.yml | 2 +- .github/workflows/build-library-task.yml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-executable-task.yml b/.github/workflows/build-executable-task.yml index 4b7a900b..9ea6dfd3 100644 --- a/.github/workflows/build-executable-task.yml +++ b/.github/workflows/build-executable-task.yml @@ -36,7 +36,7 @@ jobs: run: | dotnet publish ./Console/Console.csproj \ --runtime ${{ matrix.runtime }} \ - --output ${{ runner.temp }}/publish/${{ matrix.runtime }} \ + -property:PublishDir=${{ runner.temp }}/publish/${{ matrix.runtime }}/ \ --configuration ${{ github.ref_name == 'main' && 'Release' || 'Debug' }} \ -property:PublishAot=false \ -property:Version=${{ needs.get-version.outputs.AssemblyVersion }} \ diff --git a/.github/workflows/build-library-task.yml b/.github/workflows/build-library-task.yml index 53d4c28a..6bd1279b 100644 --- a/.github/workflows/build-library-task.yml +++ b/.github/workflows/build-library-task.yml @@ -40,7 +40,8 @@ jobs: - name: Build library project step run: | dotnet build ./Library/Library.csproj \ - --output ${{ runner.temp }}/publish \ + -property:OutputPath=${{ runner.temp }}/publish/ \ + -property:PackageOutputPath=${{ runner.temp }}/publish/ \ --configuration ${{ github.ref_name == 'main' && 'Release' || 'Debug' }} \ -property:Version=${{ needs.get-version.outputs.AssemblyVersion }} \ -property:FileVersion=${{ needs.get-version.outputs.AssemblyFileVersion }} \ From 65bb121a443336a8c93ffc8f3f1b1166a26d7523 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Wed, 15 Apr 2026 11:38:58 -0700 Subject: [PATCH 4/4] Refactor comments in merge bot workflow for clarity and consistency (#48) Signed-off-by: Pieter Viljoen --------- Signed-off-by: Pieter Viljoen Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/merge-bot-pull-request.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/merge-bot-pull-request.yml b/.github/workflows/merge-bot-pull-request.yml index fc836bc5..4e27cfd6 100644 --- a/.github/workflows/merge-bot-pull-request.yml +++ b/.github/workflows/merge-bot-pull-request.yml @@ -13,7 +13,7 @@ jobs: merge-dependabot: name: Merge dependabot pull request job runs-on: ubuntu-latest - # Must come from dependabot, and the PR must be from the same repository to prevent abuse + # To prevent abuse, the PR must come from Dependabot and the PR must originate from this repository. if: >- github.actor == 'dependabot[bot]' && github.event.pull_request.head.repo.full_name == github.repository @@ -29,8 +29,12 @@ jobs: with: github-token: "${{ secrets.GITHUB_TOKEN }}" + # Merge any non-NuGet update, e.g. GitHub Actions often updates v1 to v2. + # Merge NuGet only for non-major updates, e.g. major updates may build but break functionality. - name: Merge pull request step - if: steps.metadata.outputs.update-type != 'version-update:semver-major' + if: >- + (steps.metadata.outputs.package-ecosystem != 'nuget') || + (steps.metadata.outputs.update-type != 'version-update:semver-major') run: gh pr merge --auto --squash "$PR_URL" env: PR_URL: ${{github.event.pull_request.html_url}} @@ -39,7 +43,7 @@ jobs: merge-codegen: name: Merge codegen pull request job runs-on: ubuntu-latest - # Must come from the codegen workflow, and the PR must be from the same repository to prevent abuse + # To prevent abuse, the PR must come from the codegen workflow, and the PR must originate from this repository. if: >- github.event.pull_request.user.login == 'github-actions[bot]' && github.event.pull_request.head.ref == 'codegen' && @@ -64,7 +68,7 @@ jobs: merge-codegen-app: name: Merge codegen app pull request job runs-on: ubuntu-latest - # Must come from the codegen app workflow, and the PR must be from the same repository to prevent abuse + # To prevent abuse, the PR must come from the codegen app workflow, and the PR must originate from this repository. if: >- github.actor == 'ptr727-codegen[bot]' && github.event.pull_request.user.login == 'ptr727-codegen[bot]' &&