Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ Do not duplicate language-specific rules here.

Feature → develop PRs squash-merge — the PR title becomes the single commit on develop. Develop → main PRs merge-commit — main's history shows one merge commit per release with develop's tip as the second parent. Titles are descriptive and have no versioning effect — versioning is handled by [Nerdbank.GitVersioning](https://github.com/dotnet/Nerdbank.GitVersioning) reading [version.json](../version.json) and git history, not by parsing commit messages.

Branch protection enforces the merge method on both bases (develop allows only squash, main allows only merge). When running `gh pr merge` against either base, pick the matching flag (`--squash` for develop, `--merge` for main); a mismatch fails with "Merge method ... is not allowed on this repository". The merge-bot workflow (`.github/workflows/merge-bot-pull-request.yml`) does this dispatch automatically for Dependabot and codegen PRs via a `case` on `base.ref` — keep that pattern when adding new auto-merge jobs.

### Format

- Imperative subject summarizing the change, ≤ 72 characters, no trailing period. ("Add 24-hour PM2.5 average sensor", not "Added X" or "Adds X".)
Expand Down
24 changes: 21 additions & 3 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
#
# `target-branch: "develop"` on every ecosystem entry routes Dependabot's
# scheduled version-update PRs to the integration branch instead of the
# repo default (`main`). That keeps dep bumps in the
# `feature → develop → main` flow described in AGENTS.md: each bump joins
# the bot-triggered develop prerelease for early-warning testing, then
# bundles into the next develop → main merge-commit alongside feature work.
# This is also what keeps develop from falling behind main — bumps land on
# develop first.
#
# Caveat: `target-branch` only redirects scheduled version updates.
# Dependabot *security update PRs* (the CVE-driven ones Dependabot opens
# in response to security alerts) are opened against the default branch
# (`main`) and do not honor `target-branch`. The merge-bot's `case`
# statement in .github/workflows/merge-bot-pull-request.yml handles
# either base correctly (squash for develop, merge for main), and a
# maintainer can retarget manually from the PR UI if a one-off needs
# the other branch.
version: 2
updates:

- package-ecosystem: "nuget"
target-branch: "main"
target-branch: "develop"
directory: "/"
schedule:
interval: "daily"
Expand All @@ -13,7 +31,7 @@ updates:
- "*"

- package-ecosystem: "github-actions"
target-branch: "main"
target-branch: "develop"
directory: "/"
schedule:
interval: "daily"
Expand All @@ -23,7 +41,7 @@ updates:
- "*"

- package-ecosystem: "uv"
target-branch: "main"
target-branch: "develop"
directory: "/PyPiLibrary"
schedule:
interval: "daily"
Expand Down
26 changes: 26 additions & 0 deletions .github/workflows/build-pypilibrary-task.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,15 @@ on:

jobs:

get-version:
name: Get version information job
uses: ./.github/workflows/get-version-task.yml
secrets: inherit

Comment thread
ptr727 marked this conversation as resolved.
build-pypilibrary:
name: Build PyPI library project job
runs-on: ubuntu-latest
needs: [get-version]
defaults:
run:
working-directory: ./PyPiLibrary
Expand Down Expand Up @@ -60,6 +66,26 @@ jobs:
- name: Run pytest step
run: uv run pytest

# Overwrite `_version.py` (which ships hardcoded "0.0.0" so local
# `uv build` works without CI) with NBGV's `AssemblyFileVersion` —
# always Major.Minor.Patch.BuildNumber, all numeric, PEP 440 valid.
# `_version.py` is the single source `hatchling` reads via the
# `[tool.hatch.version]` path in pyproject.toml. Done AFTER tests so
# the test that asserts `__version__` is a non-empty string isn't
# affected. The PyPI version string therefore equals the .NET
# assemblies' `FileVersion` / `AssemblyVersion` stamp — both are
# `AssemblyFileVersion`. NuGet `PackageVersion` and Docker tags use
# NBGV's `SemVer2` instead (different format, may include `-g<sha>`
# or prerelease suffixes that PEP 440 rejects), so those strings
# are not byte-identical to PyPI's; all four still derive from the
# same NBGV computation per release commit.
Comment thread
ptr727 marked this conversation as resolved.
Outdated
- name: Write version into _version.py step
run: |
set -euo pipefail
printf '__version__ = "%s"\n' "$VERSION" > src/ptr727_projecttemplate_library/_version.py
Comment thread
ptr727 marked this conversation as resolved.
Outdated
env:
VERSION: ${{ needs.get-version.outputs.AssemblyFileVersion }}

- name: Build sdist and wheel step
run: uv build

Expand Down
213 changes: 111 additions & 102 deletions .github/workflows/merge-bot-pull-request.yml
Original file line number Diff line number Diff line change
@@ -1,102 +1,111 @@
name: Merge bot pull request action

# Token strategy:
# GitHub's recursion guard blocks pushes authored by `GITHUB_TOKEN` from
# triggering further workflow runs. When `gh pr merge --auto --squash` runs
# under `secrets.GITHUB_TOKEN`, the resulting squash-merge push therefore
# does NOT fire `publish-release.yml`.
#
# All three jobs below merge bot PRs targeting `main` (per the per-job `if:`
# conditions). Releases on `main` are dispatched manually via
# `workflow_dispatch`, so the missing trigger is acceptable for all three
# paths. If a future bot PR targets `develop` (where releases auto-fire on
# push), this merge action would need to switch to an App token so the
# resulting push is authored by an App identity not blocked by the
# recursion guard.

on:
pull_request:
types: [opened, reopened, synchronize]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:

merge-dependabot:
name: Merge dependabot pull request job
runs-on: ubuntu-latest
# 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
permissions:
contents: write
pull-requests: write

steps:

- name: Get dependabot metadata step
id: metadata
uses: dependabot/fetch-metadata@v2
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.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}}
GH_TOKEN: ${{secrets.GITHUB_TOKEN}}

merge-codegen:
name: Merge codegen pull request job
runs-on: ubuntu-latest
# 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' &&
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
# 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]' &&
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

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}}
name: Merge bot pull request action

# Token strategy:
# Every merge job in this workflow uses the GitHub App token
# (`actions/create-github-app-token`). The resulting merge push is
# committed by the App, which fires downstream workflows on develop and
# main. Pushes authored by `GITHUB_TOKEN` are blocked from triggering
# further workflow runs by GitHub's recursion guard, which would
# silently skip `publish-release.yml` on the merge commit. The App-token
# path also removes the close/reopen dance previously used by codegen
# PRs created under `GITHUB_TOKEN` to nudge the auto-merge workflow.
#
# Merge method:
# Each merge step picks `--squash` or `--merge` from the PR's base ref so
# the form matches that branch's ruleset (`develop` allows only squash,
# `main` allows only merge commits — see AGENTS.md "Branching Model").
# A mismatch fails `enablePullRequestAutoMerge` with "Merge method ... is
# not allowed on this repository". Codegen PRs always target `main` so
# they always merge-commit; Dependabot PRs default to `develop` but
# security update PRs open against `main`, so the `case` statement
# handles both bases.

on:
pull_request:
types: [opened, reopened, synchronize]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
Comment thread
ptr727 marked this conversation as resolved.
cancel-in-progress: true

jobs:

merge-dependabot:
name: Merge dependabot pull request job
runs-on: ubuntu-latest
# Restrict to Dependabot PRs that originate from this repository, not a
# fork. Check the PR author rather than the event actor so maintainer
# repair commits on Dependabot branches can still auto-merge after CI
# passes.
if: >-
github.event.pull_request.user.login == 'dependabot[bot]' &&
github.event.pull_request.head.repo.full_name == github.repository
permissions:
contents: write
pull-requests: write

steps:

- name: Generate GitHub App token step
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.CODEGEN_APP_ID }}
private-key: ${{ secrets.CODEGEN_APP_PRIVATE_KEY }}

- name: Get dependabot metadata step
id: metadata
uses: dependabot/fetch-metadata@21025c705c08248db411dc16f3619e6b5f9ea21a # v2.5.0
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"

# Skip semver-major NuGet bumps: majors can build cleanly but break
# runtime behaviour, so they should land via human review. Other
# ecosystems' majors (github-actions, uv) are usually safe and merge.
- name: Merge pull request step
if: >-
(steps.metadata.outputs.package-ecosystem != 'nuget') ||
(steps.metadata.outputs.update-type != 'version-update:semver-major')
run: |
set -euo pipefail
case "${{ github.event.pull_request.base.ref }}" in
develop) method=--squash ;;
main) method=--merge ;;
*)
echo "::error::Unsupported base branch: ${{ github.event.pull_request.base.ref }}"
exit 1
;;
esac
gh pr merge --auto "$method" "$PR_URL"
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GH_TOKEN: ${{ steps.app-token.outputs.token }}

merge-codegen:
name: Merge codegen pull request job
runs-on: ubuntu-latest
# Restrict to codegen PRs that originate from the App in this repository.
# Codegen always opens PRs against `main` from the `codegen` branch.
if: >-
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

steps:

- name: Generate GitHub App token step
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.CODEGEN_APP_ID }}
private-key: ${{ secrets.CODEGEN_APP_PRIVATE_KEY }}

- name: Merge pull request step
run: gh pr merge --auto --merge "$PR_URL"
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GH_TOKEN: ${{ steps.app-token.outputs.token }}
8 changes: 8 additions & 0 deletions .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ jobs:

publish-pypi:
name: Publish PyPI library job
# Restrict PyPI upload to `main` pushes. The `pypi` GitHub environment
# also has a Deployment branch rule allowing only `main` as defense in
# depth, but without this `if:` the job would still attempt to run on
# develop pushes and be blocked at the env gate — visible as a stalled
# / failed job on every develop release. PyPI tracks releases, not the
# prerelease channel; NuGet/Docker/executables already publish on
# develop with NBGV prerelease versions.
if: github.ref == 'refs/heads/main'
needs: [create-release]
runs-on: ubuntu-latest
environment:
Expand Down
70 changes: 0 additions & 70 deletions .github/workflows/run-codegen-app-pull-request-task.yml

This file was deleted.

Loading
Loading