Adopt scripts-to-rule-them-all: script/ entrypoints, Makefile shims, policy update #23

Merged
sneak merged 3 commits from scripts-to-rule-them-all into main 2026-07-06 23:53:05 +02:00
16 changed files with 156 additions and 56 deletions
Showing only changes of commit d0958fce80 - Show all commits

View File

@@ -1,9 +1,15 @@
.PHONY: test lint fmt fmt-check check docker hooks .PHONY: bootstrap setup test lint fmt fmt-check check docker hooks
# Makefile targets are thin shims; the implementations live in script/ # Makefile targets are thin shims; the implementations live in script/
# per the scripts-to-rule-them-all pattern (see the Entrypoints section # per the scripts-to-rule-them-all pattern (see the Entrypoints section
# of README.md). # of README.md).
bootstrap:
@script/bootstrap
setup:
@script/setup
test: test:
@script/test @script/test

View File

@@ -107,9 +107,13 @@ needed in your projects.
This repository adheres to the This repository adheres to the
[Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all) [Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all)
standard: normalized scripts in `script/` are the entrypoints for the standard: normalized scripts in `script/` are the entrypoints for the
development workflow, and the Makefile targets are thin shims that call them. We development workflow, and the Makefile targets are thin shims that call them.
provide: The scripts are POSIX sh (not bash) so they run in minimal containers such as
alpine. We provide:
- `script/bootstrap` — install all dependencies (yarn install)
- `script/setup` — set up the repo for development after a fresh clone: runs
`script/bootstrap`, then `script/install-precommit`
- `script/projectname` — output the project name (our own extension); used by - `script/projectname` — output the project name (our own extension); used by
`script/docker` for the image tag `script/docker` for the image tag
- `script/test` — run the test suite (no tests defined here) - `script/test` — run the test suite (no tests defined here)

View File

@@ -71,6 +71,11 @@ Implementations live in `script/` (scripts-to-rule-them-all); Makefile targets
are thin shims calling them. Model scripts: are thin shims calling them. Model scripts:
`https://git.eeqj.de/sneak/prompts/raw/branch/main/script/<name>` `https://git.eeqj.de/sneak/prompts/raw/branch/main/script/<name>`
- [ ] scripts are POSIX sh (`#!/bin/sh`, `set -eu`, no bashisms) so they run on
alpine images without bash
- [ ] `script/bootstrap` / `make bootstrap` — installs all dependencies
- [ ] `script/setup` / `make setup` — readies a fresh clone: runs `bootstrap`,
then `install-precommit`, plus repo-specific init
- [ ] `script/test` / `make test` — runs real tests, not a no-op (30-second - [ ] `script/test` / `make test` — runs real tests, not a no-op (30-second
timeout) timeout)
- [ ] `script/lint` / `make lint` — runs linter - [ ] `script/lint` / `make lint` — runs linter

View File

@@ -34,29 +34,35 @@ style conventions are in separate documents:
every file before committing. There are zero exceptions to this rule. every file before committing. There are zero exceptions to this rule.
- Every repo with software must have a root `Makefile` with these targets: - Every repo with software must have a root `Makefile` with these targets:
`make test`, `make lint`, `make fmt` (writes), `make fmt-check` (read-only), `make bootstrap`, `make setup`, `make test`, `make lint`, `make fmt` (writes),
`make check` (runs `test`, `lint`, `fmt-check`), `make docker`, and `make fmt-check` (read-only), `make check` (runs `test`, `lint`, `fmt-check`),
`make hooks` (installs pre-commit hook). A model Makefile is at `make docker`, and `make hooks` (installs pre-commit hook). A model Makefile
`https://git.eeqj.de/sneak/prompts/raw/branch/main/Makefile`. is at `https://git.eeqj.de/sneak/prompts/raw/branch/main/Makefile`.
- Repos follow the - Repos follow the
[Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all) [Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all)
pattern: the implementation of each Makefile target lives in an executable pattern: the implementation of each Makefile target lives in an executable
script in `script/` (`script/test`, `script/lint`, `script/fmt`, script in `script/` (`script/bootstrap`, `script/setup`, `script/test`,
`script/fmt-check`, `script/check`, `script/docker`), and the Makefile targets `script/lint`, `script/fmt`, `script/fmt-check`, `script/check`,
are thin shims that call them. `script/cibuild` (a standard `script/docker`), and the Makefile targets are thin shims that call them. The
scripts-to-rule-them-all name) runs the CI build: it changes to the repo root scripts must be POSIX sh (`#!/bin/sh`, `set -eu`, no bashisms) so they run in
and runs `docker build .`; the Gitea workflow calls it. Four further scripts minimal containers (e.g. alpine images have no bash); locate the repo root
are our own extensions to the standard: `script/check` runs `script/test`, with `$(cd "$(dirname "$0")/.." && pwd -P)` and `cd` there before acting. From
`script/lint`, and `script/fmt-check`; `script/precommit` is what the git the standard's canonical set we use `bootstrap` (install all dependencies),
pre-commit hook runs, and it calls `script/check`; `script/install-precommit` `setup` (make the repo ready for development after a fresh clone: runs
installs the git pre-commit hook (the `make hooks` target shims to it); and `bootstrap`, then `install-precommit`, plus any repo-specific initialization),
`script/projectname` (literally that filename) simply outputs the project's `test`, and `cibuild`. `script/cibuild` runs the CI build: it changes to the
name. Scripts that need the name call `script/projectname` — e.g. repo root and runs `docker build .`; the Gitea workflow calls it. Four further
`script/docker` assembles its image tag from it — so those scripts stay scripts are our own extensions to the standard: `script/check` runs
byte-identical across all repos. Repo-type-specific pre-commit extras (e.g. `script/test`, `script/lint`, and `script/fmt-check`; `script/precommit` is
`go mod tidy` verification in Go repos) belong in `script/precommit`, not in what the git pre-commit hook runs, and it calls `script/check`;
the hook itself. Model scripts are at `script/install-precommit` installs the git pre-commit hook (the `make hooks`
target shims to it); and `script/projectname` (literally that filename) simply
outputs the project's name. Scripts that need the name call
`script/projectname` — e.g. `script/docker` assembles its image tag from it —
so those scripts stay byte-identical across all repos. Repo-type-specific
pre-commit extras (e.g. `go mod tidy` verification in Go repos) belong in
`script/precommit`, not in the hook itself. Model scripts are at
`https://git.eeqj.de/sneak/prompts/raw/branch/main/script/<name>`. The README `https://git.eeqj.de/sneak/prompts/raw/branch/main/script/<name>`. The README
must document the provided scripts in an **Entrypoints** section (see the must document the provided scripts in an **Entrypoints** section (see the
README requirements below). README requirements below).
@@ -380,8 +386,9 @@ style conventions are in separate documents:
- `README.md`, `.git`, `.gitignore`, `.editorconfig` - `README.md`, `.git`, `.gitignore`, `.editorconfig`
- `LICENSE`, `REPO_POLICIES.md` (copy from the `prompts` repo) - `LICENSE`, `REPO_POLICIES.md` (copy from the `prompts` repo)
- `Makefile` - `Makefile`
- `script/` entrypoints (`projectname`, `test`, `lint`, `fmt`, `fmt-check`, - `script/` entrypoints (`bootstrap`, `setup`, `projectname`, `test`,
`check`, `docker`, `cibuild`, `precommit`, `install-precommit`) `lint`, `fmt`, `fmt-check`, `check`, `docker`, `cibuild`, `precommit`,
`install-precommit`)
- `Dockerfile`, `.dockerignore` - `Dockerfile`, `.dockerignore`
- `.gitea/workflows/check.yml` - `.gitea/workflows/check.yml`
- Go: `go.mod`, `go.sum`, `.golangci.yml` - Go: `go.mod`, `go.sum`, `.golangci.yml`

65
script/bootstrap Executable file
View File

@@ -0,0 +1,65 @@
#!/bin/sh
# script/bootstrap: install all dependencies needed to build and develop
# this repo. Idempotent: every install is guarded by a check so already
# installed tools are skipped. Installs base tooling with nix, apt, or
# brew (detected in that order); assumes nothing is present.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
PKGMGR=""
SUDO=""
detect_pkgmgr() {
if command -v nix-env >/dev/null 2>&1; then
PKGMGR="nix"
elif command -v apt-get >/dev/null 2>&1; then
PKGMGR="apt"
elif command -v brew >/dev/null 2>&1; then
PKGMGR="brew"
else
echo "bootstrap: no supported package manager (nix, apt, brew)" >&2
exit 1
fi
if [ "$PKGMGR" = "apt" ]; then
export DEBIAN_FRONTEND=noninteractive
if [ "$(id -u)" != "0" ]; then
SUDO="sudo"
fi
fi
}
# pkg_install <nix-attr> <apt-pkg> <brew-formula>
pkg_install() {
case "$PKGMGR" in
nix) nix-env -iA "nixpkgs.$1" ;;
apt) $SUDO env DEBIAN_FRONTEND=noninteractive apt-get install -y "$2" ;;
brew) brew install "$3" ;;
esac
}
missing() {
! command -v "$1" >/dev/null 2>&1
}
main() {
cd "$ROOT"
detect_pkgmgr
if missing git; then pkg_install git git git; fi
if missing make; then pkg_install gnumake make make; fi
if missing node; then pkg_install nodejs nodejs node; fi
if missing yarn; then
# corepack ships with node >= 16 and provides yarn
if command -v corepack >/dev/null 2>&1; then
corepack enable
else
pkg_install yarn yarnpkg yarn
fi
fi
yarn install --frozen-lockfile
echo "bootstrap complete"
}
main "$@"

View File

@@ -1,9 +1,9 @@
#!/usr/bin/env bash #!/bin/sh
# script/check: run all checks (test, lint, fmt-check). Our own # script/check: run all checks (test, lint, fmt-check). Our own
# extension to scripts-to-rule-them-all. Must not modify any files. # extension to scripts-to-rule-them-all. Must not modify any files.
set -euo pipefail set -eu
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
main() { main() {
"$SCRIPT_DIR/test" "$SCRIPT_DIR/test"

View File

@@ -1,9 +1,9 @@
#!/usr/bin/env bash #!/bin/sh
# script/cibuild: run the CI build. The Dockerfile runs script/check, so # script/cibuild: run the CI build. The Dockerfile runs script/check, so
# a successful build implies all checks pass. # a successful build implies all checks pass.
set -euo pipefail set -eu
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() { main() {
cd "$ROOT" cd "$ROOT"

View File

@@ -1,9 +1,9 @@
#!/usr/bin/env bash #!/bin/sh
# script/docker: build the Docker image tagged with the project name. # script/docker: build the Docker image tagged with the project name.
# Identical in all repos; the tag comes from script/projectname. # Identical in all repos; the tag comes from script/projectname.
set -euo pipefail set -eu
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)" ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)"
main() { main() {

View File

@@ -1,8 +1,8 @@
#!/usr/bin/env bash #!/bin/sh
# script/fmt: format all files (writes). # script/fmt: format all files (writes).
set -euo pipefail set -eu
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() { main() {
cd "$ROOT" cd "$ROOT"

View File

@@ -1,8 +1,8 @@
#!/usr/bin/env bash #!/bin/sh
# script/fmt-check: check formatting (read-only). # script/fmt-check: check formatting (read-only).
set -euo pipefail set -eu
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() { main() {
cd "$ROOT" cd "$ROOT"

View File

@@ -1,15 +1,15 @@
#!/usr/bin/env bash #!/bin/sh
# script/install-precommit: install the git pre-commit hook that runs # script/install-precommit: install the git pre-commit hook that runs
# script/precommit. Our own extension to scripts-to-rule-them-all. # script/precommit. Our own extension to scripts-to-rule-them-all.
set -euo pipefail set -eu
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() { main() {
cd "$ROOT" cd "$ROOT"
local hook=".git/hooks/pre-commit" hook=".git/hooks/pre-commit"
printf '#!/bin/sh\nset -e\nscript/precommit\n' > "$hook" printf '#!/bin/sh\nset -e\nscript/precommit\n' > .git/hooks/pre-commit
chmod +x "$hook" chmod +x .git/hooks/pre-commit
echo "pre-commit hook installed: runs script/precommit" echo "pre-commit hook installed: runs script/precommit"
} }

View File

@@ -1,8 +1,8 @@
#!/usr/bin/env bash #!/bin/sh
# script/lint: run the linter. # script/lint: run the linter.
set -euo pipefail set -eu
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() { main() {
cd "$ROOT" cd "$ROOT"

View File

@@ -1,9 +1,9 @@
#!/usr/bin/env bash #!/bin/sh
# script/precommit: run by the git pre-commit hook; fails the commit if # script/precommit: run by the git pre-commit hook; fails the commit if
# checks fail. Our own extension to scripts-to-rule-them-all. # checks fail. Our own extension to scripts-to-rule-them-all.
set -euo pipefail set -eu
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
main() { main() {
"$SCRIPT_DIR/check" "$SCRIPT_DIR/check"

View File

@@ -1,9 +1,9 @@
#!/usr/bin/env bash #!/bin/sh
# script/projectname: output the name of this project. Our own # script/projectname: output the name of this project. Our own
# extension to scripts-to-rule-them-all. Other scripts that need the # extension to scripts-to-rule-them-all. Other scripts that need the
# name (e.g. script/docker) call this, so they can stay identical # name (e.g. script/docker) call this, so they can stay identical
# across all repos. # across all repos.
set -euo pipefail set -eu
main() { main() {
echo "prompts" echo "prompts"

13
script/setup Executable file
View File

@@ -0,0 +1,13 @@
#!/bin/sh
# script/setup: set up the repo for development after a fresh clone:
# installs dependencies and the git pre-commit hook.
set -eu
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
main() {
"$SCRIPT_DIR/bootstrap"
"$SCRIPT_DIR/install-precommit"
}
main "$@"

View File

@@ -1,8 +1,8 @@
#!/usr/bin/env bash #!/bin/sh
# script/test: run the test suite. # script/test: run the test suite.
set -euo pipefail set -eu
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() { main() {
cd "$ROOT" cd "$ROOT"