Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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
4 changes: 4 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ end_of_line = crlf
[*.sh]
end_of_line = lf

# Dockerfiles - CRLF breaks RUN heredocs and line continuations
[{Dockerfile,*.Dockerfile}]
end_of_line = lf

# Windows scripts
[*.{cmd,bat,ps1}]
end_of_line = crlf
Expand Down
26 changes: 21 additions & 5 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Leave line endings alone
# git config --global core.autocrlf false
# git add --renormalize .
# git ls-files --eol
* -text
# Leave line endings alone; the .editorconfig per-extension end_of_line rules govern.
Comment thread
ptr727 marked this conversation as resolved.
Outdated
# git config --global core.autocrlf false
# git add --renormalize .
# git ls-files --eol
* -text

# Exception: scripts must stay LF regardless of the `* -text` default - a CRLF shebang breaks execution. `.editorconfig`
# covers `*.sh`, but extensionless executables (s6 service scripts, hooks) match no extension rule, so pin them here so
# git enforces LF on checkout and `--renormalize`. A repo shipping extensionless scripts adds an explicit path rule,
# e.g. for s6-overlay init: `Docker/s6-overlay/** text eol=lf`.
*.sh text eol=lf

# Dockerfiles must be LF - a CRLF breaks RUN heredocs and line continuations.
Dockerfile text eol=lf
*.Dockerfile text eol=lf

# Extensionless executables must stay LF - a CRLF shebang breaks execution. The Husky.Net git hook matches no extension rule.
.husky/pre-commit text eol=lf

# LanguageData/ holds downloaded source data the parser reads byte-for-byte; never normalize it. The `* -text` default
# above preserves it exactly as downloaded - do NOT add a `text`/`eol=` rule here.
Comment thread
ptr727 marked this conversation as resolved.
16 changes: 4 additions & 12 deletions .github/workflows/build-datebadge-task.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
name: Build BYOB date badge task

# Caller-gated: the publisher invokes this only when main is published - the badge has no per-branch context, it tracks
# the last main build.

on:
workflow_call:
inputs:
# Logical branch this badge run is for. The badge only updates on
# `main`; the publisher passes the branch explicitly so a scheduled
# run building `develop` doesn't try to write the main badge. Required
# (no `github.ref_name` fallback) so the gate can't silently misfire.
branch:
required: true
type: string

jobs:

Expand All @@ -21,12 +16,9 @@ jobs:

- name: Get current date step
id: date
run: |
set -euo pipefail
echo "date=$(date)" >> $GITHUB_OUTPUT
run: echo "date=$(date)" >> "$GITHUB_OUTPUT"

- name: Build BYOB date badge step
if: ${{ inputs.branch == 'main' }}
uses: RubbaBoy/BYOB@a4919104bc0ec7cfd7f113e42c405cc45246f2a4 # v1
with:
name: lastbuild
Expand Down
36 changes: 34 additions & 2 deletions .github/workflows/build-release-task.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ name: Build project release task
on:
workflow_call:
inputs:
# Input to control whether to create a GitHub release
# Whether to create a GitHub release.
github:
required: false
type: boolean
default: false
# Input to control whether to push the library to NuGet.org
# Whether to push the library to NuGet.org.
nuget:
required: false
type: boolean
Expand All @@ -35,6 +35,12 @@ on:
required: false
type: boolean
default: true
# Set false for a repo that produces no release-asset-* files (e.g. Docker-only): the release is then just the
# tag + source zip + README + LICENSE; the artifact download is skipped and the unmatched-files guard relaxes.
expect_release_assets:
required: false
type: boolean
default: true

jobs:

Expand Down Expand Up @@ -76,7 +82,28 @@ jobs:
with:
ref: ${{ needs.get-version.outputs.GitCommitId }}

# Backstop (main only): a public release must not carry a prerelease '-', guarding against NBGV mis-versioning the
# public ref (e.g. a dispatch on a non-default ref) into a malformed "Latest" release. Strip '+buildmetadata'
# first - a '-' there is legitimate; only a '-' in the core/prerelease segment marks a prerelease.
- name: Verify public release version step
if: ${{ inputs.branch == 'main' }}
env:
SEMVER2: ${{ needs.get-version.outputs.SemVer2 }}
run: |
set -euo pipefail
CORE_AND_PRE="${SEMVER2%%+*}" # drop +buildmetadata; a '-' here is the genuine prerelease separator
if [[ "$CORE_AND_PRE" == *-* ]]; then
echo "::error::Public (main) release version '$SEMVER2' carries a prerelease suffix; refusing to publish."
exit 1
fi

# Collect assets by the `release-asset-<branch>-*` pattern so this step is target-agnostic: subset releases by
# deleting the target, not `enable_*: false` (a skipped `needs` job would skip this release job too). The release
# step guards `fail_on_unmatched_files: true`, so at least one `release-asset-*` must match; a repo that drops
# every file-producing target (e.g. a Docker-only repo, whose release carries only source zip + README + LICENSE)
# relaxes that guard.
- name: Download release asset artifacts step
if: ${{ inputs.expect_release_assets }}
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: release-asset-${{ inputs.branch }}-*
Expand Down Expand Up @@ -106,6 +133,10 @@ jobs:
# `target_commitish` must be set explicitly: otherwise GitHub's REST API tags the release on the default branch.
# Pin it to `GitCommitId` so the tag is on the exact built commit, consistent with the SemVer2 tag and artifacts.
# Skip when the release already exists, but always let a manual `workflow_dispatch` through to refresh it.
# Every release (any branch, any target) is a tag on the built commit plus the auto-attached source zip, README,
# and LICENSE; targets amend it by uploading `release-asset-*` files (binaries/packages) or pushing elsewhere
# (image/registry). `fail_on_unmatched_files: true` fails loudly if a promised `release-asset-*` is missing or
# misnamed; a no-file-target repo relaxes it (see download step).
- name: Create GitHub release step
if: ${{ steps.release-exists.outputs.exists == 'false' || github.event_name == 'workflow_dispatch' }}
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
Expand All @@ -114,6 +145,7 @@ jobs:
tag_name: ${{ needs.get-version.outputs.SemVer2 }}
target_commitish: ${{ needs.get-version.outputs.GitCommitId }}
prerelease: ${{ inputs.branch != 'main' }}
fail_on_unmatched_files: ${{ inputs.expect_release_assets }}
Comment thread
ptr727 marked this conversation as resolved.
files: |
LICENSE
README.md
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/get-version-task.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ on:
type: string
default: ''
outputs:
# Version information outputs
SemVer2:
value: ${{ jobs.get-version.outputs.SemVer2 }}
AssemblyVersion:
Expand Down
7 changes: 4 additions & 3 deletions .github/workflows/merge-bot-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ on:
pull_request_target:
types: [opened, reopened, synchronize]

# `cancel-in-progress: false` is required so events process to completion in arrival order: a follow-up
# synchronize must not cancel an in-flight `opened` run before it enables auto-merge.
# Per-PR group: under `pull_request_target` `github.ref` is the base branch, which would serialize every bot PR
# against that base; key on the PR number so each PR's events queue independently. `cancel-in-progress: false` so a
# follow-up synchronize doesn't cancel an in-flight `opened` run before it enables auto-merge.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: false

jobs:
Expand Down
10 changes: 3 additions & 7 deletions .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,20 +86,16 @@ jobs:
github: true
nuget: true

# Caller-gated to main: the badge has no per-branch context, so it updates only when main is among the published
# branches (a develop-only push skips it). One invocation, not a per-branch matrix leg.
date-badge:
name: Create BYOB date badge job
needs: [setup, publish]
if: ${{ needs.setup.outputs.publish == 'true' }}
strategy:
matrix:
branch: ${{ fromJSON(needs.setup.outputs.branches) }}
if: ${{ needs.setup.outputs.publish == 'true' && contains(fromJSON(needs.setup.outputs.branches), 'main') }}
uses: ./.github/workflows/build-datebadge-task.yml
secrets: inherit
permissions:
contents: write
with:
# The badge task self-gates to `main`; the develop leg is a no-op.
branch: ${{ matrix.branch }}

# Delete the run's artifacts (durable copies live on the GitHub release) to keep them off the account storage quota.
cleanup-artifacts:
Expand Down
12 changes: 10 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,20 @@ Anti-pattern: don't keep flipping the code on the same style point. Flip the rul

This repo is derived from [`ptr727/ProjectTemplate`](https://github.com/ptr727/ProjectTemplate) and re-syncs against it periodically, not just at creation.

- **Verbatim carries.** Pull the current template version of each shared artifact and re-apply it, adapting only this repo's placeholders: [`.github/copilot-instructions.md`](./.github/copilot-instructions.md) (the Copilot review runbook - change only the `<owner>`/`<repo>`/`<N>` values in its API snippets), [`.markdownlint-cli2.jsonc`](./.markdownlint-cli2.jsonc), [`.editorconfig`](./.editorconfig), [`.gitattributes`](./.gitattributes), and this file's [PR Review Etiquette](#pr-review-etiquette) section. The `.editorconfig` EOL/per-extension block is always-verbatim; its `[*.cs]`/ReSharper block is .NET-only and is carried here. Keep `copilot-instructions.md` **narrow** (provider mechanics plus the commit/PR-title summary); project-specific conventions and API contracts live in this file (see [Library API Conventions](#library-api-conventions)), not there - non-Copilot agents are not directed to that file.
- **CODESTYLE.md.** Re-sync the whole file from the template, then keep the **General** section plus the **.NET** language section and drop the language sections this repo doesn't ship (the per-language sections are droppable, exactly like the `.editorconfig` `[*.cs]` block; this repo is .NET-only, so the Python section is dropped). Repo-root placement is load-bearing - `AGENTS.md` and `.github/copilot-instructions.md` link it by relative path. Adapt the in-section repo-specific bits: the .NET project-folder list, the `InternalsVisibleTo` project names, and the VS Code task labels. Replacing the file wholesale and dropping whole sections is simpler to keep current than hand-editing per-language snippets.
- **Verbatim carries.** Pull the current template version of each shared artifact and re-apply it, adapting only this repo's placeholders: [`.github/copilot-instructions.md`](./.github/copilot-instructions.md) (the Copilot review runbook - change only the `<owner>`/`<repo>`/`<N>` values in its API snippets), [`.markdownlint-cli2.jsonc`](./.markdownlint-cli2.jsonc), [`.editorconfig`](./.editorconfig), [`.gitattributes`](./.gitattributes), and this file's [PR Review Etiquette](#pr-review-etiquette) section. Carry [`.editorconfig`](./.editorconfig) **whole** - the EOL/per-extension block and the `[*.cs]`/ReSharper block both, even sections for languages this repo doesn't ship (an inert block costs nothing and keeps re-sync a clean overwrite). Keep `copilot-instructions.md` **narrow** (provider mechanics plus the commit/PR-title summary); project-specific conventions and API contracts live in this file (see [Library API Conventions](#library-api-conventions)), not there - non-Copilot agents are not directed to that file.
- **CODESTYLE.md.** Carry the **whole file verbatim** from the template, every language section included - the Python section is inert in this .NET-only repo but costs nothing and keeps re-sync a clean wholesale overwrite rather than a per-section merge. Repo-root placement is load-bearing - `AGENTS.md` and `.github/copilot-instructions.md` link it by relative path. Adapt the in-section repo-specific bits: the .NET project-folder list, the `InternalsVisibleTo` project names, and the VS Code task labels.
- **.vscode/tasks.json.** Carry the named **clean-compile** task definitions verbatim - `.NET Build`, `CSharpier Format`, and `.NET Format` (which chains the first two then `dotnet format style --verify-no-changes`). Their names are owned by the `CODESTYLE.md` ".NET" section and their command sequence + arguments are the canonical clean-compile spec; don't loosen them. Convenience tasks (`.NET Tool Update`, `.NET Outdated Upgrade`, `Husky.Net Run`) are the adapt zone.
- **Release notes.** Keep a short release-notes summary in [`README.md`](./README.md) and the full history in [`HISTORY.md`](./HISTORY.md); update both when cutting a release.
- **Report drift upstream.** When a re-sync surfaces a template gap, an outdated instruction, or something that bit this repo and would bite the next derived repo, open an issue in [`ptr727/ProjectTemplate`](https://github.com/ptr727/ProjectTemplate) rather than only patching locally - the template is the single source of truth, and this upstream-issue rule is this repo's only cross-repo obligation. Do not maintain or reference a "known downstream" registry, and do not name sibling repositories in docs, comments, or workflows - that registry and the maintainer fan-out duty live in the template hub only.

### Template adaptations

Intentional, documented deviations from the carried template state. Everything not listed here tracks the template verbatim.

- **Husky.Net pre-commit gate.** This repo wires the clean-compile checks as local Husky.Net pre-commit git hooks (installed via `dotnet tool restore` + `dotnet husky install`); the `Husky.Net Run` VS Code task runs them manually. The template ships no git hooks by default and treats CI as the only lint backstop, so [`CODESTYLE.md`](./CODESTYLE.md)'s git-hook note and the `.vscode/tasks.json` convenience-task set are adapted accordingly. CI still runs the same checks as a backstop.
Comment thread
ptr727 marked this conversation as resolved.
- **Codegen uses the `LanguageTagsCreate` CLI, no `NINJA_API_KEY`.** Embedded language data is regenerated by the in-repo [`LanguageTagsCreate/`](./LanguageTagsCreate/) tool pulling directly from the official ISO 639-2/3 + RFC 5646 registries. There is no external codegen API, so this repo carries no `NINJA_API_KEY` secret or any reference to one.
- **`merge-upstream-version` job legitimately absent.** [`merge-bot-pull-request.yml`](./.github/workflows/merge-bot-pull-request.yml) carries only `merge-dependabot`, `merge-codegen`, and `disable-auto-merge-on-maintainer-push`. The template's `merge-upstream-version` job auto-merges an upstream-version-bump PR flow this repo does not run (LanguageTags pins no upstream binary version), so that job is intentionally not carried. The concurrency keying and the three carried jobs match the template verbatim.

## Workflow YAML Conventions

These conventions describe the target state. New and modified workflows must respect them; the rest of the repo is expected to be brought up to the same standard.
Expand Down
Loading
Loading