diff --git a/.config/mise/tasks/github/actions/ci/lint b/.config/mise/tasks/github/actions/ci/lint new file mode 100755 index 0000000..c6c6034 --- /dev/null +++ b/.config/mise/tasks/github/actions/ci/lint @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +#MISE description="Lint every workflow and shell script" +set -euo pipefail + +actionlint + +# -r keeps this a no-op rather than an error when nothing matches. +git ls-files -z '*.sh' '.config/mise/tasks/**' | xargs -0 -r shellcheck -x diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..7a308c8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,29 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +permissions: {} + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + name: Lint + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + # Tool versions live in mise.toml and the task lives under + # .config/mise/tasks, so a contributor runs exactly what CI runs. + - uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0 + + - run: mise run github:actions:ci:lint diff --git a/.github/workflows/semconv.yml b/.github/workflows/semconv.yml new file mode 100644 index 0000000..8ea85b3 --- /dev/null +++ b/.github/workflows/semconv.yml @@ -0,0 +1,21 @@ +name: SemConv + +on: + pull_request: + types: [opened, edited, synchronize, reopened] + +permissions: {} + +jobs: + lint-pr-title: + name: Validate PR Title + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + pull-requests: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: ./actions/semconv/pull-request diff --git a/actions/semconv/README.md b/actions/semconv/README.md new file mode 100644 index 0000000..eeb50e2 --- /dev/null +++ b/actions/semconv/README.md @@ -0,0 +1,9 @@ +# semconv + +Actions that enforce [Conventional Commits][spec] across a repository. + +| Action | Enforces | +| --- | --- | +| [`pull-request`](pull-request) | The pull request title | + +[spec]: https://www.conventionalcommits.org/en/v1.0.0/ diff --git a/actions/semconv/pull-request/README.md b/actions/semconv/pull-request/README.md new file mode 100644 index 0000000..52c7643 --- /dev/null +++ b/actions/semconv/pull-request/README.md @@ -0,0 +1,68 @@ +# semconv/pull-request + +Fails a pull request whose title is not a Conventional Commit. A thin wrapper +around [`amannn/action-semantic-pull-request`][upstream] that fixes the grammar +so every repository agrees on it. + +## Usage + +The job name matters. Repository rulesets require the check by the literal +string `Validate PR Title`, so renaming the job silently stops the required +check from ever reporting. + +```yaml +name: SemConv + +on: + pull_request: + types: [opened, edited, synchronize, reopened] + +permissions: {} + +jobs: + lint-pr-title: + name: Validate PR Title + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + pull-requests: read + steps: + - uses: TrogonStack/github-actions/actions/semconv/pull-request@ +``` + +The reference must be a full commit SHA. Both organizations set +`sha_pinning_required`, so a branch or tag reference makes the job refuse to +start rather than fail. + +## Grammar + +``` +[(scope)][!]: +``` + +`feat`, `fix`, and `chore` are the only types. Anything that is not a feature or +a fix is a chore. The description must not start with an uppercase letter. + +The type list is not an input. One grammar for every repository is the reason +this action exists; a knob invites back the divergence it was built to remove. + +## Inputs + +| Input | Default | Description | +| --- | --- | --- | +| `extra-ignore-labels` | none | Further labels that skip the check, one per line. | +| `token` | `github.token` | Read access is sufficient. | + +`bot`, `dependencies`, and `autorelease: pending` are always ignored. The last +is release-please's own pull request, which does not follow the grammar it +exists to produce. + +## Title only + +Only the title is checked, because only the title reaches the default branch on +a squash merge. Repositories that allow merge or rebase commits, or that squash +with `COMMIT_OR_PR_TITLE`, can still land an unconventional subject. That is a +repository settings problem and is fixed in the Terraform that owns those +settings, not here. + +[upstream]: https://github.com/amannn/action-semantic-pull-request diff --git a/actions/semconv/pull-request/action.yml b/actions/semconv/pull-request/action.yml new file mode 100644 index 0000000..64832a0 --- /dev/null +++ b/actions/semconv/pull-request/action.yml @@ -0,0 +1,44 @@ +name: Validate PR Title +description: >- + Fails a pull request whose title is not a Conventional Commit. +author: TrogonStack + +inputs: + extra-ignore-labels: + description: >- + Further labels that skip the check, one per line. Bot and release labels + are already ignored. + required: false + default: '' + token: + description: Token used to read the pull request. Read access is sufficient. + required: false + default: ${{ github.token }} + +runs: + using: composite + steps: + - name: Validate PR title + uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1 + env: + GITHUB_TOKEN: ${{ inputs.token }} + with: + # Anything that is not a feature or a fix is a chore. This list is + # deliberately not an input: one grammar for every repository is the + # reason this action exists. + types: | + feat + fix + chore + requireScope: false + subjectPattern: ^(?![A-Z]).+$ + subjectPatternError: | + The subject "{subject}" in the pull request title "{title}" should start + with a lowercase letter. + # "autorelease: pending" is release-please's own pull request, which does + # not follow the grammar it exists to produce. + ignoreLabels: | + bot + dependencies + autorelease: pending + ${{ inputs.extra-ignore-labels }} diff --git a/mise.toml b/mise.toml new file mode 100644 index 0000000..6707bf1 --- /dev/null +++ b/mise.toml @@ -0,0 +1,3 @@ +[tools] +actionlint = "1.7.12" +shellcheck = "0.11.0"