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
5 changes: 5 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ indent_size = 2
[.github/workflows/*.{yml,yaml}]
end_of_line = lf

# Catalog snippet workflows mirror real workflow files (LF above); keep the snippets LF so a copied snippet
# lands compliant instead of needing conversion.
[catalog/snippets/workflows/*.{yml,yaml}]
end_of_line = lf

# Linux scripts
[*.sh]
end_of_line = lf
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ Applies to code and workflow (`#`) comments alike.
### Line Endings

- **[`.editorconfig`](./.editorconfig) sets the line ending:** `[*] end_of_line = crlf` is the **default** - every file type is CRLF unless pinned otherwise - with **LF** pinned for the execution-sensitive exceptions - `*.sh`, Dockerfiles, and any individual `.py` executed directly via its shebang (pinned **by path**, e.g. `spec/validate.py`; vanilla `.py` stays CRLF, since Python's universal newlines accept it and it is commonly edited on Windows). Only the LF exceptions are declared; the redundant per-type CRLF rules are intentionally omitted. `.gitattributes` mirrors it: `* -text` (git stores the exact bytes you commit and will **not** normalize) plus the matching LF pins.
- **Choosing an ending for a new file type:** CRLF is the **default** - cross-platform editors on Windows produce it, and it is harmless on Linux for everything except shell. Use LF only when the type **requires** it or CRLF **breaks how it is consumed**: executable scripts/shebangs (`*.sh`, s6, husky), Dockerfiles (CRLF breaks `RUN` heredocs/continuations), and tool-owned formats with a native LF ending (KiCad). **Non-workflow YAML stays CRLF** - GitHub Actions' parser tolerates it (a repo that also runs yamllint sets `new-lines: disable` to defer to `.editorconfig`). **Workflow YAML (`.github/workflows/*.{yml,yaml}`) is pinned LF** in `.editorconfig` - Dependabot and Actions rewrite it with LF, so declaring LF keeps it consistent instead of mixed on every bump. This is the one LF class that is **not** also a `.gitattributes` pin: git keeps `* -text` (no normalization), and CI's `editorconfig-checker` (EOL-only) catches a mismatch instead. Distinguish where a file is *consumed* from where it is *edited*: consumption on Linux alone does not force LF.
- **Choosing an ending for a new file type:** CRLF is the **default** - cross-platform editors on Windows produce it, and it is harmless on Linux for everything except shell. Use LF only when the type **requires** it or CRLF **breaks how it is consumed**: executable scripts/shebangs (`*.sh`, s6, husky), Dockerfiles (CRLF breaks `RUN` heredocs/continuations), and tool-owned formats with a native LF ending (KiCad). **Non-workflow YAML stays CRLF** - GitHub Actions' parser tolerates it (a repo that also runs yamllint sets `new-lines: disable` to defer to `.editorconfig`). **Workflow YAML (`.github/workflows/*.{yml,yaml}`) is pinned LF** in `.editorconfig` - Dependabot and Actions rewrite it with LF, so declaring LF keeps it consistent instead of mixed on every bump. This (and the catalog snippet workflows in `catalog/snippets/workflows/*`, pinned LF the same way) is an LF class **not** backed by a `.gitattributes` pin: git keeps `* -text` (no normalization), and CI's `editorconfig-checker` (EOL-only) catches a mismatch instead. Distinguish where a file is *consumed* from where it is *edited*: consumption on Linux alone does not force LF. A config or pattern file consumed by a Linux tool stays CRLF when the tool tolerates a trailing CR: `.dockerignore` and `.gitignore` are CRLF (their parsers strip the CR), and only a *Dockerfile* - interpreted, where a CR breaks `RUN` heredocs and line continuations - is LF.
- **Scripts and extensionless executables must be LF - and pinned in `.gitattributes`, not just configured.** A CRLF shebang (`#!/usr/bin/env bash\r`) breaks execution. `.editorconfig` sets `[*.sh] = lf`, but that extension-based rule does not match **extensionless** executables (s6 service scripts `run`/`up`/`finish`, husky/git hook scripts like `.husky/pre-commit`), and `* -text` enforces nothing - so a broad normalization pass or an editor can silently flip them to CRLF (it has). `.gitattributes` is the enforcement layer: it carries `*.sh text eol=lf`, and any repo whose tooling ships extensionless scripts **adds the matching path pin** - e.g. `Docker/s6-overlay/** text eol=lf` for s6 init, `.husky/pre-commit text eol=lf` for husky hooks - so git holds them at LF on checkout and `--renormalize`. This pin is mandatory for any repo that overrides s6 init, uses husky/git hooks, or otherwise ships executable scripts. The same explicit-pin rule extends to **tool-owned file formats the base config doesn't key on**: pin them to whatever ending the tool reads and writes so a normalization sweep can't churn them - e.g. KiCad project/footprint/3D files (`*.kicad_mod`, `*.kicad_sym`, `*.step`), which KiCad writes LF (`*.kicad_mod text eol=lf`, ...). The principle is general: a file class the `.editorconfig` extension rules and `* -text` don't cover needs an explicit `.gitattributes` pin matching its tool's native ending.
- **Pair each such pin with a matching `.editorconfig` override - the git pin alone is not enough.** `.gitattributes` governs **git** (checkout, commit, `--renormalize`); the **editor** follows `.editorconfig`, where the `[*] end_of_line = crlf` default still applies to any file no extension rule covers. So even with the git pin, the editor writes a CRLF shebang into an extensionless hook (breaking it when run from the working tree) or re-ends/trims a byte-sensitive data file. Give every extensionless **executable** an editorconfig LF override beside its `.gitattributes` pin (`[.husky/pre-commit] end_of_line = lf`); and for a **byte-preserve data directory** (downloaded or opaque source whose exact bytes the consumer may depend on) disable *all* editor normalization, not just EOL - `[<dir>/*]` with `charset = unset`, `end_of_line = unset`, `insert_final_newline = false`, `trim_trailing_whitespace = false` (`unset` is EditorConfig's spec-defined special value that removes an inherited property, so the editor enforces neither the global `charset` nor `end_of_line` on that path). Keep these overrides with the line-ending governance (above any `.NET-only` divider), not in the language-style section.
- **New files:** create them with the `.editorconfig`-mandated ending.
Expand Down
58 changes: 29 additions & 29 deletions catalog/snippets/workflows/build-datebadge-task.yml
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
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:
jobs:
date-badge:
name: Build BYOB date badge job
runs-on: ubuntu-latest
steps:
- name: Get current date step
id: date
run: echo "date=$(date)" >> "$GITHUB_OUTPUT"
- name: Build BYOB date badge step
uses: RubbaBoy/BYOB@24f464284c1fd32028524b59607d417a2e36fee7 # v1.3.0
with:
name: lastbuild
label: "Last Build"
icon: "github"
status: ${{ steps.date.outputs.date }}
color: "blue"
github_token: ${{ secrets.GITHUB_TOKEN }}
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:

jobs:

date-badge:
name: Build BYOB date badge job
runs-on: ubuntu-latest

steps:

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

- name: Build BYOB date badge step
uses: RubbaBoy/BYOB@24f464284c1fd32028524b59607d417a2e36fee7 # v1.3.0
with:
name: lastbuild
label: "Last Build"
icon: "github"
status: ${{ steps.date.outputs.date }}
color: "blue"
github_token: ${{ secrets.GITHUB_TOKEN }}
192 changes: 96 additions & 96 deletions catalog/snippets/workflows/build-docker-task.yml
Original file line number Diff line number Diff line change
@@ -1,96 +1,96 @@
name: Build Docker image task
on:
workflow_call:
inputs:
# Input to control whether to push the Docker image to Docker Hub
push:
required: false
type: boolean
default: false
# Git ref to check out / version (empty = default checkout ref).
ref:
required: false
type: string
default: ''
# Logical branch driving config and tags (main => Release/latest, otherwise Debug/develop). Required (no
# github.ref_name fallback): the publisher builds develop from a main-ref run, so a fallback would mistag it.
branch:
required: true
type: string
# Smoke mode: build linux/amd64 only (no QEMU/arm64), never push, and skip the shared registry cache-to so PR
# builds don't pollute the release buildcache. Used for fast PR feedback.
smoke:
required: false
type: boolean
default: false
jobs:
get-version:
name: Get version information job
uses: ./.github/workflows/get-version-task.yml
secrets: inherit
with:
ref: ${{ inputs.ref }}
build-docker:
name: Build Docker image job
runs-on: ubuntu-latest
needs: [get-version]
env:
# Multi-arch (amd64+arm64) only when publishing the main image; every other build is amd64 only.
PLATFORMS: ${{ (inputs.push && inputs.branch == 'main') && 'linux/amd64,linux/arm64' || 'linux/amd64' }}
steps:
- name: Checkout step
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ inputs.ref }}
# arm64 is non-native on the amd64 runner, so install its QEMU emulator only when the build includes it.
- name: Setup QEMU step
if: ${{ contains(env.PLATFORMS, 'arm64') }}
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
with:
platforms: linux/arm64
- name: Setup Buildx step
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
with:
platforms: ${{ env.PLATFORMS }}
# Always login (even for smoke) for the higher Docker rate limits on pulls/cache reads; fork PRs without the
# secrets can't run the Docker smoke build, acceptable for same-repo PRs.
- name: Login to Docker Hub step
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Docker build and push step
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
with:
context: .
push: ${{ inputs.push }}
file: ./Docker/Dockerfile
tags: |
docker.io/ptr727/projecttemplate:${{ inputs.branch == 'main' && 'latest' || 'develop' }}
docker.io/ptr727/projecttemplate:${{ needs.get-version.outputs.SemVer2 }}
platforms: ${{ env.PLATFORMS }}
# Read both branches' caches (near-identical layers) but write only this branch's tag, and only when
# pushing, so the weekly main+develop matrix legs don't overwrite one shared cache.
cache-from: |
type=registry,ref=docker.io/ptr727/projecttemplate:buildcache-main
type=registry,ref=docker.io/ptr727/projecttemplate:buildcache-develop
cache-to: ${{ inputs.push && format('type=registry,ref=docker.io/ptr727/projecttemplate:buildcache-{0},mode=max,ignore-error=true', inputs.branch) || '' }}
build-args: |
LABEL_VERSION=${{ needs.get-version.outputs.SemVer2 }}
BUILD_CONFIGURATION=${{ inputs.branch == 'main' && 'Release' || 'Debug' }}
BUILD_VERSION=${{ needs.get-version.outputs.AssemblyVersion }}
BUILD_FILE_VERSION=${{ needs.get-version.outputs.AssemblyFileVersion }}
BUILD_ASSEMBLY_VERSION=${{ needs.get-version.outputs.AssemblyVersion }}
BUILD_INFORMATION_VERSION=${{ needs.get-version.outputs.AssemblyInformationalVersion }}
BUILD_PACKAGE_VERSION=${{ needs.get-version.outputs.SemVer2 }}
name: Build Docker image task

on:
workflow_call:
inputs:
# Input to control whether to push the Docker image to Docker Hub
push:
required: false
type: boolean
default: false
# Git ref to check out / version (empty = default checkout ref).
ref:
required: false
type: string
default: ''
# Logical branch driving config and tags (main => Release/latest, otherwise Debug/develop). Required (no
# github.ref_name fallback): the publisher builds develop from a main-ref run, so a fallback would mistag it.
branch:
required: true
type: string
# Smoke mode: build linux/amd64 only (no QEMU/arm64), never push, and skip the shared registry cache-to so PR
# builds don't pollute the release buildcache. Used for fast PR feedback.
smoke:
required: false
type: boolean
default: false

jobs:

get-version:
name: Get version information job
uses: ./.github/workflows/get-version-task.yml
secrets: inherit
with:
ref: ${{ inputs.ref }}

build-docker:
name: Build Docker image job
runs-on: ubuntu-latest
needs: [get-version]
env:
# Multi-arch (amd64+arm64) only when publishing the main image; every other build is amd64 only.
PLATFORMS: ${{ (inputs.push && inputs.branch == 'main') && 'linux/amd64,linux/arm64' || 'linux/amd64' }}

steps:

- name: Checkout step
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ inputs.ref }}

# arm64 is non-native on the amd64 runner, so install its QEMU emulator only when the build includes it.
- name: Setup QEMU step
if: ${{ contains(env.PLATFORMS, 'arm64') }}
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
with:
platforms: linux/arm64

- name: Setup Buildx step
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
with:
platforms: ${{ env.PLATFORMS }}

# Always login (even for smoke) for the higher Docker rate limits on pulls/cache reads; fork PRs without the
# secrets can't run the Docker smoke build, acceptable for same-repo PRs.
- name: Login to Docker Hub step
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}

- name: Docker build and push step
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
with:
context: .
push: ${{ inputs.push }}
file: ./Docker/Dockerfile
tags: |
docker.io/ptr727/projecttemplate:${{ inputs.branch == 'main' && 'latest' || 'develop' }}
docker.io/ptr727/projecttemplate:${{ needs.get-version.outputs.SemVer2 }}
platforms: ${{ env.PLATFORMS }}
# Read both branches' caches (near-identical layers) but write only this branch's tag, and only when
# pushing, so the weekly main+develop matrix legs don't overwrite one shared cache.
cache-from: |
type=registry,ref=docker.io/ptr727/projecttemplate:buildcache-main
type=registry,ref=docker.io/ptr727/projecttemplate:buildcache-develop
cache-to: ${{ inputs.push && format('type=registry,ref=docker.io/ptr727/projecttemplate:buildcache-{0},mode=max,ignore-error=true', inputs.branch) || '' }}
build-args: |
LABEL_VERSION=${{ needs.get-version.outputs.SemVer2 }}
BUILD_CONFIGURATION=${{ inputs.branch == 'main' && 'Release' || 'Debug' }}
BUILD_VERSION=${{ needs.get-version.outputs.AssemblyVersion }}
BUILD_FILE_VERSION=${{ needs.get-version.outputs.AssemblyFileVersion }}
BUILD_ASSEMBLY_VERSION=${{ needs.get-version.outputs.AssemblyVersion }}
BUILD_INFORMATION_VERSION=${{ needs.get-version.outputs.AssemblyInformationalVersion }}
BUILD_PACKAGE_VERSION=${{ needs.get-version.outputs.SemVer2 }}

Loading