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
29 changes: 29 additions & 0 deletions .githooks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# .githooks

Committed Git hooks that run the fleet's gate locally, before a commit lands,
so a failure surfaces here rather than after a push.

Fleet-managed by [conf](https://github.com/PowderworksCode/conf); edit them
there, not here — a local change is drift the next sync reports.

## Activate

Hooks are not enabled by a clone. The repository's development script does it,
or run it once yourself:

```sh
git config core.hooksPath .githooks
```

## What runs

- `commit-msg` enforces Conventional Commits on the subject line.
- `pre-commit` runs Straitjacket over the tree, through `run-straitjacket`.

`run-straitjacket` **fails** when Straitjacket is not installed. A hook that
skips its only check wherever the tool is missing reports "clean" most loudly
where it has looked least; the message says how to install it.

## Bypass

`git commit --no-verify` skips the hooks for one commit.
26 changes: 26 additions & 0 deletions .githooks/commit-msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/bin/sh
# Conventional-commit gate. The fleet lints pull-request titles in CI; this
# applies the same rule to the commit subject, so a bad message fails here
# rather than after a push. Bypass with `git commit --no-verify`.
#
# Fleet-managed by conf (.ordnung/managed/githooks/commit-msg): edit it there.
msg_file="$1"

# First non-blank, non-comment line is the subject.
subject=$(grep -vE '^[[:space:]]*#' "$msg_file" | grep -vE '^[[:space:]]*$' | head -n1)

# Git's own machine-generated messages pass untouched.
case "$subject" in
"Merge "*|"Revert "*|"fixup! "*|"squash! "*) exit 0 ;;
esac

if printf '%s' "$subject" |
grep -qE '^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-z0-9./_-]+\))?!?: .+'; then
exit 0
fi

echo "commit message must follow Conventional Commits: type(scope): summary" >&2
echo " types: feat fix docs style refactor perf test build ci chore revert" >&2
echo " got: $subject" >&2
echo " see https://www.conventionalcommits.org" >&2
exit 1
9 changes: 9 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/sh
# The local gate: what CI would say about this tree, said before the commit
# lands. Bypass with `git commit --no-verify`.
#
# Fleet-managed by conf (.ordnung/managed/githooks/pre-commit).
set -eu

hooks_dir=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
exec "$hooks_dir/run-straitjacket" .
27 changes: 27 additions & 0 deletions .githooks/run-straitjacket
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/bin/sh
# Run Straitjacket over the repository, the way CI does.
#
# It fails when Straitjacket is not installed rather than passing quietly: a
# hook that skips its only check on the machines that lack the tool is a hook
# that reports "clean" most loudly where it has looked least.
#
# Fleet-managed by conf (.ordnung/managed/githooks/run-straitjacket).
set -eu

if command -v straitjacket >/dev/null 2>&1; then
exec straitjacket "$@"
fi

# A local install that is not on PATH is still an install.
for candidate in "$HOME/.local/bin/straitjacket" "$HOME/.cargo/bin/straitjacket"; do
if [ -x "$candidate" ]; then
exec "$candidate" "$@"
fi
done

echo "straitjacket is not installed, and this hook will not pass without it." >&2
echo "" >&2
echo " curl -fsSL https://straitjacket.dev/install | sh" >&2
echo "" >&2
echo "Then re-run the commit. To skip the hooks once: git commit --no-verify" >&2
exit 1
67 changes: 67 additions & 0 deletions .github/workflows/fleet-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,73 @@ jobs:
min-severity: high
advanced-security: false

# Prose style, over the Markdown a member writes. Vale fetches the packages
# named in .vale.ini at run time, so the styles are versioned by that file
# rather than committed here.
prose-style:
name: vale
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# Asked after checkout, never as a job-level `if: hashFiles(...)`: that
# reads a workspace which does not exist yet, and a workflow whose
# expression cannot be evaluated does not start at all.
- name: A configured member is a graded one
id: configured
run: |
if [ -f .vale.ini ]; then
echo "vale=true" >> "$GITHUB_OUTPUT"
else
echo "vale=false" >> "$GITHUB_OUTPUT"
echo "no .vale.ini; prose style is not graded here"
fi
- uses: errata-ai/vale-action@518a9136acc6e6668ce7c00d367051e0941e87ff # v3.0.0
if: steps.configured.outputs.vale == 'true'
with:
fail_on_error: true

# Stylesheets, for the members that configure them. Run through bunx rather
# than a dependency, so a repository that writes CSS is not made to declare a
# linter it never imports.
styles:
name: stylelint
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: "1.4.0"
- name: A configured member is a graded one
id: configured
run: |
if [ -f .stylelintrc.json ]; then
echo "stylelint=true" >> "$GITHUB_OUTPUT"
else
echo "stylelint=false" >> "$GITHUB_OUTPUT"
echo "no .stylelintrc.json; stylesheets are not graded here"
fi
- name: Stylelint
if: steps.configured.outputs.stylelint == 'true'
run: |
sheets=$(git ls-files '*.css' '*.scss' | grep -v node_modules || true)
if [ -z "$sheets" ]; then
echo "no stylesheets tracked"
exit 0
fi
# stylelint-config-standard extends stylelint-config-recommended, so
# both have to be resolvable from wherever stylelint runs — `bunx
# stylelint` alone brings neither, and fails on the extends rather
# than on the CSS. They go in a scratch directory, and
# --config-basedir points the resolution there, so a repository that
# writes CSS still does not declare a linter it never imports.
tools=$(mktemp -d)
(cd "$tools" && bun add --silent stylelint@17.14.1 stylelint-config-standard@40.0.0)
# shellcheck disable=SC2086 — the file list is deliberately split.
"$tools/node_modules/.bin/stylelint" \
--config .stylelintrc.json --config-basedir "$tools" $sheets

shell:
name: shellcheck
runs-on: ubuntu-latest
Expand Down
16 changes: 16 additions & 0 deletions .vale.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Fleet-managed by conf (.ordnung/managed/vale/vale.ini).
#
# proselint and write-good rather than a house style guide: they catch prose
# that is weak on anyone's terms — weasel words, passive constructions,
# clichés, redundancy — without imposing a voice on repositories whose readers
# are each other. A style guide can come later, from writing that exists.
StylesPath = .vale/styles
MinAlertLevel = warning

Packages = proselint, write-good

[*.md]
BasedOnStyles = proselint, write-good

# Prose lives in sentences; a fenced block is code someone will run.
BlockIgnores = (?s) *(```.*?```)