Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
27 changes: 22 additions & 5 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
# Leave line endings alone
# git config --global core.autocrlf false
# git add --renormalize .
# git ls-files --eol
* -text
# Default: do not normalize line endings (`* -text`); .editorconfig end_of_line rules guide what the editor writes.
# The exception pins below are git's own enforcement - they force LF for execution-sensitive classes regardless of editor.
# 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.
18 changes: 5 additions & 13 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,13 +16,10 @@ 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
uses: RubbaBoy/BYOB@24f464284c1fd32028524b59607d417a2e36fee7 # v1.3.0
with:
name: lastbuild
label: "Last Build"
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
Loading
Loading