Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fdb566a209 | ||
|
|
d709d88e79 | ||
|
|
f77d785bed | ||
|
|
3f8201d532 | ||
|
|
a8686891ba | ||
|
|
cc6a5a00e7 |
+24
-63
@@ -1,75 +1,37 @@
|
||||
# Docker matches this file with moby/patternmatcher: Go filepath.Match
|
||||
# semantics plus a `**` extension, compiled to a regexp. Plain
|
||||
# filepath.Match has no `**` at all. What follows from that: `*` does not
|
||||
# cross `/`, and a pattern without a leading `**/` is anchored at the
|
||||
# build-context root. Every depth-independent pattern therefore needs the
|
||||
# `**/` prefix — without it `config/.env` and `certs/server.key` still
|
||||
# ship while the file reads as solved.
|
||||
# .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 the file reads as solved. Only
|
||||
# genuinely root-anchored entries go unprefixed. Never transplant these
|
||||
# into .gitignore, where `**/` is wrong.
|
||||
#
|
||||
# Root-anchored entries are for paths that occur exactly once, at the
|
||||
# context root. A host-built binary is the usual case, and it must be
|
||||
# written anchored: `/myapp`, never `**/myapp`. The prefixed form also
|
||||
# matches `cmd/myapp/`, which deletes the package directory from the
|
||||
# context. In-repo agent scratch is the other case, for the same reason
|
||||
# — with the caveat recorded at that entry: anchoring is exact only
|
||||
# where agents run at the repo root, and a repo where they do not must
|
||||
# add its own entries.
|
||||
# Matching is case-sensitive, so secrets use character ranges rather
|
||||
# than an ALL-CAPS twin, which would still miss `Server.Key`.
|
||||
#
|
||||
# Matching is case-sensitive, so `**/*.key` does not match
|
||||
# `certs/SERVER.KEY`, which is reachable on the case-insensitive
|
||||
# filesystems most laptops use. Adding an ALL-CAPS twin per pattern is
|
||||
# not the fix: it still misses `Server.Key` while reading as though case
|
||||
# were handled. Character ranges cover every spelling in one line, so
|
||||
# every secret name below is written that way — including the
|
||||
# extensionless SSH keys and `.envrc`, because on those same
|
||||
# case-insensitive filesystems direnv reads `.ENVRC` and ssh reads
|
||||
# `ID_RSA`.
|
||||
#
|
||||
# `**/*.[eE][nN][vV]` also excludes a committed env template such as
|
||||
# `example.env`. If the build genuinely needs one, re-include it with a
|
||||
# negation after the pattern: `!docs/example.env`.
|
||||
#
|
||||
# Extend this file with the repo's own host-built artifacts (compiled
|
||||
# binaries, test binaries, coverage output); those are per-repo and
|
||||
# belong here because a host build otherwise drops them into the
|
||||
# context.
|
||||
# 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.
|
||||
|
||||
# Repository metadata: exactly one, at the context root. Excluding it
|
||||
# means `git describe` cannot run in any build stage, and it fails
|
||||
# quietly there rather than erroring, so a version embedded that way
|
||||
# comes out empty. Compute the version on the host and pass it in with
|
||||
# `--build-arg VERSION=...`; see the version rule in REPO_POLICIES.md.
|
||||
# Excluding .git means `git describe` cannot run in any build stage and
|
||||
# fails quietly there; pass the version in with --build-arg VERSION.
|
||||
.git
|
||||
|
||||
# In-repo agent scratch: a directory holding a full additional checkout
|
||||
# of the repo for each in-flight agent. Anchored because it occurs
|
||||
# exactly once *where agents run at the repo root*, which is the
|
||||
# convention this file assumes; the `**/` form would also match any
|
||||
# nested directory of that name and delete it from the build.
|
||||
#
|
||||
# KNOWN GAP, and it is not hypothetical: the directory is created in the
|
||||
# agent's working directory. If agents in this repo run in
|
||||
# subdirectories — a monorepo with a per-service agent, say — then
|
||||
# `services/api/.claude/` is NOT excluded by the line below and still
|
||||
# reaches the build context and the image, which is the exposure this
|
||||
# entry exists to close. A repo in that shape adds its own anchored
|
||||
# entries (`/services/api/.claude`), or `**/.claude` after confirming no
|
||||
# legitimately named nested directory would be caught.
|
||||
#
|
||||
# Not case-folded, unlike the secret patterns below: tooling creates
|
||||
# this directory in exactly one spelling, so a folded pattern would add
|
||||
# no coverage.
|
||||
# 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 both the bare `.env` name (`*` matches
|
||||
# the empty string) and the `prod.env` convention.
|
||||
# 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 that carry them. Public certificates
|
||||
# (*.crt, *.cer) are deliberately absent: they are not secrets and are
|
||||
# sometimes a legitimate build input.
|
||||
# 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
|
||||
@@ -86,8 +48,7 @@
|
||||
**/.DS_Store
|
||||
**/Thumbs.db
|
||||
|
||||
# Editor state. Never a build input, and it churns under a developer's
|
||||
# hands, so it invalidates COPY for reasons unrelated to the source.
|
||||
# Editor state: never a build input, and it churns COPY.
|
||||
**/*.swp
|
||||
**/*.swo
|
||||
**/*~
|
||||
|
||||
+13
-13
@@ -3,26 +3,26 @@ FROM node@sha256:e4bf2a82ad0a4037d28035ae71529873c069b13eb0455466ae0bc13363826e3
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# script/bootstrap installs all prerequisites (make via apk here; node
|
||||
# and yarn are already in the base image, so those steps are skipped).
|
||||
# Dependency manifests are copied first so the bootstrap layer is
|
||||
# cached until they change.
|
||||
# Makes script/lint run the linter directly rather than building
|
||||
# Dockerfile.lint, which would need a docker daemon here.
|
||||
ENV LINT_IN_CONTAINER=1
|
||||
|
||||
# script/bootstrap installs all prerequisites. Manifests are copied
|
||||
# first so that layer stays cached until dependencies change.
|
||||
COPY script/ script/
|
||||
COPY package.json yarn.lock ./
|
||||
RUN script/bootstrap
|
||||
|
||||
COPY . .
|
||||
|
||||
# CHECK_EPOCH is a per-invocation nonce supplied by script/cibuild and
|
||||
# script/docker. Without it an unchanged tree serves this layer from
|
||||
# CHECK_EPOCH is a per-invocation nonce from script/cibuild and
|
||||
# script/docker; without it an unchanged tree serves this layer from
|
||||
# cache and the build reports a green it never ran. ARG is stage-scoped,
|
||||
# so it must be redeclared in every stage that runs checks. The guard
|
||||
# makes a bare `docker build .` fail loudly instead of silently reusing
|
||||
# the empty (and therefore stable) cache key. Expand the value into the
|
||||
# command so the cache miss does not depend on BuildKit's handling of an
|
||||
# unreferenced ARG. Both the guard and the check RUN reference the value,
|
||||
# so both are value-keyed: there are two independent invalidation points
|
||||
# here, not one. Keep both.
|
||||
# so declare it in every stage that runs checks. The guard fails a bare
|
||||
# `docker build .`, which would otherwise reuse the empty (and therefore
|
||||
# stable) cache key. The value is also expanded into the check command,
|
||||
# so the cache miss does not depend on BuildKit's handling of an
|
||||
# unreferenced ARG; keep both references.
|
||||
ARG CHECK_EPOCH
|
||||
RUN [ -n "$CHECK_EPOCH" ] || exit 1
|
||||
RUN echo "check epoch: ${CHECK_EPOCH}" && make check
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# Lint-only image, built by script/lint when it is not already inside a
|
||||
# container. Linting is a build step, so a successful build is a clean
|
||||
# lint, and nothing is bind-mounted, which matters when the daemon is
|
||||
# remote.
|
||||
#
|
||||
# node 22-alpine, 2026-02-22
|
||||
FROM node@sha256:e4bf2a82ad0a4037d28035ae71529873c069b13eb0455466ae0bc13363826e34
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Makes script/lint run the linter directly instead of recursing into
|
||||
# another docker build, which has no daemon here.
|
||||
ENV LINT_IN_CONTAINER=1
|
||||
|
||||
COPY script/ script/
|
||||
COPY package.json yarn.lock ./
|
||||
RUN script/bootstrap
|
||||
|
||||
COPY . .
|
||||
|
||||
# ARG sits after the dependency layer so that layer stays cached and
|
||||
# only the lint re-runs. The guard fails a bare `docker build
|
||||
# -f Dockerfile.lint .`, which would otherwise reuse the empty (stable)
|
||||
# cache key and report a lint it never ran.
|
||||
ARG CHECK_EPOCH
|
||||
RUN [ -n "$CHECK_EPOCH" ] || exit 1
|
||||
RUN echo "lint epoch: ${CHECK_EPOCH}" && make lint
|
||||
@@ -117,7 +117,9 @@ alpine. We provide:
|
||||
- `script/projectname` — output the project name (our own extension); used by
|
||||
`script/docker` for the image tag
|
||||
- `script/test` — run the test suite (no tests defined here)
|
||||
- `script/lint` — lint the markdown files with prettier
|
||||
- `script/lint` — lint the markdown files with prettier. Inside a container
|
||||
(`LINT_IN_CONTAINER=1`, set by both Dockerfiles) it runs prettier directly; on
|
||||
a host it builds `Dockerfile.lint` so the linter still runs in a container
|
||||
- `script/fmt` — format all markdown files with prettier (writes)
|
||||
- `script/fmt-check` — check formatting (read-only)
|
||||
- `script/check` — run all checks: `test`, `lint`, `fmt-check` (our own
|
||||
|
||||
@@ -21,6 +21,27 @@ fmt-check, and commit.
|
||||
|
||||
# Completed Steps
|
||||
|
||||
- 2026-08-10: Moved every lint run into a container. `script/lint` now runs the
|
||||
linter directly when `LINT_IN_CONTAINER=1` and otherwise builds
|
||||
`Dockerfile.lint`, so the linter never runs on a developer host — closing the
|
||||
content-keyed result cache that produced a confirmed false green, the
|
||||
host-global `$TMPDIR/golangci-lint.lock`, and host/container version skew.
|
||||
Detection is on that marker alone: a false negative inside a container fails
|
||||
loudly on the missing daemon, while a false positive on a host would silently
|
||||
restore host linting, so `/.dockerenv` is rejected outright — measured absent
|
||||
inside BuildKit `RUN` steps and present on hosts that are themselves
|
||||
containers. Everything else keeps its existing shape: `make check` still runs
|
||||
in the image, `script/cibuild` is still one build, and the Go multistage lint
|
||||
stage survives with `ENV LINT_IN_CONTAINER=1`. `Dockerfile.lint` carries the
|
||||
same `CHECK_EPOCH` guard, with the `ARG` below the dependency layer so only
|
||||
the lint re-runs. The `script/bootstrap` golangci-lint install and the
|
||||
per-checkout cache/lock/`.lint-cache` wrapper are deleted as superseded; a JS
|
||||
repo's `yarn install` stays, since the rule is about where a verdict comes
|
||||
from, not about which binaries exist. `golangci-lint config verify` was kept
|
||||
on measurement: a bogus config key passes `golangci-lint run` with `0 issues`
|
||||
and fails `config verify`, and every case reproduced byte-identically under
|
||||
`--network none`, so the schema is embedded and the line costs no network.
|
||||
Comment blocks across the touched files were cut hard in the same pass.
|
||||
- 2026-08-09: Made a golangci-lint result belong to the tree that asked for it.
|
||||
REPO_POLICIES.md now carries the canonical Go `script/lint`, which gives the
|
||||
linter per-checkout `GOLANGCI_LINT_CACHE` and per-checkout `TMPDIR`. The two
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Code Styleguide — Go
|
||||
last_modified: 2026-08-09
|
||||
last_modified: 2026-08-10
|
||||
---
|
||||
|
||||
1. Try to hard wrap long lines at 77 characters or less.
|
||||
@@ -111,7 +111,11 @@ last_modified: 2026-08-09
|
||||
|
||||
1. For anything beyond a simple script or tool, or anything that is going to
|
||||
run in any sort of "production" anywhere, make sure it passes
|
||||
`golangci-lint`.
|
||||
`golangci-lint`. Run it with `make lint`, never by invoking the binary: the
|
||||
linter always runs in a container, and `golangci-lint` is not installed on
|
||||
the host by any repo. Invoked directly on a shared host it reads a result
|
||||
cache keyed on file content rather than location, and a host-global lock, so
|
||||
its answer may belong to another checkout entirely.
|
||||
|
||||
1. Write a `Dockerfile` for every repo, even if it only runs the tests and
|
||||
linting. `script/cibuild` and `script/docker` should always make sure that
|
||||
@@ -144,10 +148,15 @@ last_modified: 2026-08-09
|
||||
|
||||
1. Keep the `main()` function as small as possible.
|
||||
|
||||
1. Keep the `main` package as small as possible. Move as much code as is
|
||||
feasible to a library package, even if it's an internal one. `main` is just
|
||||
an entrypoint to your code, not a place for implementations. Exception:
|
||||
single-file scripts.
|
||||
1. Keep the `main` package as small as possible. Each `cmd/<name>/` directory
|
||||
contains a single `main.go` whose body is one call into library code (for
|
||||
example `os.Exit(cli.Main())` calling `internal/cli`). All CLI logic — flag
|
||||
parsing, subcommand dispatch, argument handling, output formatting — lives
|
||||
in `internal/` or `pkg/`, not in `cmd/`. `main` is just an entrypoint to
|
||||
your code, not a place for implementations. Exception: single-file scripts.
|
||||
|
||||
1. No project logic outside `internal/` or `pkg/`. Anything in `cmd/` is a thin
|
||||
entrypoint only.
|
||||
|
||||
1. HTTP HandleFuncs should be returned from methods or functions that need to
|
||||
handle HTTP requests. Don't use methods or your top level functions as
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Existing Repo Checklist
|
||||
last_modified: 2026-08-09
|
||||
last_modified: 2026-08-10
|
||||
---
|
||||
|
||||
Use this checklist when beginning work in a repo that may not yet conform to our
|
||||
@@ -42,6 +42,15 @@ with your task.
|
||||
`CHECK_EPOCH` rule in `REPO_POLICIES.md`. Without them the check layer is
|
||||
served from cache on an unchanged tree and the build reports a green it
|
||||
never ran.
|
||||
- [ ] **Every stage that runs checks sets `ENV LINT_IN_CONTAINER=1`** — the lint
|
||||
stage and the build stage both. This is the item an existing repo most
|
||||
often fails after adopting the containerised lint: without it
|
||||
`script/lint` tries to build `Dockerfile.lint` from inside a build step,
|
||||
where there is no daemon.
|
||||
- [ ] `Dockerfile.lint` exists and `script/lint` builds it when not already in a
|
||||
container — see the containerised-lint rule in `REPO_POLICIES.md`. Base
|
||||
image pinned by sha256 with a version/date comment, `ARG CHECK_EPOCH`
|
||||
**after** the dependency layer with the guard below it.
|
||||
- [ ] `.dockerignore` excludes the repo's own host-built artifacts (compiled
|
||||
binaries, test binaries, coverage output), written root-anchored —
|
||||
`/myapp`, never `**/myapp`, which would also match `cmd/myapp/`. An
|
||||
@@ -100,15 +109,28 @@ with your task.
|
||||
`script/install-precommit`, shimmed by `make hooks`) runs it
|
||||
- [ ] README has an **Entrypoints** section documenting the `script/`
|
||||
entrypoints and linking the standard
|
||||
- [ ] Go: `script/lint` isolates golangci-lint per checkout —
|
||||
`GOLANGCI_LINT_CACHE` and `TMPDIR` both exported into `.lint-cache/`
|
||||
(which is in `.gitignore` and `.dockerignore`), `--allow-serial-runners`
|
||||
passed, and the lock error retried rather than reported as findings. Copy
|
||||
the canonical block from `REPO_POLICIES.md`. Setting only the cache is the
|
||||
common half-fix and leaves `parallel golangci-lint is running` failing
|
||||
runs red.
|
||||
- [ ] `script/lint` is the canonical detect-and-branch form, and no host
|
||||
invocation anywhere in the repo can produce a lint **verdict** — grep for
|
||||
the linter's own name across `script/`, the `Makefile` and CI config, not
|
||||
just `script/lint`. A second path is likeliest here: a `make lint-fast`,
|
||||
an older container-versus-host branch, or a CI step calling the binary
|
||||
directly. **Expected hits that are not the defect**: `script/fmt`, and in
|
||||
a repo whose formatter is also its linter, `script/fmt-check`. Everything
|
||||
else the grep finds is a real second path and goes.
|
||||
- [ ] Detection is on `LINT_IN_CONTAINER` alone. Reject any `/.dockerenv` or
|
||||
cgroup heuristic: absent in BuildKit `RUN` steps, present on hosts that
|
||||
are themselves containers, and a false positive lints on the host.
|
||||
- [ ] `script/bootstrap` installs no golangci-lint. Delete the block, its
|
||||
version and ref variables, and its call site. A JS repo's `yarn install`
|
||||
stays — it brings a linter along with every other dependency, which is
|
||||
fine as long as no verdict is taken from it.
|
||||
- [ ] The per-checkout lint state is gone: no `GOLANGCI_LINT_CACHE` or `TMPDIR`
|
||||
exports, no `--allow-serial-runners`, and `.lint-cache/` removed from
|
||||
`.gitignore` and `.dockerignore`.
|
||||
- [ ] `make check` does not modify any files in the repo
|
||||
- [ ] `make test` has a 30-second timeout
|
||||
- [ ] `make test` has a 90-second timeout and completes within the 60-second
|
||||
hard cap (over 20 seconds is green but must be filed as an improvement
|
||||
bug)
|
||||
- [ ] `make test` runs real tests, not a no-op (at minimum, import/compile
|
||||
check)
|
||||
- [ ] `make check` passes on current branch
|
||||
@@ -153,6 +175,9 @@ with your task.
|
||||
# Final
|
||||
|
||||
- [ ] `make check` passes
|
||||
- [ ] `script/cibuild` succeeds (a bare `docker build .` fails closed by design,
|
||||
on the `CHECK_EPOCH` guard)
|
||||
- [ ] `make lint` runs twice on an unchanged tree with the lint layer `DONE`
|
||||
both times, never `CACHED` and never sub-second
|
||||
- [ ] `script/cibuild` succeeds (a bare `docker build .` or
|
||||
`docker build -f Dockerfile.lint .` fails closed by design, on the
|
||||
`CHECK_EPOCH` guard)
|
||||
- [ ] Commit and merge fixes before starting your actual task
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: New Repo Checklist
|
||||
last_modified: 2026-08-09
|
||||
last_modified: 2026-08-10
|
||||
---
|
||||
|
||||
Use this checklist when creating a new repository from scratch. Follow the steps
|
||||
@@ -79,9 +79,21 @@ Template files can be fetched from:
|
||||
`CHECK_EPOCH` rule in `REPO_POLICIES.md`. Without them the check layer is
|
||||
served from cache on an unchanged tree and the build reports a green it
|
||||
never ran.
|
||||
- Every stage that runs checks sets `ENV LINT_IN_CONTAINER=1`, so
|
||||
`script/lint` runs the linter natively instead of trying to build
|
||||
`Dockerfile.lint` where there is no daemon.
|
||||
- Go repos: separate `lint` stage on the `golangci/golangci-lint` image,
|
||||
with `COPY --from=lint /src/go.sum /dev/null` in the build stage to force
|
||||
the ordering. Re-prove that ordering warm after adopting `CHECK_EPOCH`.
|
||||
- Server: also builds and runs the application
|
||||
- Non-server: brings up dev environment and runs `make check`
|
||||
- Image pinned by sha256 hash with version/date comment
|
||||
- [ ] `Dockerfile.lint` — the lint-only image `script/lint` builds when it is
|
||||
not already inside a container. Sets `ENV LINT_IN_CONTAINER=1`; same
|
||||
`ARG CHECK_EPOCH` + guard + expanded-value discipline as above, with the
|
||||
`ARG` **after** the dependency layer so only the lint re-runs. Base image
|
||||
pinned by sha256 with a version/date comment. Copy from
|
||||
`REPO_POLICIES.md`.
|
||||
- [ ] Gitea Actions workflow at `.gitea/workflows/check.yml` that runs
|
||||
`script/cibuild` on push — reference
|
||||
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.gitea/workflows/check.yml`
|
||||
@@ -106,20 +118,17 @@ are thin shims calling them. Model scripts:
|
||||
installs
|
||||
- [ ] `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
|
||||
timeout)
|
||||
- [ ] `script/lint` / `make lint` — runs linter
|
||||
- [ ] Go: exports `GOLANGCI_LINT_CACHE` **and** `TMPDIR` into a
|
||||
`.lint-cache/` directory inside the checkout, above any
|
||||
container-versus-host branch so every path that reaches the linter
|
||||
gets them; passes `--allow-serial-runners` (never
|
||||
`--allow-parallel-runners`); retries on
|
||||
`parallel golangci-lint is running` detected on **stderr** and exits
|
||||
75 with a VOID message on exhaustion. Copy the canonical block from
|
||||
`REPO_POLICIES.md` rather than writing your own: a version that sets
|
||||
only the cache leaves the false-red half live, and one that detects
|
||||
the collision by exit status can retry a real finding away.
|
||||
- [ ] Go: `.lint-cache/` is in both `.gitignore` and `.dockerignore`
|
||||
- [ ] `script/test` / `make test` — runs real tests, not a no-op (90-second
|
||||
timeout, 60-second hard cap on wall time)
|
||||
- [ ] `script/lint` / `make lint` — runs the linter directly when
|
||||
`LINT_IN_CONTAINER=1`, otherwise `epoch="$(date +%s%N)$$"` on its own line
|
||||
then `docker build --build-arg CHECK_EPOCH="$epoch" -f Dockerfile.lint .`.
|
||||
No lint verdict may come from a host invocation. Copy from
|
||||
`REPO_POLICIES.md`. Detect on `LINT_IN_CONTAINER` only — never
|
||||
`/.dockerenv`, which is absent in BuildKit `RUN` steps and present on
|
||||
hosts that are themselves containers. Without the nonce this exits 0 on an
|
||||
unchanged tree having linted nothing; without `-f Dockerfile.lint` it
|
||||
builds the main image and lints nothing at all.
|
||||
- [ ] `script/fmt` / `make fmt` — formats code (writes)
|
||||
- [ ] `script/fmt-check` / `make fmt-check` — checks formatting (read-only)
|
||||
- [ ] `script/check` / `make check` — runs `test`, `lint`, `fmt-check`; must not
|
||||
@@ -149,7 +158,10 @@ are thin shims calling them. Model scripts:
|
||||
export with no `.git` and on a repo with no commits — keep it, and do not
|
||||
collapse it into `|| echo unknown`, which makes it unreachable. See the
|
||||
`CHECK_EPOCH` and git-describe rules in `REPO_POLICIES.md` for why each
|
||||
element is load-bearing. A bare `docker build .` fails closed by design.
|
||||
element is load-bearing. A bare `docker build .` fails closed by design, and
|
||||
so does a bare `docker build -f Dockerfile.lint .`. The image runs
|
||||
`make check`, which includes lint, so `script/cibuild` needs no separate
|
||||
lint step.
|
||||
|
||||
- [ ] `script/precommit` — called by the pre-commit hook; runs `script/check`
|
||||
- [ ] `script/install-precommit` — installs the pre-commit hook that runs
|
||||
@@ -161,7 +173,11 @@ are thin shims calling them. Model scripts:
|
||||
# 4. Verify
|
||||
|
||||
- [ ] `make check` passes
|
||||
- [ ] `make lint` demonstrably runs the linter rather than returning a cached
|
||||
build: run it twice on an unchanged tree and confirm the lint layer says
|
||||
`DONE`, never `CACHED`, both times
|
||||
- [ ] `make docker` succeeds
|
||||
- [ ] `script/cibuild` succeeds and demonstrably executes
|
||||
- [ ] No secrets in repo
|
||||
- [ ] No mutable image/package references
|
||||
- [ ] No unnecessary files in repo root
|
||||
|
||||
+331
-582
File diff suppressed because it is too large
Load Diff
+9
-14
@@ -1,27 +1,22 @@
|
||||
#!/bin/sh
|
||||
# script/cibuild: run the CI build. The Dockerfile runs script/check, but
|
||||
# that only proves anything because CHECK_EPOCH is a fresh nonce on every
|
||||
# invocation: without it Docker serves the check layer from cache on an
|
||||
# unchanged tree and the build exits 0 without running the suite.
|
||||
# invocation: without it Docker serves the check layer from cache and the
|
||||
# build exits 0 without running the suite.
|
||||
set -eu
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||
|
||||
main() {
|
||||
cd "$ROOT"
|
||||
# Assign on its own line: a failing command substitution inside an
|
||||
# argument does not trip `set -e`, which would silently degrade the
|
||||
# nonce to an empty constant. `$$` is required because busybox `date`
|
||||
# drops %N without erroring.
|
||||
# Both assignments on 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. `$$` because busybox `date`
|
||||
# drops %N without erroring. VERSION is computed here because
|
||||
# .dockerignore excludes .git, so `git describe` in a build stage
|
||||
# yields an empty version without failing; the guard below is the
|
||||
# single place the fallback is applied.
|
||||
epoch="$(date +%s%N)$$"
|
||||
# VERSION must be computed here, on the host: .dockerignore excludes
|
||||
# .git, so `git describe` cannot run in any build stage and fails
|
||||
# quietly there rather than erroring. Same own-line discipline as the
|
||||
# epoch. `|| true` keeps a failing describe from tripping `set -e`
|
||||
# and leaves the value empty; the guard below is then the single
|
||||
# place the fallback is applied, and it does fire — on an export with
|
||||
# no .git, or a repo with no commits yet. `unknown` is visibly wrong
|
||||
# in a binary in a way that an empty version is not.
|
||||
version="$(git describe --tags --always --dirty 2>/dev/null || true)"
|
||||
[ -n "$version" ] || version="unknown"
|
||||
docker build \
|
||||
|
||||
+6
-12
@@ -11,19 +11,13 @@ ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)"
|
||||
|
||||
main() {
|
||||
cd "$ROOT"
|
||||
# Assign on its own line: a failing command substitution inside an
|
||||
# argument does not trip `set -e`, which would silently degrade the
|
||||
# nonce to an empty constant. `$$` is required because busybox `date`
|
||||
# drops %N without erroring.
|
||||
# Both assignments on 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. `$$` because busybox `date`
|
||||
# drops %N without erroring. VERSION is computed here because
|
||||
# .dockerignore excludes .git, so `git describe` in a build stage
|
||||
# yields an empty version without failing.
|
||||
epoch="$(date +%s%N)$$"
|
||||
# VERSION must be computed here, on the host: .dockerignore excludes
|
||||
# .git, so `git describe` cannot run in any build stage and fails
|
||||
# quietly there rather than erroring. Same own-line discipline as the
|
||||
# epoch. `|| true` keeps a failing describe from tripping `set -e`
|
||||
# and leaves the value empty; the guard below is then the single
|
||||
# place the fallback is applied, and it does fire — on an export with
|
||||
# no .git, or a repo with no commits yet. `unknown` is visibly wrong
|
||||
# in a binary in a way that an empty version is not.
|
||||
version="$(git describe --tags --always --dirty 2>/dev/null || true)"
|
||||
[ -n "$version" ] || version="unknown"
|
||||
docker build \
|
||||
|
||||
+24
-3
@@ -1,13 +1,34 @@
|
||||
#!/bin/sh
|
||||
# script/lint: run the linter.
|
||||
# script/lint: run the linter. Inside a container, run it directly;
|
||||
# on a host, build Dockerfile.lint so it runs in one anyway. The linter
|
||||
# is never run on a developer host, where a shared result cache, a
|
||||
# host-global lock and a stale toolchain make its answer untrustworthy.
|
||||
#
|
||||
# LINT_IN_CONTAINER is set by this repo's Dockerfiles and is the ONLY
|
||||
# accepted signal. Do not add a /.dockerenv fallback: it is absent
|
||||
# inside BuildKit RUN steps and present on hosts that are themselves
|
||||
# containers, so it both misses and false-positives — and a false
|
||||
# positive silently restores host linting.
|
||||
set -eu
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||
|
||||
main() {
|
||||
cd "$ROOT"
|
||||
echo "Linting markdown files..."
|
||||
yarn run prettier --check '**/*.md' --tab-width 4 --prose-wrap always
|
||||
|
||||
if [ "${LINT_IN_CONTAINER:-}" = "1" ]; then
|
||||
exec yarn run prettier --check '**/*.md' \
|
||||
--tab-width 4 --prose-wrap always
|
||||
fi
|
||||
|
||||
# Own line, and `$$` because busybox `date` drops %N silently.
|
||||
# Without a fresh nonce the lint layer is cached and this exits 0
|
||||
# having linted nothing.
|
||||
epoch="$(date +%s%N)$$"
|
||||
docker build \
|
||||
--build-arg CHECK_EPOCH="$epoch" \
|
||||
-f Dockerfile.lint \
|
||||
.
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
Reference in New Issue
Block a user