diff --git a/.githooks/pre-push b/.githooks/pre-push new file mode 100755 index 0000000..c90f18d --- /dev/null +++ b/.githooks/pre-push @@ -0,0 +1,26 @@ +#!/bin/sh +# Refuse to push to main. The GitHub ruleset rejects it too, but only after the +# objects have gone over the wire and with a less obvious message; this catches +# the habit locally. +# +# Enable in a fresh clone with: +# git config core.hooksPath .githooks +# +# git stdin is one line per ref being pushed: +# + +protected_ref='refs/heads/main' +status=0 + +while read -r _local_ref _local_sha remote_ref _remote_sha; do + case "$remote_ref" in + "$protected_ref") + echo "pre-push: refusing to push to $protected_ref." >&2 + echo " Branch, push that, and open a pull request." >&2 + echo " Deliberate exception: git push --no-verify" >&2 + status=1 + ;; + esac +done + +exit $status diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b929d7d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,54 @@ +# The check `main` is protected by. Both suites run in one job because they are +# seconds each and a matrix would only make the required-check name harder to +# pin down in the ruleset. +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # go-version-file rather than a literal, so the toolchain follows go.mod + # and there is one place to bump it. + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + # Only for `node --test`; the client has no dependencies and no build. + - uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: gofmt + run: | + unformatted=$(gofmt -l .) + if [ -n "$unformatted" ]; then + echo "gofmt needed on:" + echo "$unformatted" + exit 1 + fi + + - name: go vet + run: go vet ./... + + - name: go test + run: go test ./... + + # core.test.mjs — pure functions, no DOM, so no browser is needed. + - name: node test + run: node --test + + # Not published anywhere; this only proves the packaging manifest still + # builds, which is easy to break without noticing. + - name: package + run: make deb diff --git a/CLAUDE.md b/CLAUDE.md index cec0f9b..4c94357 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -29,6 +29,14 @@ make deb # cross-built .deb into dist/ Go 1.22+. One dependency (`golang.org/x/image`), no npm, no build step for the client. +**Never commit or push to `main`.** It is protected on GitHub — direct pushes +are rejected and changes land through a pull request that CI has passed — and +`.githooks/pre-push` refuses the push locally (`git config core.hooksPath +.githooks`, once per clone). Branch, push the branch, open a PR. CI is +`.github/workflows/ci.yml`; it adds `gofmt -l .`, `go vet ./...` and a `make +deb` packaging build on top of the two test suites, so run those before pushing +rather than discovering them in the PR. + `static/` is embedded with `//go:embed`, so **editing anything under `static/` requires a rebuild** — a running binary keeps serving the old assets. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7977a24 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Paul Glover + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index c60a33d..9e9f02b 100644 --- a/README.md +++ b/README.md @@ -414,3 +414,27 @@ counting it in the inset would shrink every landscape image for no visual gain. ## Not done - No service worker, so this does not run offline. + +## Working on it + +`main` is protected: direct pushes are rejected and changes land through a pull +request that CI has passed. Set the local half up once per clone, which makes +git refuse the push before it reaches GitHub: + +```bash +git config core.hooksPath .githooks +``` + +CI (`.github/workflows/ci.yml`) runs what `make test` runs, plus `gofmt -l`, +`go vet` and a packaging build. Everything it does works locally: + +```bash +make test # go test ./... and node --test +gofmt -l . # prints nothing when clean +go vet ./... +make deb # proves nfpm.yaml still packages +``` + +## License + +MIT — see [LICENSE](LICENSE).