ptr727.ProjectTemplate.Library 1.0.90

dotnet add package ptr727.ProjectTemplate.Library --version 1.0.90
                    
NuGet\Install-Package ptr727.ProjectTemplate.Library -Version 1.0.90
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ptr727.ProjectTemplate.Library" Version="1.0.90" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ptr727.ProjectTemplate.Library" Version="1.0.90" />
                    
Directory.Packages.props
<PackageReference Include="ptr727.ProjectTemplate.Library" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ptr727.ProjectTemplate.Library --version 1.0.90
                    
#r "nuget: ptr727.ProjectTemplate.Library, 1.0.90"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ptr727.ProjectTemplate.Library@1.0.90
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ptr727.ProjectTemplate.Library&version=1.0.90
                    
Install as a Cake Addin
#tool nuget:?package=ptr727.ProjectTemplate.Library&version=1.0.90
                    
Install as a Cake Tool

ProjectTemplate

C# .NET project template.

Build and Distribution

  • Source Code: GitHub - Source code, issues, discussions, and CI/CD pipelines.
  • Versioned Releases: GitHub Releases - Version tagged source code and build artifacts.
  • Docker Images: Docker Hub - Container images with all tools pre-installed.
  • NuGet Packages: NuGet Packages - .NET libraries published to NuGet.org.
  • PyPI Packages: PyPI Packages - Python library published to PyPI.org.

Build Status

Release Status
Docker Status
Last Commit
Last Build

Releases

GitHub Release
GitHub Pre-Release
Docker Latest
Docker Develop
NuGet Release
PyPI Release

Release Notes

Version: 1.0:

Summary:

  • Something.
  • And something else.

⚠️ Breaking Changes:

  • Something.
  • And something else.

See Release History for complete release notes and older versions.

Getting Started

Get started with ProjectTemplate in three easy steps:

⚠️ Important: Some important warning.

ℹ️ Note: Some interesting note.

  1. Install ProjectTemplate:

    • Do something.
  2. Configure ProjectTemplate:

    • Then something else.
  3. Run ProjectTemplate:

    Console --loglevel=Debug
    

See Installation for detailed setup instructions.

Table of Contents

Use Cases

ℹ️ TL;DR: This widget is special because it does something special.

  • It does something special.
  • And it does something else special.

Installation

Choose an installation method based on your platform and requirements:

  • Method 1 (Recommended): Easiest and most up-to-date option.
    • ✅ Some good reason.
    • ⚠️ Some not so good reason.
    • ❌ Strong reason to avoid.
    • Best for: Linux, NAS devices, servers, cross-platform deployments.
  • Method 2: Custom configuration options.
    • ✅ Some good reason.
    • ⚠️ Some not so good reason.
    • ❌ Strong reason to avoid.
    • Best for: Specialized devices.

Configuration

⚠️ Important: The spinner setting must be configured before first use.

Required configuration:

  • Set foo to something.
  • Set bar to something else.

Optional configuration:

  • Set advanced to special.
  • Some other custom option.

Usage

Console [global options] <command> [command options]

Command Quick Reference

Command Description Notes
default Default action when no command is specified First time setup
test Do something else useful Some note
--help Show help output Use <command> --help for command specific help
--version Show version output

Use the --help option to get a list of all commands and global options.
To get help for a specific command run Console <command> --help.

Global Options

Global options apply to all commands:

Option Description Default
--logfile Debug log file Optional
--loglevel Debug log level Default is Information
--logfile-clear Clear log file at startup Default is false

General help:

>.\Console\bin\Debug\net10.0\Console --help
Description:
  C# .NET console project

Usage:
  Console [command] [options]

Options:
  -l, --loglevel <Debug|Error|Fatal|Information|Verbose|Warning>  Set the log level (default: Information). [default: Information]
  -f, --logfile <logfile>                                         Write logs to the specified file (optional).
  -c, --logfile-clear                                             Clear the log file before writing (default: false).
  -?, -h, --help                                                  Show help and usage information
  --version                                                       Show version information

Commands:
  test  Test command

Test Command

Test command options:

Option Description Default
--test Test options Optional

Test command help:

>.\Console\bin\Debug\net10.0\Console test --help
Description:
  Test command

Usage:
  Console test [options]

Options:
  -t, --test <test>                                               Test command option (optional).
  -?, -h, --help                                                  Show help and usage information
  -l, --loglevel <Debug|Error|Fatal|Information|Verbose|Warning>  Set the log level (default: Information). [default: Information]
  -f, --logfile <logfile>                                         Write logs to the specified file (optional).
  -c, --logfile-clear                                             Clear the log file before writing (default: false).

Questions or Issues

For General Questions:

For Bug Reports:

  • Ask in the Discussions forum if you are not sure if it is a bug.
  • Check the existing Issues tracker for known problems.
  • If the issue is unique and a bug, file it in Issues, and include all pertinent steps to reproduce the issue.

Development Environment Setup

The recommended setup is one of the per-language Dev Containers under .devcontainer/:

  • .devcontainer/dotnet/ - .NET 10 SDK + GitHub CLI. Pair with DotNet.code-workspace.
  • .devcontainer/python/ - Python 3.14 + uv + GitHub CLI. Pair with Python.code-workspace.

Each container bind-mounts your SSH public key, allowed-signers file, and gh config from the host so commits sign correctly. gh is pre-authenticated when the host token is file-backed; macOS Keychain and Linux libsecret-backed tokens require an in-container gh auth login - see the credential-store nuance section.

Windows note: Python work is intentionally not supported on the Windows host. The Python extension caches the Linux-layout PyPiLibrary/.venv/bin/python against a venv whose actual Windows path is PyPiLibrary\.venv\Scripts\python.exe, breaking Ruff. Use the python devcontainer.

Recommended (devcontainer):

  1. Complete host setup once per machine (git identity, SSH key, allowed_signers, gh auth login, SSH commit signing).
  2. Clone the repo, open the matching workspace (DotNet.code-workspace or Python.code-workspace) in VS Code with the Dev Containers extension, and run Reopen in Container - pick the language flavor.
  3. The postCreateCommand runs dotnet tool restore (.NET container) or installs uv and runs uv sync (Python container). No git hooks are installed by default - see "Optional: enable git hooks locally" below.

Alternative (host install):

  • Install Developer Tools:

    • Install .NET SDK:

      # Windows
      winget install Microsoft.DotNet.SDK.10
      
      # Linux
      apt install dotnet-sdk-10.0
      
    • Install Visual Studio Code:

      # Windows
      winget install Microsoft.VisualStudioCode
      
    • Install Visual Studio:

      # Windows
      winget install Microsoft.VisualStudio.Community
      
  • Clone and Configure Project:

    • Clone the repository and initialize tools:

      # Clone from CLI (or clone from VSCode)
      git clone -b main https://github.com/ptr727/[Project].git ./[Project]
      
      # Initialize dotnet tools
      cd ./[Project]
      dotnet tool restore
      
    • Open DotNet.code-workspace (or Python.code-workspace) in Visual Studio Code.

    • Open [Project].slnx in Visual Studio.

Optional: enable git hooks locally:

Hooks are not shipped with the template - CI is the lint backstop. Opt in per language if you want pre-commit checks locally.

  • For .NET work - install Husky.Net:

    dotnet new tool-manifest  # if no tool manifest exists yet
    dotnet tool install Husky
    dotnet husky install
    dotnet husky add pre-commit -c "dotnet csharpier check . && dotnet format style --verify-no-changes --severity=info"
    
  • For Python work - install pre-commit:

    uv tool install pre-commit
    pre-commit install
    

    Sample .pre-commit-config.yaml (the hooks shell into PyPiLibrary/ because the uv project - and therefore ruff/pyright and their configs - lives there, not at the repo root):

    repos:
      - repo: local
        hooks:
          - id: ruff-check
            name: ruff check
            entry: uv run --directory PyPiLibrary ruff check
            language: system
            files: ^PyPiLibrary/.*\.py$
            pass_filenames: false
          - id: ruff-format
            name: ruff format
            entry: uv run --directory PyPiLibrary ruff format --check
            language: system
            files: ^PyPiLibrary/.*\.py$
            pass_filenames: false
          - id: pyright
            name: pyright
            entry: uv run --directory PyPiLibrary pyright
            language: system
            files: ^PyPiLibrary/.*\.py$
            pass_filenames: false
    

CI runs these same checks on every PR, so hooks are purely a local convenience.

3rd Party Tools

3rd Party tools used in this project:

License

Licensed under the MIT License
GitHub License

Template Project Setup

Template - TODO List

  • Configure git for SSH signing before making any commits (the signed-commits ruleset rejects unsigned history, and retrofitting it forces a full re-sign - see GitHub Setup), plus SSH forwarding in dev containers - see docs/host-setup.md, docs/ssh-signing.md, and docs/devcontainer.md.
  • Decide whether your project needs the .NET (NuGetLibrary/) side, the Python (PyPiLibrary/) side, or both. Delete the unused folder and remove its references from ProjectTemplate.slnx, .github/dependabot.yml, and the corresponding .github/workflows/build-*-task.yml.
  • Start on Linux to avoid file permission issues when moving from Windows.
  • Configure the Developer Environment.
  • Open the project directory (not the workspace) in Visual Studio Code, and rename (Ctrl-Shift-H) all instances of ProjectTemplate to [NewProject] in code.
  • Rename DotNet.code-workspace to [NewProject].code-workspace and Python.code-workspace to [NewProject]-Python.code-workspace, or delete the workspace for the language you don't need. Rename ProjectTemplate.slnx to [NewProject].slnx.
  • Open the workspace file for the language you kept ([NewProject].code-workspace and/or [NewProject]-Python.code-workspace) in Visual Studio Code.
  • Delete any projects and associated actions that will not be used, update dependencies in actions to remove deleted actions.
  • Rename projects to match the naming, update .slnx and .csproj files, and update actions to match the naming.
  • Update the namespace in .cs and .csproj files to match the naming.
  • Update all ref-links in README.md to point to the naming.
  • Keep the template's mandatory shared files and sections - do not re-invent them per repo. Carry verbatim the AGENTS.md "PR Review Etiquette" section, .github/copilot-instructions.md (the Copilot review runbook), .markdownlint-cli2.jsonc, .editorconfig, and .gitattributes, adapting only <owner>/<repo> placeholders. See AGENTS.md "Files and Sections Derived Repos Must Carry Verbatim", and re-sync them from the template periodically - filing an upstream issue when you spot a template gap.
  • Publish to GitHub from VSCode to create a new empty GitHub repository.
  • Commit and push the first-branch.
  • Edit and iterate only in first-branch until ready to start with git history.
  • Setup main as the first permanent branch when ready.
  • Configure GitHub for the new repository - including deleting any classic branch protection and creating the two develop/main rulesets by exporting/importing the template's (see Rules / Rulesets).
  • Follow the Branching Workflow.
  • Delete the Project Template Setup section from README.md.

Template - Developer Environment Setup

Template - Git Setup
  • ⚠️ Prerequisites:

  • Setup new project from template:

    # Clone the template project
    git clone -b main https://github.com/ptr727/ProjectTemplate.git ./[NewProject]
    
    # Reset git to start a new repo
    rm -r ./[NewProject]/.git
    cd ./[NewProject]
    git init -b first-branch
    
    # Init dotnet tools
    dotnet tool restore
    
    # Update dotnet tools
    dotnet tool update --all
    dotnet outdated --upgrade:prompt
    
  • Setup new project from scratch:

    ⚠️ Linux: Start configuration on Linux to avoid file permission issues.

    # Init git
    mkdir ./[NewProject]
    cd ./[NewProject]
    git init -b first-branch
    
    # Init dotnet tools
    dotnet new tool-manifest
    dotnet tool install csharpier
    dotnet tool install dotnet-outdated-tool
    
  • Use first-branch for all the initial project setup and testing.

  • When ready, only when ready, create main branch from first-branch with no history:

    Warning - sign from the very first commit. The Initial import (squashed) commit below - and every commit after it - must be cryptographically signed. Configure SSH signing (the Prerequisites above) before running these commands, and verify it is live: git config --get commit.gpgsign is true and a signer is loaded (ssh-add -L for SSH). Do not enable the Require signed commits ruleset (below) until the branch's history is fully signed. Enabling it on a branch that already contains unsigned commits forces you to rewrite the entire history to re-sign it - which changes every commit SHA and makes whoever does the rewrite the committer and signer of every commit (you cannot sign another contributor's commits for them; a rebase preserves the author field but not the original signatures). Order: signing first → clean signed history → then the ruleset.

    # Create main branch with no history
    git checkout --orphan main
    git commit --allow-empty -m "temp"
    
    # Squash merge changes
    git merge --squash first-branch
    git commit -m "Initial import (squashed)"
    
    # Drop the temporary commit
    git reset --hard HEAD~1
    
    # Delete first-branch
    git branch -D first-branch
    

Template - GitHub Setup

GitHub secrets setup:

  • Create a NuGet API Key.

    • Save the Key as NUGET_API_KEY in:
      • GitHub project Settings / Secrets / Actions.
      • GitHub project Settings / Secrets / Dependabot.
      • GitHub Local Actions Settings / Secrets.
  • Create a Docker Hub Personal Access Token.

    • Save the PAT as DOCKER_HUB_ACCESS_TOKEN and DOCKER_HUB_USERNAME in:
      • GitHub project Settings / Secrets / Actions.
      • GitHub project Settings / Secrets / Dependabot.
  • Create a GitHub App for the codegen and merge-bot workflows.

    • App name: ptr727-codegen.
    • Bot user: ptr727-codegen[bot].
    • Permissions required (repository scope):
      • Contents: Read & write - push commits to the codegen branch and merge bot PRs.
      • Pull requests: Read & write - open, update, and merge pull requests.
      • Metadata: Read-only (auto-required).
    • Note the client id, and download and secure the private key .pem file.
    • Grant the app access to the repository, or all repositories.
      • GitHub Settings / Developer settings / GitHub Apps / ptr727-codegen / Configure
      • The app must be both created and installed - creating it alone is not sufficient (actions/create-github-app-token fails with Not Found if the app isn't installed on the repository).
    • Save the Client ID as CODEGEN_APP_CLIENT_ID and the private key contents as CODEGEN_APP_PRIVATE_KEY in both of:
      • GitHub project Settings / Secrets / Actions - for the codegen workflow and the codegen merge job.
      • GitHub project Settings / Secrets / Dependabot - required because Dependabot-triggered pull_request workflow runs use a separate, restricted secret context that doesn't see Actions secrets. Without the App secrets in the Dependabot store, the merge-dependabot job in merge-bot-pull-request.yml can't mint an App token and the PR will never auto-merge.
    • If the codegen workflows require additional secrets (e.g. third-party API keys), register them in the Actions store; if a Dependabot-triggered workflow ever needs them, register them in the Dependabot store too.
    • The App token is used by both the codegen workflow (run-codegen-pull-request-task.yml) and every job in merge-bot-pull-request.yml. App-authored pushes/PRs trigger downstream pull_request and push workflow events directly - unlike GITHUB_TOKEN-authored events, which are blocked by GitHub's recursion guard. This matters for two reasons: bot-opened PRs trigger the test-pull-request.yml smoke build (so they can't auto-merge unvalidated), and - when PUBLISH_ON_MERGE is enabled - the merge commit triggers publish-release.yml. App-authored events also let the codegen workflow's auto-merge fire directly.
    • The codegen auto-merge condition in merge-bot-pull-request.yml (merge-codegen job) requires:
      • Event is opened or reopened - auto-merge is enabled once per PR at open time; subsequent synchronize events do not re-enable. This is what lets the disable-auto-merge-on-maintainer-push safeguard (below) stick.
      • github.event.pull_request.user.login == 'ptr727-codegen[bot]' - PR was opened by the App.
      • github.event.pull_request.head.repo.full_name == github.repository - PR is from this repo (not a fork).
      • Strict head/base pairing - (head.ref == 'codegen-main' && base.ref == 'main') || (head.ref == 'codegen-develop' && base.ref == 'develop'). Codegen runs as a matrix opening one PR per branch; this pairing prevents a misconfigured workflow from sneaking a codegen-develop branch into main or vice versa.
    • The disable-auto-merge-on-maintainer-push job in merge-bot-pull-request.yml runs on synchronize events against bot-authored PRs (Dependabot or codegen) when the event actor is NOT the same bot - i.e. a maintainer pushed commits. It calls gh pr merge --disable-auto so the maintainer's commits don't auto-merge along with the bot's content. Re-enable auto-merge manually (gh pr merge --auto <PR> or the GitHub UI) when ready.

    Codegen targets main AND develop in parallel (matrix in run-codegen-pull-request-task.yml), so generated content lands on both branches independently without any back-merging. See AGENTS.md "Branching Model" for why this dual-target pattern beats develop-only-with-flow-through.

Codegen workflow schedule:

  • run-periodic-codegen-pull-request.yml runs daily at 04:00 UTC (staggered two hours after the weekly publish), plus on-demand via workflow_dispatch. It uses the App token (CODEGEN_APP_CLIENT_ID + CODEGEN_APP_PRIVATE_KEY) to commit, open the PR as ptr727-codegen[bot], and let the merge-bot auto-merge once CI passes. No PAT, no close/reopen dance. Daily is cheap in the default two-phase model - codegen merges only smoke-test; the weekly publish batches the actual release.

GitHub project settings:

  • General:
    • Default branch: main
    • Pull requests - both merge methods enabled at the repo level so each branch ruleset can pick the right one (develop = Squash, main = Merge):
      • Allow merge commits ✓ (required for develop → main releases)
      • Allow squash merging ✓ (required for feature → develop merges)
      • Allow rebase merging - disabled (no flow uses it; the develop ruleset forbids it anyway)
      • Always suggest updating pull request branches
      • Allow auto-merge
  • Rules / Rulesets - separate rulesets per branch. Develop and main intentionally diverge on two rules - allowed merge methods and Require linear history. Require branches to be up to date before merging is off on both for related-but-distinct reasons (below); everything else is shared.
    • Configure these by exporting the template's rulesets and re-importing them - do not hand-build the rules. The result must be exactly two rulesets named develop and main (the names are load-bearing: AGENTS.md and these docs reference them). Reconstructing each rule by hand is the step that has gone wrong on past ports.
      • Step 0 - remove ALL legacy protection first. Delete every classic branch-protection rule (Settings → Branches) and every pre-existing or stray ruleset (Settings → Rules → Rulesets) - not just some - so enforcement isn't doubled or contradicted. This template uses rulesets only, configured exclusively by the JSON export/import in Steps 1-2 below; never hand-build the rules in the UI. Partial cleanup (leaving a stray ruleset or a classic rule behind) is what has gone wrong on past ports. Equivalent API:

        # Delete classic branch protection if present (404 = none, which is fine)
        for b in main develop; do gh api -X DELETE "repos/<owner>/<repo>/branches/$b/protection" 2>/dev/null || true; done
        # List existing rulesets; delete any that are not the two created below
        gh api "repos/<owner>/<repo>/rulesets" --jq '.[] | "\(.id)\t\(.name)"'
        # gh api -X DELETE "repos/<owner>/<repo>/rulesets/<id>"
        
      • Step 1 - export the template's two rulesets, keeping only the re-importable fields (the GET response also carries id, timestamps, _links, source, etc. that a create call rejects):

        for name in develop main; do
          id=$(gh api repos/ptr727/ProjectTemplate/rulesets --jq ".[] | select(.name==\"$name\") | .id")
          gh api "repos/ptr727/ProjectTemplate/rulesets/$id" \
            --jq '{name, target, enforcement, bypass_actors, conditions, rules}' > "$name-ruleset.json"
        done
        
      • Step 2 - import into the new repo:

        for name in develop main; do
          gh api -X POST "repos/<owner>/<repo>/rulesets" --input "$name-ruleset.json"
        done
        
      • Caveats: bypass_actors uses the Admin repository role (actor_id: 5), a global GitHub id that ports across repos as-is. The required status-check context (Check pull request workflow status) is matched by name and only turns green after test-pull-request.yml has run at least once. gh ruleset is read-only (list/view) - creation must go through gh api -X POST as above. If a field is rejected, edit the JSON and re-run the import.

      • Renaming or updating an existing ruleset needs a FULL-payload PUT, not a partial one. gh api -X PUT "repos/<owner>/<repo>/rulesets/<id>" -f name=develop (name only) fails with 422 Unexpected parameter 'allowed_dismissal_actors': GitHub re-validates the stored pull_request rule on a partial update, and that rule carries fields the GET response does not return. To rename (e.g. legacy Develop/Maindevelop/main) or otherwise edit a ruleset, GET it, change the field, and PUT the whole {name, target, enforcement, bypass_actors, conditions, rules} back (the same writable-field subset used for export above). Back up the GET first and verify afterward that the rule types, required_signatures, non_fast_forward, and the required status-check context are all still present. Renaming is safe for enforcement - the required status-check binds by check name, not ruleset name, so a rename won't break CI - but the template still expects the exact develop/main names that AGENTS.md and these docs reference (which is precisely what a legacy Develop/Main repo is renaming to); the rename removes inconsistency, it isn't a license for arbitrary names.

      • Migrating a brownfield repo with unsigned history. The shared Require signed commits rule (below) rejects any commit made before signing was enabled, so on a pre-existing repo the first develop -> main release is blocked the moment it tries to introduce that legacy history. The fix is to re-sign the legacy commits, but that rewrite is a non-fast-forward and the Block force pushes rule rejects it - and the ruleset's admin bypass does not cover git push --force (GitHub honors ruleset bypass for UI/API operations, not git force-push). So even the owner cannot complete the re-sign without temporarily relaxing the ruleset. This is a one-time, maintainer-performed manual migration - it deliberately uses the force-push that AGENTS.md "Git and Commit Rules" forbids agents from running, so an AI agent must never execute this procedure; surface it to the maintainer instead. Procedure:

        1. Re-sign the divergent history, preserving merge topology. Prefer a rebase, which re-signs each commit with your current key (commit.gpgsign / -S):

          git rebase --rebase-merges --exec 'git commit --amend --no-edit -S' <merge-base>
          

          git filter-branch also works but is deprecated upstream (it prints a warning; suppress with FILTER_BRANCH_SQUELCH_WARNING=1, or use git filter-repo if installed) - keep it only as a fallback:

          git filter-branch -f --commit-filter 'git commit-tree -S "$@"' -- <merge-base>..HEAD
          
        2. Temporarily set the develop (and main if it diverged) ruleset Enforcement to Disabled (Settings → Rules → Rulesets), since the admin bypass won't permit the force-push.

        3. (Maintainer only) Force-push the re-signed branch. This is the single manual force-push the template sanctions; agents must never run it (see AGENTS.md "Git and Commit Rules").

        4. Re-enable Enforcement.

        Alternatively, enable Require signed commits only on a repo whose full history is already signed - greenfield repos created from this template (where signing is live before the first commit, per AGENTS.md "Git and Commit Rules") never hit this.

      • The per-branch settings below are the reference for what each ruleset contains and why (and the manual fallback if you configure via the UI):

    • "Develop":
      • Target branches: develop.
      • Allowed merge methods: Squash
      • Require linear history (develop is kept linear; main carries merge commits by design, so this setting belongs to develop only)
      • Require status checks to passRequire branches to be up to date before merging intentionally OFF. Leaving it on stalls bot auto-merge when two bot PRs against develop land within the same window - the first merge flips the second to mergeStateStatus: BEHIND, and GitHub's auto-merge will not fire while strict is on. The merge-bot in .github/workflows/merge-bot-pull-request.yml only enables auto-merge on opened/reopened and never auto-updates bot branches; Dependabot's rebase isn't real-time. With strict off, squash mechanics still rebase the diff onto develop's tip on merge, Require linear history still enforces linearity, textual conflicts still block mergeable: CONFLICTING, and the required Check pull request workflow status still gates merges. See AGENTS.md "Branching Model" for the full reasoning.
      • Plus shared settings (below).
    • "Main":
      • Target branches: main.
      • Allowed merge methods: Merge
      • Require status checks to passRequire branches to be up to date before merging intentionally OFF. This rule is incompatible with the forward-only develop model. GitHub's "up to date" check is graph-based: it asks whether main's tip commit is reachable from develop. After any develop → main release, main's new tip is a brand-new merge commit that develop's history doesn't contain. Forward-only develop never adds it (no back-merge of main into develop, no rebase of develop onto main), so the check fails permanently on every subsequent release. Leaving the rule on would force every release through an admin bypass. See AGENTS.md "Branching Model" for the full reasoning.
      • Plus shared settings (below).
    • Shared settings (apply to both rulesets):
      • Restrict deletions
      • Require signed commits
      • Require a pull request before merging
        • Dismiss stale pull request approvals when new commits are pushed
        • Require conversation resolution before merging
      • Require status checks to pass
        • Status checks that are required: Check pull request workflow status
      • Block force pushes
      • Automatically request Copilot code review
        • Review new pushes
        • Review draft pull requests
  • Actions / General:
    • Allow GitHub Actions to create and approve pull requests

Template - Branching Workflow

See AGENTS.md "Branching Model" for the authoritative definition. Summary:

  • Persistent main and develop branches, each with its own ruleset (above). Both must always be building error free.
  • Feature branches off develop. Only commit on feature branches, never directly to develop or main.
  • Feature → develop: squash-merge (develop ruleset enforces this; develop is kept linear).
  • developmain: merge-commit (preserves develop's commit list as a real second-parent reference on main; main ruleset enforces this).
  • develop is forward-only. No main -> develop back-merges. The develop squash-only ruleset physically blocks merge commits.
  • A develop -> main release requires a develop version bump right after. Once the merge lands and main's publish completes, raise the version minor in version.json on develop via an isolated bump-version-X.Y PR, so develop's prereleases stay numerically above main's last stable. A develop -> main promotion that carries only maintenance (not a release) holds main's version instead (git checkout main -- version.json on the promotion branch). See AGENTS.md "Release Model".
  • Bots open parallel PRs against both branches. .github/dependabot.yml duplicates each ecosystem entry per branch, and .github/workflows/run-codegen-pull-request-task.yml runs as a matrix (branch names codegen-main and codegen-develop). Each branch absorbs its own bot PRs independently - neither falls behind, no back-merges needed.
  • Review-then-merge loop. Every PR is reviewed by GitHub Copilot. The agent pushes, re-requests a review on the new head (via the requestReviews GraphQL mutation), addresses and resolves each finding, repeats until green, and then waits for the maintainer's explicit permission to merge - it does not self-merge. See AGENTS.md "PR Review Etiquette" and the Copilot Review Runbook for the mechanics.

Template - Release Distribution Model: Two-Phase by Default

This template ships with a two-phase model that decouples merging from publishing:

  • Pull requests smoke-test only. .github/workflows/test-pull-request.yml always runs unit tests, then path-gates a reduced build of only the targets a PR touches (dorny/paths-filter): Docker as linux/amd64 only (no QEMU/arm64), the executable as a representative runtime subset, and nothing is pushed. A docs-only PR runs unit tests alone; a Dependabot github-actions bump is unit-tests-only. This is fast feedback, not a release.
  • Merges to main/develop do not publish. A push only smoke-tested the PR; merging it republishes nothing.
  • The weekly schedule + manual dispatch are the sole publishers. .github/workflows/publish-release.yml runs every Monday 02:00 UTC and on-demand via workflow_dispatch, and on either trigger does the full build/publish of both main (Release / latest / non-prerelease) and develop (Debug / develop / prerelease) - GitHub release, NuGet/PyPI uploads, multi-arch Docker tags, platform executables, and a refreshed Docker base image. Trigger a release on demand from the Actions UI when you want one between weekly runs.

This batches cheap bot churn (Dependabot/codegen merge daily, validated by smoke builds) into one periodic publish instead of one release per merge, and keeps PR feedback fast by deferring the slow arm64/full-matrix builds to the publisher. A no-op weekly run (no new commit, so an unchanged SemVer2) re-pushes nothing to GitHub Releases / NuGet / PyPI - only Docker re-pushes, to pick up upstream base-image refreshes.

Reusing the Release Pipeline in a Derived Project (Any Language)

The pipeline is built in two layers so you only customize one of them:

  • Orchestration (sync verbatim - don't rewrite): the publish plan and branch matrix in publish-release.yml, the version step (get-version-task.yml), the date badge (build-datebadge-task.yml), and - the key part - the github-release job that tags the built commit, creates the GitHub Release, and attaches assets. It collects assets by the pattern release-asset-<branch>-* and never names a build job, so it works unchanged no matter what you ship.
  • Build (you own these): the build-<target>-task.yml leaf tasks. Each one builds an output and either pushes it to a registry, uploads a release-asset-<branch>-<name> artifact for the GitHub Release, or both.

The one rule: to put a file on the GitHub Release, upload it as an artifact named release-asset-<branch>-<name>. That's the seam - implement it in a leaf task and the rest of the release just works.

Customize by where your outputs go, not by language:

What you ship What to do Goes to
A zip / packaged files / a binary on the GitHub Release (e.g. a data or asset library) One leaf task: validate → zip → upload release-asset-<branch>-library GitHub Release asset
A NuGet package Keep/adapt build-nugetlibrary-task (it dotnet nuget pushes and uploads a release-asset-*) NuGet.org + GitHub Release asset
A PyPI package Keep/adapt build-pypilibrary-task (build + artifact) and the publish-pypi job in publish-release.yml (OIDC upload) PyPI only
A Docker image Keep/adapt build-docker-task (pushes multi-arch tags) Docker Hub only
A compiled app/CLI Keep/adapt build-executable-task - note it is specifically dotnet publish; replace it wholesale for another toolchain GitHub Release asset
Just validate + tag a release (no build output) Put your checks in test-pull-request.yml; attach a release-asset-* only if you have a file GitHub Release (tag, optionally an asset)

For each output you don't ship, delete its build-<target>-task.yml, its job + needs entry in build-release-task.yml, its test-pull-request.yml path filter, and (PyPI) the publish-pypi job. So build-release-task.yml is edited to reflect your set of leaf jobs, but its github-release logic stays as-is - that's the part you reuse rather than fork. get-version-task.yml installs the .NET SDK to run NBGV (which produces the version/tag) even in a non-.NET repo. See AGENTS.md "Release Model" for the full seam contract and the no-op-republish guarantee.

Opt in to publish-on-merge. Set the repository variable PUBLISH_ON_MERGE to true (Settings → Secrets and variables → Actions → Variables) to switch to the continuous-release model: every push/merge to main publishes main and every push to develop publishes develop, immediately. The weekly and manual publishers also run. Leave the variable unset (or false) for the two-phase default. It's a repository variable, not a workflow edit, so pulling template updates never conflicts with your choice.

Which to pick: two-phase suits projects whose consumers are pushed updates (HACS for Home Assistant, package managers that auto-update, Linux distros that vendor from main) where every release is a forced update and frequent bot-driven releases are noise. PUBLISH_ON_MERGE=true suits projects whose consumers pull at their own cadence (Docker pulls, NuGet/PyPI installs, manual downloads) and want every merged change available immediately. For an example of a push-distribution project, see homeassistant-purpleair (ships through HACS).

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.90 45 6/15/2026
1.0.82 42 6/15/2026
1.0.75 95 6/8/2026
1.0.70 88 6/8/2026
1.0.64 100 6/4/2026
1.0.62 98 6/1/2026
1.0.60 94 5/25/2026
1.0.59-gad39ee1061 89 6/1/2026
1.0.58 91 5/25/2026
1.0.58-g427b88a37b 95 5/25/2026
1.0.57 103 5/18/2026
1.0.57-g9c1f39e442 93 5/25/2026
1.0.56-gd013abf7ef 89 5/24/2026
1.0.55 95 5/13/2026
1.0.55-gbd3876e3dc 96 5/18/2026
1.0.54 102 5/12/2026
1.0.54-ga03ad12524 92 5/13/2026
1.0.53-gd450626890 90 5/12/2026
1.0.52 97 5/12/2026
1.0.52-gbb66475387 91 5/12/2026
Loading failed