Adopt the scripts-to-rule-them-all layout and the canonical Docker gates
check / check (push) Waiting to run
check / check (push) Waiting to run
script/ holds the entrypoints and the Makefile targets shim to them. Linting and testing are phases of the Dockerfile, built alone by script/lint and script/test with a fresh CACHEBUST value and cache mounts for Go's build cache and golangci-lint's; the build stage depends on both, and the runtime stage ships the binary and the source without .git. Formatting stays on the host in script/fmt and script/fmt-check (gofmt and the prettier pinned in yarn.lock). script/cibuild bootstraps, runs script/check and builds the tagged image, and the Gitea workflow calls it. script/precommit refuses a commit whose go.mod or go.sum is not tidy. cgo stays off, so the test phase runs on the alpine Go image without -race; that is the one departure from the canonical Go Dockerfile. Re-vendored from the prompts repository's next branch: REPO_POLICIES.md (2026-11-04), .golangci.yml (byte-identical, so the local exhaustruct_v5 addition is gone now that canonical carries it), .gitea/workflows/check.yml, .editorconfig, .gitignore and .dockerignore, the last two followed by this repository's own entries. AGENTS.md holds the repository-specific guidance for agents. The README gains the Entrypoints section and describes the new gates.
This commit is contained in:
+79
-8
@@ -1,9 +1,80 @@
|
||||
.git
|
||||
node_modules
|
||||
.DS_Store
|
||||
# .dockerignore does NOT use .gitignore semantics. Docker matches with
|
||||
# moby/patternmatcher: filepath.Match plus `**`, so `*` does not cross
|
||||
# `/` and an unprefixed pattern is anchored at the context root. Every
|
||||
# depth-independent pattern therefore needs `**/`, or `config/.env` and
|
||||
# `certs/server.key` still ship while this file reads as solved. Only
|
||||
# genuinely root-anchored entries go unprefixed. Never transplant these
|
||||
# into .gitignore, where `**/` is wrong.
|
||||
#
|
||||
# Matching is case-sensitive, so secrets use character ranges rather
|
||||
# than an ALL-CAPS twin, which would still miss `Server.Key`.
|
||||
#
|
||||
# Extend with this repo's own host-built artifacts, written anchored:
|
||||
# `/myapp`, never `**/myapp`, which also matches `cmd/myapp/` and
|
||||
# deletes the package directory from the context.
|
||||
|
||||
# .git is sent without its config. Without a VERSION build argument the
|
||||
# stage that compiles runs `git describe --tags --always` on .git, which
|
||||
# does not need .git/config; that file can hold a credential, such as a
|
||||
# password in a remote URL or the token the CI checkout step stores there.
|
||||
# Each submodule keeps a config with the same exposure in its git directory
|
||||
# under .git/modules/, nested again for a submodule's own submodules, or in
|
||||
# its own .git directory when it keeps one.
|
||||
# KNOWN GAP: a submodule whose name has a `config` segment (`config`,
|
||||
# `deploy/config`, `config/lib`) loses its whole git directory, because
|
||||
# `**/.git/modules/**/config` also matches that segment's directory
|
||||
# under .git/modules/. Go's version stamping then fails the build;
|
||||
# nothing leaks. Name such a submodule without that segment:
|
||||
# `git submodule add --name`.
|
||||
**/.git/config
|
||||
**/.git/modules/**/config
|
||||
|
||||
# Agent scratch: one full checkout of the repo per in-flight agent.
|
||||
# Anchored because it occurs once where agents run at the repo root.
|
||||
# KNOWN GAP: a repo running agents in subdirectories still ships
|
||||
# `services/api/.claude/` and must add its own anchored entry.
|
||||
.claude
|
||||
|
||||
# Environment files. `*.env` covers bare `.env` and the `prod.env`
|
||||
# convention. Re-include a committed template with a negation if the
|
||||
# build needs one: `!docs/example.env`.
|
||||
**/*.[eE][nN][vV]
|
||||
**/.[eE][nN][vV].*
|
||||
**/.[eE][nN][vV][rR][cC]
|
||||
|
||||
# Private keys and the bundles carrying them. Public certificates
|
||||
# (*.crt, *.cer) are deliberately absent: they are legitimate inputs.
|
||||
**/*.[pP][eE][mM]
|
||||
**/*.[kK][eE][yY]
|
||||
**/*.[pP]12
|
||||
**/*.[pP][fF][xX]
|
||||
**/[iI][dD]_[rR][sS][aA]
|
||||
**/[iI][dD]_[dD][sS][aA]
|
||||
**/[iI][dD]_[eE][cC][dD][sS][aA]
|
||||
**/[iI][dD]_[eE][cC][dD][sS][aA]_[sS][kK]
|
||||
**/[iI][dD]_[eE][dD]25519
|
||||
**/[iI][dD]_[eE][dD]25519_[sS][kK]
|
||||
|
||||
# Dependencies: restored inside the image, never copied in.
|
||||
**/node_modules
|
||||
|
||||
# OS metadata.
|
||||
**/.DS_Store
|
||||
**/Thumbs.db
|
||||
|
||||
# Editor state: never a build input, and it churns COPY.
|
||||
**/*.swp
|
||||
**/*.swo
|
||||
**/*~
|
||||
**/*.bak
|
||||
**/.idea
|
||||
**/.vscode
|
||||
**/*.sublime-*
|
||||
|
||||
# This repository's own host-built artifacts, anchored.
|
||||
/sutil
|
||||
*.test
|
||||
*.out
|
||||
*.log
|
||||
*.prof
|
||||
coverage.*
|
||||
**/*.test
|
||||
**/*.out
|
||||
**/*.log
|
||||
**/*.prof
|
||||
**/coverage.*
|
||||
|
||||
@@ -10,3 +10,9 @@ insert_final_newline = true
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
|
||||
[*.go]
|
||||
indent_style = tab
|
||||
|
||||
# This repository's own sections, such as one for another language it
|
||||
# uses, go below this comment, and a re-vendor keeps them.
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
name: check
|
||||
on: [push]
|
||||
# Free the shared runner: a new push cancels only the same branch's older run.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
# Free the shared runner from a hung build.
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
# actions/checkout v4.2.2, 2026-02-22
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
- run: docker build .
|
||||
# script/cibuild needs no token, so none is left in .git/config.
|
||||
with:
|
||||
persist-credentials: false
|
||||
# All history and tags, so git describe finds the version tag.
|
||||
fetch-depth: 0
|
||||
- run: script/cibuild
|
||||
|
||||
+39
-7
@@ -11,17 +11,49 @@ Thumbs.db
|
||||
.vscode/
|
||||
*.sublime-*
|
||||
|
||||
# Node (prettier for markdown formatting)
|
||||
# Agent scratch (worktrees of this repo, created and destroyed by
|
||||
# in-flight tooling). Unanchored: .gitignore patterns already match at
|
||||
# every depth, so no prefix is wanted here. This is not a .dockerignore
|
||||
# entry and must not be given a `**/` prefix on the way into one.
|
||||
.claude/
|
||||
|
||||
# Node
|
||||
node_modules/
|
||||
|
||||
# Environment / secrets
|
||||
.env
|
||||
.env.*
|
||||
*.pem
|
||||
*.key
|
||||
# Secrets. Unanchored like every entry above, so each matches at every
|
||||
# depth. Matching is case-sensitive on Linux, so names use character
|
||||
# ranges rather than a lowercase form that misses `Server.Key`.
|
||||
|
||||
# Go build and test artifacts
|
||||
# Environment files. `*.env` covers bare `.env` and the `prod.env`
|
||||
# convention. Only the templates `example.env` and `sample.env` are
|
||||
# re-included below. A repository that commits any other template adds
|
||||
# its own negation at the end of this file, for example `!.env.example`.
|
||||
*.[eE][nN][vV]
|
||||
.[eE][nN][vV].*
|
||||
.[eE][nN][vV][rR][cC]
|
||||
!example.env
|
||||
!sample.env
|
||||
|
||||
# Private keys and the bundles carrying them.
|
||||
*.[pP][eE][mM]
|
||||
*.[kK][eE][yY]
|
||||
*.[pP]12
|
||||
*.[pP][fF][xX]
|
||||
[iI][dD]_[rR][sS][aA]
|
||||
[iI][dD]_[dD][sS][aA]
|
||||
[iI][dD]_[eE][cC][dD][sS][aA]
|
||||
[iI][dD]_[eE][cC][dD][sS][aA]_[sS][kK]
|
||||
[iI][dD]_[eE][dD]25519
|
||||
[iI][dD]_[eE][dD]25519_[sS][kK]
|
||||
|
||||
# This repository's own entries, such as its build outputs, go below
|
||||
# this comment, and a re-vendor keeps them. Anchor a binary built at the
|
||||
# root: `/myapp`, never `myapp`, which also ignores `cmd/myapp/`.
|
||||
|
||||
# The sutil binary that `make build` writes at the root.
|
||||
/sutil
|
||||
|
||||
# Go test and profiling outputs.
|
||||
*.test
|
||||
*.out
|
||||
*.log
|
||||
|
||||
+33
-10
@@ -3,6 +3,9 @@ version: "2"
|
||||
# Config schema uses the golangci-lint v2 layout (settings live under
|
||||
# linters.settings, not top-level linters-settings) so that the
|
||||
# thresholds below are actually applied by golangci-lint >= v2.
|
||||
#
|
||||
# This file has no formatters section, so golangci-lint run does not run
|
||||
# gofmt. A repository's script/fmt-check runs it, on the host.
|
||||
|
||||
run:
|
||||
timeout: 5m
|
||||
@@ -16,13 +19,13 @@ linters:
|
||||
- gomodguard_v2
|
||||
disable:
|
||||
# Genuinely incompatible with project patterns
|
||||
- exhaustruct # Requires all struct fields
|
||||
- exhaustruct_v5 # Same linter, newer major version (since v2.13.0)
|
||||
- exhaustruct_v5 # Requires all struct fields
|
||||
- godot # Requires comments to end with periods
|
||||
- wrapcheck # Too verbose for internal packages
|
||||
- varnamelen # Short names like db, id are idiomatic Go
|
||||
# Deprecated: the warning is attached to the old name, so it is
|
||||
# silenced by disabling that name, not by enabling the successor.
|
||||
- exhaustruct # Deprecated, replaced by exhaustruct_v5
|
||||
- wsl # Deprecated, replaced by wsl_v5
|
||||
- gomodguard # Deprecated, replaced by gomodguard_v2
|
||||
settings:
|
||||
@@ -40,7 +43,10 @@ linters:
|
||||
# test-support package exists to hand a test privileges the program
|
||||
# itself must never have, so a file that is not a test must not import
|
||||
# one. Test files, and the files inside a package whose directory name
|
||||
# ends in `test`, are where that code belongs, and are exempt.
|
||||
# ends in `test`, are where that code belongs, and are exempt. A package
|
||||
# in a subdirectory of that directory is not exempt. `${base-path}` is
|
||||
# the directory holding this file, so the name of the directory a
|
||||
# repository is cloned into does not count.
|
||||
#
|
||||
# The deny list below is the one part of this file a repository is
|
||||
# expected to extend, and the only part it may. depguard matches an
|
||||
@@ -55,24 +61,25 @@ linters:
|
||||
files:
|
||||
- "$all"
|
||||
- "!$test"
|
||||
- "!**/*test/**"
|
||||
- "!${base-path}**/*test/*"
|
||||
deny:
|
||||
- pkg: net/http/httptest
|
||||
desc: >-
|
||||
Test-support code belongs in test files and in packages whose
|
||||
directory name ends in test, not in the shipped binary.
|
||||
# Only decisions already recorded in the Go package defaults are
|
||||
# listed here. Every entry matches the module path exactly.
|
||||
# Each entry names a module not to use and what to use instead. Every
|
||||
# entry matches the module path exactly.
|
||||
gomodguard_v2:
|
||||
blocked:
|
||||
- module: github.com/rs/zerolog
|
||||
recommendations:
|
||||
- log/slog
|
||||
reason: "Structured logging is stdlib log/slog."
|
||||
# One entry per pre-fork module path, because the later releases
|
||||
# are separate paths. A prefix match would be shorter but would
|
||||
# also reach github.com/go-redis/redismock, the test double for
|
||||
# the successor these entries recommend.
|
||||
# These entries cover only the github.com/go-redis/redis module paths,
|
||||
# one entry per path, because the later releases are separate paths.
|
||||
# A prefix match would be shorter but would also reach
|
||||
# github.com/go-redis/redismock/v9, the test double for the successor
|
||||
# these entries recommend.
|
||||
- module: github.com/go-redis/redis
|
||||
recommendations:
|
||||
- github.com/redis/go-redis/v9
|
||||
@@ -85,6 +92,10 @@ linters:
|
||||
recommendations:
|
||||
- github.com/redis/go-redis/v9
|
||||
reason: "Pre-fork module; use the maintained go-redis v9."
|
||||
- module: github.com/go-redis/redis/v9
|
||||
recommendations:
|
||||
- github.com/redis/go-redis/v9
|
||||
reason: "Pre-fork module; use the maintained go-redis v9."
|
||||
- module: github.com/sergi/go-diff
|
||||
recommendations:
|
||||
- github.com/aymanbagabas/go-udiff
|
||||
@@ -93,7 +104,19 @@ linters:
|
||||
recommendations:
|
||||
- github.com/aymanbagabas/go-udiff
|
||||
reason: "Unmaintained fork; use go-udiff."
|
||||
exclusions:
|
||||
rules:
|
||||
# homoicon keeps a local copy of sobek, its JavaScript engine, as sneak
|
||||
# decided in
|
||||
# https://git.eeqj.de/sneak/homoicon/issues/1759#issuecomment-153261.
|
||||
# This is an exclusion because gomoddirectives does not check its
|
||||
# replace-allow-list for a replace that points at a local path.
|
||||
- path: ^go\.mod$
|
||||
linters:
|
||||
- gomoddirectives
|
||||
text: '^local replacement are not allowed: github\.com/grafana/sobek$'
|
||||
|
||||
issues:
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
||||
uniq-by-line: false
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# AGENTS.md
|
||||
|
||||
Guidance for coding agents working in this repository. The repository policies
|
||||
are in `REPO_POLICIES.md`, vendored from the `prompts` repository; read that
|
||||
first. Everything below is specific to this repository, and the README's Design
|
||||
section explains each point.
|
||||
|
||||
- `pkg/` holds only packages designed to be imported by other programs. Anything
|
||||
that exists to support `sutil` lives under `internal/`.
|
||||
- `sutil` is not a command-line front end for the packages in `pkg/`. It holds
|
||||
only the utilities the author wants available everywhere; a command is added
|
||||
because a tool is wanted, not because a package exists.
|
||||
- `pkg/cbk` derives crypto_box keypairs from BIP39 mnemonics with BIP85 and does
|
||||
nothing else. BIP39 passphrases are deliberately unsupported.
|
||||
- cgo stays off: builds are static, and the test phase runs without `-race`.
|
||||
- Version tags are created by the author alone, when the author decides. Do not
|
||||
propose, schedule, or create tags.
|
||||
- `.golangci.yml` is byte-identical to the canonical copy and is not edited
|
||||
here; changes go to the `prompts` repository.
|
||||
+75
-36
@@ -1,56 +1,95 @@
|
||||
# The checks behind `make check` are split across independent stages so that
|
||||
# formatting and lint failures surface in seconds, before any compilation:
|
||||
# In every stage ARG CACHEBUST comes right after the steps that install
|
||||
# system packages and download Go's modules, or right after FROM in a
|
||||
# stage with neither, and every step that lints, tests or compiles
|
||||
# comes after it. script/ passes a new value on every build, which
|
||||
# reruns every RUN step after it. Docker keeps the layers above it
|
||||
# until the base image, their own lines or go.mod and go.sum change.
|
||||
# The steps that compile mount Go's build cache, and golangci-lint run
|
||||
# also mounts golangci-lint's; both survive from build to build.
|
||||
#
|
||||
# lint - gofmt and golangci-lint (make fmt-check-go, make lint)
|
||||
# docs - prettier check of all markdown (make fmt-check-md)
|
||||
# builder - tests (make test) and the release build
|
||||
#
|
||||
# The builder stage declares explicit dependencies on lint and docs so
|
||||
# BuildKit cannot finish the build unless every check passed.
|
||||
# This repository keeps cgo off, so its test phase departs from the
|
||||
# canonical Go Dockerfile in REPO_POLICIES.md in one way: it runs on the
|
||||
# alpine Go image without -race, which needs a C toolchain. Everything
|
||||
# else follows the canonical file.
|
||||
|
||||
# Lint stage: fast feedback on Go formatting and lint issues
|
||||
# golangci/golangci-lint:v2.15.0, 2026-10-16 (keep in sync with Makefile)
|
||||
# Lint phase
|
||||
# golangci/golangci-lint:v2.15.0, 2026-10-16
|
||||
FROM golangci/golangci-lint@sha256:fbfcfd12a6f8d7f1b97b3362ca80fc2a244d46863148c73cb88e7793af96c481 AS lint
|
||||
WORKDIR /src
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
ARG CACHEBUST
|
||||
COPY . .
|
||||
RUN make fmt-check-go
|
||||
RUN make lint
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/root/.cache/golangci-lint \
|
||||
golangci-lint run --config .golangci.yml ./...
|
||||
|
||||
# Docs stage: markdown formatting check with the pinned prettier
|
||||
# node:24-alpine, 2026-10-28
|
||||
FROM node@sha256:8a32c8cf649a49915a66e81e7a609223c708d0adabf288ee3f4077d765542f85 AS docs
|
||||
RUN apk add --no-cache make
|
||||
WORKDIR /src
|
||||
COPY package.json yarn.lock ./
|
||||
RUN yarn install --frozen-lockfile
|
||||
COPY . .
|
||||
RUN make fmt-check-md
|
||||
|
||||
# Build stage
|
||||
# Test phase. -count=1 because the mounted cache also holds test
|
||||
# results, which go test would otherwise replay. A first run that timed
|
||||
# out gets no rerun, because a rerun would only wait for the same hang.
|
||||
# golang:1.26-alpine, 2026-10-27
|
||||
FROM golang@sha256:a2012b4a1d1f9f7e9f44e0d39b805a901e856b1dd80b4e6ee4338db547050308 AS builder
|
||||
RUN apk add --no-cache make
|
||||
FROM golang@sha256:a2012b4a1d1f9f7e9f44e0d39b805a901e856b1dd80b4e6ee4338db547050308 AS test
|
||||
ENV CGO_ENABLED=0
|
||||
WORKDIR /src
|
||||
|
||||
# Force BuildKit to run the lint and docs stages before proceeding
|
||||
COPY --from=lint /src/go.sum /dev/null
|
||||
COPY --from=docs /src/package.json /dev/null
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
ARG CACHEBUST
|
||||
COPY . .
|
||||
RUN make test
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
status=0; \
|
||||
output=$(go test -count=1 -timeout 90s -cover ./... 2>&1) || status=$?; \
|
||||
printf '%s\n' "$output"; \
|
||||
test "$status" -eq 0 && exit 0; \
|
||||
case "$output" in \
|
||||
*"panic: test timed out"*|*"*** Test killed"*) exit 1 ;; \
|
||||
esac; \
|
||||
echo "--- Rerunning with -v for details ---"; \
|
||||
go test -count=1 -timeout 90s -v ./...; \
|
||||
exit 1
|
||||
|
||||
ARG VERSION=dev
|
||||
RUN CGO_ENABLED=0 go build -trimpath \
|
||||
-ldflags="-s -w -X main.Version=${VERSION}" \
|
||||
-o /sutil ./cmd/sutil
|
||||
# Build stage. Nothing is wanted from either phase above; the copies
|
||||
# are what make BuildKit build them first, so nothing after them can
|
||||
# run unless lint and test passed.
|
||||
# golang:1.26-alpine, 2026-10-27
|
||||
FROM golang@sha256:a2012b4a1d1f9f7e9f44e0d39b805a901e856b1dd80b4e6ee4338db547050308 AS builder
|
||||
# git comes from the base image's own distribution package index, which
|
||||
# is trusted, so it needs no version and no hash.
|
||||
RUN apk add --no-cache git
|
||||
WORKDIR /src
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
ARG CACHEBUST
|
||||
COPY --from=lint /src/go.sum /dev/null
|
||||
COPY --from=test /src/go.sum /dev/null
|
||||
# A tar-stream context keeps the sender's file owners, which git refuses.
|
||||
RUN git config --system --add safe.directory /src
|
||||
COPY . .
|
||||
|
||||
# Runtime stage: the binary plus the full source it was built from
|
||||
# The VERSION build arg when one is given, otherwise
|
||||
# `git describe --tags --always` on the .git in the build context. With
|
||||
# .git present, a version that is still empty, dev or unknown fails the
|
||||
# build: git is missing or could not read the checkout.
|
||||
ARG VERSION
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
VERSION="${VERSION:-$(git describe --tags --always)}"; \
|
||||
if [ -e .git ]; then \
|
||||
case "$VERSION" in ""|dev|unknown) \
|
||||
echo "version is '$VERSION' although .git is present" >&2; \
|
||||
exit 1 ;; \
|
||||
esac; \
|
||||
fi; \
|
||||
CGO_ENABLED=0 go build -trimpath \
|
||||
-ldflags="-s -w -X main.Version=${VERSION}" \
|
||||
-o /sutil ./cmd/sutil
|
||||
# The runtime stage ships the source; the repository history is not
|
||||
# part of it.
|
||||
RUN rm -rf /src/.git
|
||||
|
||||
# Runtime stage, and the last one: the binary plus the source it was
|
||||
# built from.
|
||||
# alpine:3.24, 2026-10-23
|
||||
FROM alpine@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a
|
||||
ARG CACHEBUST
|
||||
COPY --from=builder /sutil /usr/local/bin/sutil
|
||||
COPY --from=builder /src /usr/local/src/x
|
||||
ENTRYPOINT ["sutil"]
|
||||
|
||||
@@ -1,30 +1,45 @@
|
||||
# sneak.berlin/go/x
|
||||
#
|
||||
# This Makefile is the single source of truth for how this repo is built,
|
||||
# tested, linted, and formatted. Always use these targets rather than
|
||||
# invoking the underlying tools directly.
|
||||
.PHONY: bootstrap setup test lint fmt fmt-check check docker hooks \
|
||||
build install tidy clean
|
||||
|
||||
.PHONY: all build install test lint fmt fmt-go fmt-md fmt-check \
|
||||
fmt-check-go fmt-check-md check docker hooks tidy setup clean
|
||||
# Makefile targets are thin shims; the implementations live in script/
|
||||
# per the scripts-to-rule-them-all pattern (see the Entrypoints section
|
||||
# of README.md). The targets after the shims are this repo's own.
|
||||
|
||||
bootstrap:
|
||||
@script/bootstrap
|
||||
|
||||
setup:
|
||||
@script/setup
|
||||
|
||||
test:
|
||||
@script/test
|
||||
|
||||
lint:
|
||||
@script/lint
|
||||
|
||||
fmt:
|
||||
@script/fmt
|
||||
|
||||
fmt-check:
|
||||
@script/fmt-check
|
||||
|
||||
check:
|
||||
@script/check
|
||||
|
||||
docker:
|
||||
@script/docker
|
||||
|
||||
hooks:
|
||||
@script/install-precommit
|
||||
|
||||
# This repository's own targets.
|
||||
|
||||
BINARY := sutil
|
||||
VERSION := $(shell git describe --always --dirty 2>/dev/null || echo dev)
|
||||
VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo unknown)
|
||||
GOLDFLAGS := -X main.Version=$(VERSION)
|
||||
GO_DIRS := cmd internal pkg
|
||||
|
||||
# golangci/golangci-lint:v2.15.0, 2026-10-16 (keep in sync with Dockerfile)
|
||||
GOLANGCI_LINT_IMAGE := golangci/golangci-lint@sha256:fbfcfd12a6f8d7f1b97b3362ca80fc2a244d46863148c73cb88e7793af96c481
|
||||
|
||||
# flags are repeated here (also in .prettierrc) so this Makefile works
|
||||
# standalone when copied as a template
|
||||
PRETTIER := yarn run --silent prettier
|
||||
PRETTIER_FLAGS := --tab-width 4 --prose-wrap always
|
||||
MARKDOWN_GLOB := '**/*.md'
|
||||
|
||||
all: build
|
||||
|
||||
# cgo stays off everywhere: builds are static and the tests do not use
|
||||
# -race, which would require a C toolchain.
|
||||
# cgo stays off everywhere: builds are static, and the test phase of the
|
||||
# Dockerfile runs without -race, which would need a C toolchain.
|
||||
export CGO_ENABLED := 0
|
||||
|
||||
# Build the sutil binary in the repo root with the git version embedded.
|
||||
@@ -35,81 +50,9 @@ build:
|
||||
install:
|
||||
go install -trimpath -ldflags "$(GOLDFLAGS)" ./cmd/$(BINARY)
|
||||
|
||||
# Run the tests quietly; on failure rerun verbosely for diagnostics and
|
||||
# still fail.
|
||||
test:
|
||||
@go test -timeout 30s -cover ./... || \
|
||||
{ echo "--- Rerunning with -v for details ---"; \
|
||||
go test -timeout 30s -v ./...; exit 1; }
|
||||
|
||||
# Lint with the pinned golangci-lint image when docker is available so local
|
||||
# results match CI exactly. Inside the Docker build (where there is no
|
||||
# docker) the image's own golangci-lint binary is used. The host module
|
||||
# cache is shared read-only; the linter and build caches live in named
|
||||
# volumes so repeated runs are fast.
|
||||
#
|
||||
# .golangci.yml is verified against the linter's schema first: golangci-lint
|
||||
# otherwise ignores keys it does not know and silently runs with defaults.
|
||||
LINT_COMMAND := golangci-lint config verify && golangci-lint run ./...
|
||||
|
||||
lint:
|
||||
@if command -v docker >/dev/null 2>&1; then \
|
||||
docker run --rm \
|
||||
-v "$(CURDIR)":/src \
|
||||
-v "$$(go env GOMODCACHE)":/go/pkg/mod:ro \
|
||||
-v sutil-golangci-lint-cache:/root/.cache/golangci-lint \
|
||||
-v sutil-go-build-cache:/root/.cache/go-build \
|
||||
-w /src $(GOLANGCI_LINT_IMAGE) sh -c '$(LINT_COMMAND)'; \
|
||||
else \
|
||||
$(LINT_COMMAND); \
|
||||
fi
|
||||
|
||||
# Format everything (writes files).
|
||||
fmt: fmt-go fmt-md
|
||||
|
||||
fmt-go:
|
||||
gofmt -w $(GO_DIRS)
|
||||
|
||||
fmt-md:
|
||||
$(PRETTIER) --write $(MARKDOWN_GLOB) $(PRETTIER_FLAGS)
|
||||
|
||||
# Check formatting without writing anything.
|
||||
fmt-check: fmt-check-go fmt-check-md
|
||||
|
||||
fmt-check-go:
|
||||
@unformatted="$$(gofmt -l $(GO_DIRS))"; \
|
||||
if [ -n "$$unformatted" ]; then \
|
||||
echo "gofmt: the following files need formatting:"; \
|
||||
echo "$$unformatted"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
fmt-check-md:
|
||||
$(PRETTIER) --check $(MARKDOWN_GLOB) $(PRETTIER_FLAGS)
|
||||
|
||||
# Everything CI gates on. Must not modify any files.
|
||||
check: test lint fmt-check
|
||||
|
||||
# Build the Docker image; the Dockerfile runs the same checks as `make check`.
|
||||
docker:
|
||||
docker build -t $(BINARY) .
|
||||
|
||||
# Install the pre-commit hook.
|
||||
hooks:
|
||||
@printf '#!/bin/sh\nset -e\n' > .git/hooks/pre-commit
|
||||
@if [ -f go.mod ]; then \
|
||||
printf 'go mod tidy\ngo fmt ./...\ngit diff --exit-code -- go.mod go.sum || { echo "go mod tidy changed files; please stage and retry"; exit 1; }\n' >> .git/hooks/pre-commit; \
|
||||
fi
|
||||
@printf 'make check\n' >> .git/hooks/pre-commit
|
||||
@chmod +x .git/hooks/pre-commit
|
||||
|
||||
# Tidy go.mod and go.sum.
|
||||
# Tidy go.mod and go.sum; script/precommit refuses a commit that needs it.
|
||||
tidy:
|
||||
go mod tidy
|
||||
|
||||
# One-time developer setup: install the pinned prettier for markdown checks.
|
||||
setup:
|
||||
yarn install --frozen-lockfile
|
||||
|
||||
clean:
|
||||
rm -f $(BINARY)
|
||||
|
||||
@@ -217,38 +217,74 @@ added because a tool is wanted, not because a package exists.
|
||||
|
||||
### Build, test, and CI
|
||||
|
||||
The `Makefile` is the single entry point: `make test` (30-second timeout, quiet
|
||||
unless something fails), `make lint`, `make fmt` and `make fmt-check` (gofmt for
|
||||
Go, prettier for markdown), `make check` for all three, `make build`,
|
||||
`make install`, `make docker`, `make hooks`, `make tidy`, and `make setup` to
|
||||
install the pinned prettier.
|
||||
The gates live in the `Dockerfile`, in the canonical Go layout that
|
||||
`REPO_POLICIES.md` prescribes: a `lint` phase on the hash-pinned golangci-lint
|
||||
image and a `test` phase on the Go image. `script/lint` and `script/test` each
|
||||
build one phase alone, with a fresh `CACHEBUST` value so the check step runs on
|
||||
every build instead of being served from Docker's cache, and write no image. The
|
||||
build stage depends on both phases, so the image cannot be built unless they
|
||||
pass. Go's build cache and golangci-lint's cache are mounted into the phases and
|
||||
survive between builds. Formatting is the one check that runs on the host:
|
||||
`script/fmt-check` runs gofmt and the prettier pinned in `yarn.lock`.
|
||||
`script/check` runs all three. `script/cibuild`, which the Gitea workflow calls,
|
||||
bootstraps, runs `script/check`, and then builds the tagged image with the
|
||||
version from `git describe`.
|
||||
|
||||
`make lint` runs golangci-lint inside the same hash-pinned Docker image the CI
|
||||
build uses, so local results match CI exactly, and first verifies
|
||||
`.golangci.yml` against the linter's schema, because golangci-lint otherwise
|
||||
ignores keys it does not know and silently runs with defaults. The `Dockerfile`
|
||||
splits `make check` across stages: a lint stage and a markdown-check stage fail
|
||||
fast in seconds, and the build stage, which runs the tests and compiles `sutil`,
|
||||
depends on both. The runtime image carries the binary and the source it was
|
||||
built from. Every external reference, from base images to the GitHub Action in
|
||||
Every external reference, from base images to the GitHub Action in
|
||||
`.gitea/workflows/check.yml`, is pinned by content hash with a version and date
|
||||
comment. `.golangci.yml` is this repo's copy of the standard configuration, kept
|
||||
in the v2 layout with `exhaustruct_v5` and the deprecated `gomodguard` disabled.
|
||||
cgo stays off: the Makefile exports `CGO_ENABLED=0` for every build and test, so
|
||||
binaries are static and no C toolchain is needed anywhere.
|
||||
comment. `.golangci.yml` is byte-identical to the canonical copy in the
|
||||
`prompts` repository and is never edited here; the lint phase pins the
|
||||
golangci-lint release that the policy names. cgo stays off: the Makefile exports
|
||||
`CGO_ENABLED=0`, binaries are static, and the test phase runs on the alpine Go
|
||||
image without `-race`, which is this repository's one departure from the
|
||||
canonical `Dockerfile`. The runtime image carries the binary and the source it
|
||||
was built from.
|
||||
|
||||
Version tags are created by the author alone, when the author decides. Tooling,
|
||||
contributors, and agents working in this repository do not propose, schedule, or
|
||||
create tags.
|
||||
|
||||
## Entrypoints
|
||||
|
||||
This repository adheres to the
|
||||
[Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all)
|
||||
standard: normalized scripts in `script/` are the entrypoints for the
|
||||
development workflow, and the Makefile targets are thin shims that call them. We
|
||||
provide:
|
||||
|
||||
- `script/bootstrap`: install everything the repository needs, idempotently:
|
||||
make, git, go, and, because `yarn.lock` is present, node, yarn 1 and the
|
||||
pinned prettier.
|
||||
- `script/setup`: make a fresh clone ready for development by running
|
||||
`script/bootstrap` and then `script/install-precommit`.
|
||||
- `script/projectname`: print the project name, `x`, which `script/docker` and
|
||||
`script/cibuild` use as the image tag.
|
||||
- `script/test`: build the `test` phase of the `Dockerfile` with a new
|
||||
`CACHEBUST` value, writing no image.
|
||||
- `script/lint`: build the `lint` phase the same way.
|
||||
- `script/fmt`: gofmt and prettier, writing files.
|
||||
- `script/fmt-check`: the read-only twin of `script/fmt`.
|
||||
- `script/check`: `script/test`, `script/lint`, then `script/fmt-check`. It
|
||||
modifies nothing.
|
||||
- `script/docker`: build the image tagged `x`, passing the version from
|
||||
`git describe`.
|
||||
- `script/cibuild`: what CI runs: `script/bootstrap`, `script/check`, then the
|
||||
tagged image build.
|
||||
- `script/precommit`: run by the pre-commit hook. It refuses a commit whose
|
||||
`go.mod` or `go.sum` is not tidy, then runs `script/check`.
|
||||
- `script/install-precommit`: install that hook; `make hooks` shims to it.
|
||||
|
||||
The Makefile adds four targets of its own: `make build`, `make install`,
|
||||
`make tidy`, and `make clean`.
|
||||
|
||||
## Development
|
||||
|
||||
```sh
|
||||
make setup # once: install the pinned prettier with yarn
|
||||
make check # test + lint + fmt-check, exactly what CI gates on
|
||||
make setup # once: bootstrap and install the pre-commit hook
|
||||
make check # test, lint, fmt-check: what the hook and CI run
|
||||
make fmt # gofmt and prettier, writes files
|
||||
make hooks # install the pre-commit hook that runs make check
|
||||
make docker # the full CI build
|
||||
make build # build ./sutil on the host
|
||||
make docker # build the image tagged x
|
||||
```
|
||||
|
||||
## TODO
|
||||
|
||||
+773
-269
File diff suppressed because it is too large
Load Diff
Executable
+104
@@ -0,0 +1,104 @@
|
||||
#!/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. Base tools, go, node and yarn come from
|
||||
# nix, apt, brew, or apk (detected in that order), with no version and
|
||||
# no hash, because the distribution is trusted. It installs node, yarn
|
||||
# and the node packages only when yarn.lock is in the tree. Assumes
|
||||
# nothing is present.
|
||||
set -eu
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||
|
||||
PKGMGR=""
|
||||
SUDO=""
|
||||
APT_UPDATED=""
|
||||
|
||||
detect_pkgmgr() {
|
||||
[ -n "$PKGMGR" ] && return 0
|
||||
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"
|
||||
elif command -v apk >/dev/null 2>&1; then
|
||||
PKGMGR="apk"
|
||||
else
|
||||
echo "bootstrap: no supported package manager (nix, apt, brew, apk)" >&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> <apk-pkg>
|
||||
pkg_install() {
|
||||
detect_pkgmgr
|
||||
case "$PKGMGR" in
|
||||
nix) nix-env -iA "nixpkgs.$1" ;;
|
||||
apt)
|
||||
# Package lists may be empty (fresh images); refresh once per run.
|
||||
if [ -z "$APT_UPDATED" ]; then
|
||||
$SUDO env DEBIAN_FRONTEND=noninteractive apt-get update
|
||||
APT_UPDATED=1
|
||||
fi
|
||||
$SUDO env DEBIAN_FRONTEND=noninteractive apt-get install -y "$2"
|
||||
;;
|
||||
brew) brew install "$3" ;;
|
||||
apk) apk add --no-cache "$4" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
missing() {
|
||||
! command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Debian and Ubuntu name the yarn command yarnpkg.
|
||||
run_yarn() {
|
||||
if command -v yarn >/dev/null 2>&1; then
|
||||
yarn "$@"
|
||||
elif command -v yarnpkg >/dev/null 2>&1; then
|
||||
yarnpkg "$@"
|
||||
else
|
||||
echo "no yarn; run script/bootstrap first" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
cd "$ROOT"
|
||||
|
||||
if missing make; then pkg_install gnumake make make make; fi
|
||||
if missing git; then pkg_install git git git git; fi
|
||||
# Go on the host is for script/fmt and script/fmt-check (gofmt) and
|
||||
# for make build; the gates compile inside the Dockerfile's phases.
|
||||
if missing go; then pkg_install go golang-go go go; fi
|
||||
|
||||
# A Dockerfile stage that needs no yarn copies no yarn.lock.
|
||||
if [ -f yarn.lock ]; then
|
||||
if missing node; then pkg_install nodejs nodejs node nodejs; fi
|
||||
if missing yarn && missing yarnpkg; then
|
||||
pkg_install yarn yarnpkg yarn yarn
|
||||
fi
|
||||
|
||||
# Debian 13 and Ubuntu 26.04 package yarn 4, which refuses a yarn 1
|
||||
# yarn.lock, or rewrites it in its own format.
|
||||
yarn_version="$(run_yarn --version)"
|
||||
if [ "${yarn_version%%.*}" != "1" ]; then
|
||||
echo "bootstrap: yarn $yarn_version refuses this yarn 1 yarn.lock;" \
|
||||
"use a distribution that packages yarn 1" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
run_yarn install --frozen-lockfile
|
||||
fi
|
||||
|
||||
echo "bootstrap complete"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/bin/sh
|
||||
# script/check: run all checks (test, lint, fmt-check). Our own
|
||||
# extension to scripts-to-rule-them-all. test and lint are Docker
|
||||
# phases; fmt-check is native, because a formatter writes the working
|
||||
# tree. Must not modify any files.
|
||||
set -eu
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
|
||||
|
||||
main() {
|
||||
"$SCRIPT_DIR/test"
|
||||
"$SCRIPT_DIR/lint"
|
||||
"$SCRIPT_DIR/fmt-check"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/bin/sh
|
||||
# script/cibuild: run the CI build. It bootstraps first: a CI runner
|
||||
# checks out and runs this and nothing else, and script/fmt-check runs
|
||||
# the formatter on the host, which a pristine checkout cannot do.
|
||||
# A new CACHEBUST value reruns every RUN step after the ARG CACHEBUST
|
||||
# line, since a cached gate phase is a check that did not run; unlike
|
||||
# --no-cache, it keeps the layers above that line and the cache mounts.
|
||||
# This relies on every stage of the Dockerfile declaring ARG CACHEBUST
|
||||
# before every step that lints, tests or compiles; a stage without that
|
||||
# line is served from cache on an unchanged tree, with no warning.
|
||||
set -eu
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
|
||||
ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)"
|
||||
|
||||
main() {
|
||||
cd "$ROOT"
|
||||
"$SCRIPT_DIR/bootstrap"
|
||||
"$SCRIPT_DIR/check"
|
||||
# The version and the tag each get their own line: a failing
|
||||
# command substitution inside an argument does not trip `set -e`,
|
||||
# so the inline form degrades silently to an empty constant. The
|
||||
# VERSION build argument takes precedence over the version a build
|
||||
# stage derives from the .git in the context.
|
||||
version="$(git describe --tags --always --dirty 2>/dev/null || true)"
|
||||
[ -n "$version" ] || version="unknown"
|
||||
tag="$("$SCRIPT_DIR/projectname")"
|
||||
docker build --build-arg CACHEBUST="$(date +%s)-$$" \
|
||||
--build-arg VERSION="$version" \
|
||||
-t "$tag" .
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/bin/sh
|
||||
# script/docker: build the Docker image tagged with the project name.
|
||||
# Identical in all repos; the tag comes from script/projectname.
|
||||
# A new CACHEBUST value reruns every RUN step after the ARG CACHEBUST
|
||||
# line, since a cached gate phase is a check that did not run; unlike
|
||||
# --no-cache, it keeps the layers above that line and the cache mounts.
|
||||
# This relies on every stage of the Dockerfile declaring ARG CACHEBUST
|
||||
# before every step that lints, tests or compiles; a stage without that
|
||||
# line is served from cache on an unchanged tree, with no warning.
|
||||
set -eu
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
|
||||
ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)"
|
||||
|
||||
main() {
|
||||
cd "$ROOT"
|
||||
# The version and the tag each get their own line: a failing
|
||||
# command substitution inside an argument does not trip `set -e`,
|
||||
# so the inline form degrades silently to an empty constant. The
|
||||
# VERSION build argument takes precedence over the version a build
|
||||
# stage derives from the .git in the context.
|
||||
version="$(git describe --tags --always --dirty 2>/dev/null || true)"
|
||||
[ -n "$version" ] || version="unknown"
|
||||
tag="$("$SCRIPT_DIR/projectname")"
|
||||
docker build --build-arg CACHEBUST="$(date +%s)-$$" \
|
||||
--build-arg VERSION="$version" \
|
||||
-t "$tag" .
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/bin/sh
|
||||
# script/fmt: format all files (writes): gofmt for Go, prettier for
|
||||
# markdown.
|
||||
set -eu
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||
|
||||
GO_DIRS="cmd internal pkg"
|
||||
|
||||
# Debian and Ubuntu name the yarn command yarnpkg.
|
||||
run_yarn() {
|
||||
if command -v yarn >/dev/null 2>&1; then
|
||||
yarn "$@"
|
||||
elif command -v yarnpkg >/dev/null 2>&1; then
|
||||
yarnpkg "$@"
|
||||
else
|
||||
echo "no yarn; run script/bootstrap first" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
cd "$ROOT"
|
||||
# shellcheck disable=SC2086 # GO_DIRS is a word list on purpose
|
||||
gofmt -w $GO_DIRS
|
||||
run_yarn run prettier --write '**/*.md' --tab-width 4 --prose-wrap always
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/bin/sh
|
||||
# script/fmt-check: check formatting (read-only): gofmt for Go, prettier
|
||||
# for markdown.
|
||||
set -eu
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||
|
||||
GO_DIRS="cmd internal pkg"
|
||||
|
||||
# Debian and Ubuntu name the yarn command yarnpkg.
|
||||
run_yarn() {
|
||||
if command -v yarn >/dev/null 2>&1; then
|
||||
yarn "$@"
|
||||
elif command -v yarnpkg >/dev/null 2>&1; then
|
||||
yarnpkg "$@"
|
||||
else
|
||||
echo "no yarn; run script/bootstrap first" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
cd "$ROOT"
|
||||
# shellcheck disable=SC2086 # GO_DIRS is a word list on purpose
|
||||
unformatted="$(gofmt -l $GO_DIRS)"
|
||||
if [ -n "$unformatted" ]; then
|
||||
echo "gofmt: the following files need formatting:" >&2
|
||||
echo "$unformatted" >&2
|
||||
exit 1
|
||||
fi
|
||||
run_yarn run prettier --check '**/*.md' --tab-width 4 --prose-wrap always
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/bin/sh
|
||||
# script/install-precommit: install the git pre-commit hook that runs
|
||||
# script/precommit. Our own extension to scripts-to-rule-them-all.
|
||||
set -eu
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||
|
||||
main() {
|
||||
cd "$ROOT"
|
||||
hook=".git/hooks/pre-commit"
|
||||
printf '#!/bin/sh\nset -e\nscript/precommit\n' > .git/hooks/pre-commit
|
||||
chmod +x .git/hooks/pre-commit
|
||||
echo "pre-commit hook installed: runs script/precommit"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
# script/lint: run the linter. Linting is a phase of the Dockerfile and
|
||||
# this builds that phase alone; the linter is never installed or run on
|
||||
# a developer host, where a shared result cache and a host-global lock
|
||||
# make its answer untrustworthy.
|
||||
#
|
||||
# The phase is not the last stage in the file, so it is built only when
|
||||
# --target names it. A new CACHEBUST value reruns every RUN step after
|
||||
# the ARG CACHEBUST line, since a cached lint layer is a lint that did
|
||||
# not run; unlike --no-cache, it keeps the layers above that line and
|
||||
# the cache mounts. This relies on every stage of the Dockerfile
|
||||
# declaring ARG CACHEBUST before every step that lints, tests or
|
||||
# compiles; a stage without that line is served from cache on an
|
||||
# unchanged tree, with no warning.
|
||||
# --output type=cacheonly writes no image, since nothing uses one.
|
||||
set -eu
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
|
||||
ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)"
|
||||
|
||||
main() {
|
||||
cd "$ROOT"
|
||||
docker build --build-arg CACHEBUST="$(date +%s)-$$" \
|
||||
--target lint \
|
||||
--output type=cacheonly .
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#!/bin/sh
|
||||
# script/precommit: run by the git pre-commit hook; fails the commit if
|
||||
# checks fail. Our own extension to scripts-to-rule-them-all. The Go
|
||||
# extra: go.mod and go.sum must already be tidy, so a commit never
|
||||
# carries a module file that go mod tidy would rewrite.
|
||||
set -eu
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
|
||||
ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)"
|
||||
|
||||
main() {
|
||||
cd "$ROOT"
|
||||
go mod tidy
|
||||
if ! git diff --quiet --exit-code -- go.mod go.sum; then
|
||||
echo "precommit: go mod tidy changed go.mod or go.sum;" \
|
||||
"stage them and retry" >&2
|
||||
exit 1
|
||||
fi
|
||||
"$SCRIPT_DIR/check"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
# script/projectname: output the name of this project. Our own
|
||||
# extension to scripts-to-rule-them-all. Other scripts that need the
|
||||
# name (e.g. script/docker) call this, so they can stay identical
|
||||
# across all repos.
|
||||
set -eu
|
||||
|
||||
main() {
|
||||
echo "x"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Executable
+13
@@ -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 "$@"
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
#!/bin/sh
|
||||
# script/test: run the test suite. Testing is a phase of the Dockerfile
|
||||
# and this builds that phase alone, on the same terms as script/lint:
|
||||
# --target because a phase that is not the last stage is built only when
|
||||
# named, and a new CACHEBUST value because a cached test layer is a test
|
||||
# that did not run; unlike --no-cache, it keeps the layers above the ARG
|
||||
# CACHEBUST line and the cache mounts. This relies on every stage of the
|
||||
# Dockerfile declaring ARG CACHEBUST before every step that lints, tests
|
||||
# or compiles; a stage without that line is served from cache on an
|
||||
# unchanged tree, with no warning. --output type=cacheonly writes no
|
||||
# image, since nothing uses one.
|
||||
set -eu
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
|
||||
ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)"
|
||||
|
||||
main() {
|
||||
cd "$ROOT"
|
||||
docker build --build-arg CACHEBUST="$(date +%s)-$$" \
|
||||
--target test \
|
||||
--output type=cacheonly .
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user