Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae183d5529 |
@@ -123,16 +123,17 @@ alpine. We provide:
|
|||||||
- `script/fmt` — format all markdown files with prettier (writes; native, not in
|
- `script/fmt` — format all markdown files with prettier (writes; native, not in
|
||||||
a container)
|
a container)
|
||||||
- `script/fmt-check` — check formatting (read-only; native)
|
- `script/fmt-check` — check formatting (read-only; native)
|
||||||
- `script/check` — run all checks: `test`, `lint`, `fmt-check`, then
|
- `script/check` — run all checks: `test`, `lint`, `fmt-check` (our own
|
||||||
`docker build -t prompts .` (our own extension; no `--no-cache` there, because
|
extension); builds no image
|
||||||
the two phases that build depends on were just built without it)
|
|
||||||
- `script/docker` —
|
- `script/docker` —
|
||||||
`docker build --no-cache --build-arg VERSION="$version" -t prompts .`, the tag
|
`docker build --no-cache --build-arg VERSION="$version" -t prompts .`, the tag
|
||||||
coming from `script/projectname` (byte-identical across repos)
|
coming from `script/projectname` (byte-identical across repos)
|
||||||
- `script/cibuild` — cd to the repo root, compute `version` from `git describe`,
|
- `script/cibuild` — cd to the repo root, run `script/bootstrap`, run
|
||||||
run `script/check`, then
|
`script/check`, compute `version` from `git describe`, then
|
||||||
`docker build --build-arg VERSION="$version" -t prompts .` (what CI runs; the
|
`docker build --no-cache --build-arg VERSION="$version" -t prompts .` (what CI
|
||||||
version is computed on the host because `.dockerignore` excludes `.git`)
|
runs; it bootstraps because CI checks out and runs this alone while
|
||||||
|
`script/fmt-check` is native, and the version is computed on the host because
|
||||||
|
`.dockerignore` excludes `.git`)
|
||||||
- `script/precommit` — run by the git pre-commit hook (our own extension); calls
|
- `script/precommit` — run by the git pre-commit hook (our own extension); calls
|
||||||
`script/check`
|
`script/check`
|
||||||
- `script/install-precommit` — installs the git pre-commit hook (our own
|
- `script/install-precommit` — installs the git pre-commit hook (our own
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ fmt-check, and commit.
|
|||||||
they pass. This also closes issue 30: a container has its own result cache and
|
they pass. This also closes issue 30: a container has its own result cache and
|
||||||
its own lock, so a lint verdict can no longer belong to another checkout. No
|
its own lock, so a lint verdict can no longer belong to another checkout. No
|
||||||
separate lint Dockerfile, and no `golangci-lint config verify` step.
|
separate lint Dockerfile, and no `golangci-lint config verify` step.
|
||||||
|
`script/check` runs the gates and nothing else, and `script/cibuild`
|
||||||
|
bootstraps first, since it is all CI runs and `script/fmt-check` is native.
|
||||||
- 2026-09-08: Kept in-repo agent scratch out of the Docker build context and out
|
- 2026-09-08: Kept in-repo agent scratch out of the Docker build context and out
|
||||||
of version control: `.claude/` is one full checkout of the repo per in-flight
|
of version control: `.claude/` is one full checkout of the repo per in-flight
|
||||||
agent, and under `COPY . .` all of it was reaching the image. Also closed the
|
agent, and under `COPY . .` all of it was reaching the image. Also closed the
|
||||||
|
|||||||
@@ -99,6 +99,10 @@ with your task.
|
|||||||
on the host.
|
on the host.
|
||||||
- [ ] Every `docker build` in `script/` is tagged — an untagged one leaves a
|
- [ ] Every `docker build` in `script/` is tagged — an untagged one leaves a
|
||||||
dangling image behind on every run, on every host and CI runner
|
dangling image behind on every run, on every host and CI runner
|
||||||
|
- [ ] `script/cibuild` runs `script/bootstrap` before `script/check`, and builds
|
||||||
|
the image with `--no-cache`. Without the bootstrap the CI run dies in
|
||||||
|
`script/fmt-check`, which runs the formatter on the host and finds nothing
|
||||||
|
installed.
|
||||||
- [ ] `script/bootstrap` installs no linter of its own — delete the block, its
|
- [ ] `script/bootstrap` installs no linter of its own — delete the block, its
|
||||||
version variables and its call site. A JS repo's `yarn install` stays; it
|
version variables and its call site. A JS repo's `yarn install` stays; it
|
||||||
brings a linter along with every other dependency, and no verdict is taken
|
brings a linter along with every other dependency, and no verdict is taken
|
||||||
@@ -151,8 +155,9 @@ with your task.
|
|||||||
# Final
|
# Final
|
||||||
|
|
||||||
- [ ] `make check` passes
|
- [ ] `make check` passes
|
||||||
- [ ] `script/cibuild` succeeds and demonstrably executed the checks — a
|
- [ ] `script/cibuild` succeeds in a fresh clone with nothing installed, which
|
||||||
sub-second build, or `CACHED` on a gate layer, means nothing ran
|
is what CI has, and demonstrably executed the checks — a sub-second build,
|
||||||
|
or `CACHED` on a gate layer, means nothing ran
|
||||||
- [ ] A planted lint violation fails both `make lint` and a plain
|
- [ ] A planted lint violation fails both `make lint` and a plain
|
||||||
`docker build .`; revert it afterwards
|
`docker build .`; revert it afterwards
|
||||||
- [ ] Commit and merge fixes before starting your actual task
|
- [ ] Commit and merge fixes before starting your actual task
|
||||||
|
|||||||
@@ -113,15 +113,18 @@ are thin shims calling them. Model scripts:
|
|||||||
container)
|
container)
|
||||||
- [ ] `script/fmt-check` / `make fmt-check` — checks formatting (read-only;
|
- [ ] `script/fmt-check` / `make fmt-check` — checks formatting (read-only;
|
||||||
native)
|
native)
|
||||||
- [ ] `script/check` / `make check` — runs `test`, `lint`, `fmt-check`, then
|
- [ ] `script/check` / `make check` — runs `test`, `lint`, `fmt-check`; must not
|
||||||
builds the image; must not modify files
|
modify files
|
||||||
- [ ] `script/projectname` — outputs the project name (used by `script/docker`
|
- [ ] `script/projectname` — outputs the project name (used by `script/docker`
|
||||||
for the image tag)
|
for the image tag)
|
||||||
- [ ] `script/docker` / `make docker` — builds Docker image, tagged via
|
- [ ] `script/docker` / `make docker` — builds Docker image, tagged via
|
||||||
`script/projectname` (byte-identical across repos); `--no-cache`, plus the
|
`script/projectname` (byte-identical across repos); `--no-cache`, plus the
|
||||||
version as a build arg
|
version as a build arg
|
||||||
- [ ] `script/cibuild` — cd to repo root, run `script/check`, then
|
- [ ] `script/cibuild` — cd to repo root, run `script/bootstrap`, run
|
||||||
`docker build --build-arg VERSION="$version" .` (what CI runs)
|
`script/check`, then
|
||||||
|
`docker build --no-cache --build-arg VERSION="$version" .` (what CI runs).
|
||||||
|
The bootstrap is required: CI checks out and runs this alone, and
|
||||||
|
`script/fmt-check` runs the formatter on the host.
|
||||||
- [ ] Every `docker build` in `script/` is tagged, so no invocation leaves a
|
- [ ] Every `docker build` in `script/` is tagged, so no invocation leaves a
|
||||||
dangling image behind
|
dangling image behind
|
||||||
- [ ] `script/precommit` — called by the pre-commit hook; runs `script/check`
|
- [ ] `script/precommit` — called by the pre-commit hook; runs `script/check`
|
||||||
@@ -135,8 +138,9 @@ are thin shims calling them. Model scripts:
|
|||||||
|
|
||||||
- [ ] `make check` passes
|
- [ ] `make check` passes
|
||||||
- [ ] `make docker` succeeds
|
- [ ] `make docker` succeeds
|
||||||
- [ ] `script/cibuild` succeeds and demonstrably executed the checks — a
|
- [ ] `script/cibuild` succeeds in a fresh clone with nothing installed, which
|
||||||
sub-second build, or `CACHED` on a gate layer, means nothing ran
|
is what CI has, and demonstrably executed the checks — a sub-second build,
|
||||||
|
or `CACHED` on a gate layer, means nothing ran
|
||||||
- [ ] Plant a lint violation and confirm both `make lint` and a plain
|
- [ ] Plant a lint violation and confirm both `make lint` and a plain
|
||||||
`docker build .` fail on it; revert. A plain build that passes proves the
|
`docker build .` fail on it; revert. A plain build that passes proves the
|
||||||
final stage is missing its `COPY --from=` edge to the gate phases.
|
final stage is missing its `COPY --from=` edge to the gate phases.
|
||||||
|
|||||||
+28
-20
@@ -60,12 +60,16 @@ style conventions are in separate documents:
|
|||||||
prerequisite since nvm requires bash. yarn is then pinned via
|
prerequisite since nvm requires bash. yarn is then pinned via
|
||||||
`corepack prepare yarn@<version> --activate`. Never install "latest" or "lts";
|
`corepack prepare yarn@<version> --activate`. Never install "latest" or "lts";
|
||||||
always exact versions. `script/cibuild` runs the CI build: it changes to the
|
always exact versions. `script/cibuild` runs the CI build: it changes to the
|
||||||
repo root, runs `script/check`, and builds the image with the version; the
|
repo root, runs `script/bootstrap`, runs `script/check`, and builds the image
|
||||||
Gitea workflow calls it. Four further scripts are our own extensions to the
|
with the version; the Gitea workflow calls it. **`script/cibuild` runs
|
||||||
standard: `script/check` runs `script/test`, `script/lint` and
|
`script/bootstrap` first**, because the workflow checks out the repo and runs
|
||||||
`script/fmt-check`, then builds the image; `script/precommit` is what the git
|
nothing else, while `script/fmt-check` runs the formatter on the host: on a
|
||||||
pre-commit hook runs, and it calls `script/check`; `script/install-precommit`
|
pristine checkout with nothing installed the run dies there, after the
|
||||||
installs the git pre-commit hook (the `make hooks` target shims to it); and
|
containerised gates have passed. Four further scripts are our own extensions
|
||||||
|
to the standard: `script/check` runs `script/test`, `script/lint` and
|
||||||
|
`script/fmt-check`; `script/precommit` is what the git pre-commit hook runs,
|
||||||
|
and it calls `script/check`; `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
|
`script/projectname` (literally that filename) simply outputs the project's
|
||||||
name. Scripts that need the name call `script/projectname` — e.g.
|
name. Scripts that need the name call `script/projectname` — e.g.
|
||||||
`script/docker` assembles its image tag from it — so those scripts stay
|
`script/docker` assembles its image tag from it — so those scripts stay
|
||||||
@@ -115,7 +119,7 @@ style conventions are in separate documents:
|
|||||||
plain `docker build .` builds the last stage alone and exits 0 having linted
|
plain `docker build .` builds the last stage alone and exits 0 having linted
|
||||||
and tested nothing.
|
and tested nothing.
|
||||||
|
|
||||||
**Every `docker build` in `script/` is tagged**, here and in `script/check`,
|
**Every `docker build` in `script/` is tagged**, here and in
|
||||||
`script/cibuild` and `script/docker`. An untagged build leaves a dangling
|
`script/cibuild` and `script/docker`. An untagged build leaves a dangling
|
||||||
image behind on every invocation, on every developer host and every CI
|
image behind on every invocation, on every developer host and every CI
|
||||||
runner; a tagged one replaces the previous image.
|
runner; a tagged one replaces the previous image.
|
||||||
@@ -139,14 +143,13 @@ style conventions are in separate documents:
|
|||||||
- **Any build that runs checks is built with `--no-cache`.** Docker invalidates
|
- **Any build that runs checks is built with `--no-cache`.** Docker invalidates
|
||||||
a `COPY` layer only when the copied content changes, so on an unchanged tree
|
a `COPY` layer only when the copied content changes, so on an unchanged tree
|
||||||
the check `RUN` is served from cache, nothing executes, and the build still
|
the check `RUN` is served from cache, nothing executes, and the build still
|
||||||
exits 0. `script/lint`, `script/test`, `script/cibuild` and `script/docker`
|
exits 0. Every `docker build` in `script/` therefore passes `--no-cache`:
|
||||||
therefore pass `--no-cache`, and a bare `docker build .` is not evidence that
|
`script/lint`, `script/test`, `script/cibuild` and `script/docker` are the
|
||||||
anything ran: a sub-second build reporting success is a cache hit, not a
|
four, and there is no fifth — `script/check` runs the two gate phases and
|
||||||
result. The one build that may go through the cache is the image build inside
|
`script/fmt-check`, and builds no image of its own. A bare `docker build .` is
|
||||||
`script/check`, which runs immediately after the two gate phases were built
|
not evidence that anything ran: a sub-second build reporting success is a
|
||||||
uncached and so reuses that run. Never invalidate by pruning —
|
cache hit, not a result. Never invalidate by pruning — `docker builder prune`
|
||||||
`docker builder prune` and friends destroy a build cache shared with every
|
and friends destroy a build cache shared with every other build on the host.
|
||||||
other build on the host.
|
|
||||||
|
|
||||||
- **The gate phases are separate stages, and the build stage depends on both.**
|
- **The gate phases are separate stages, and the build stage depends on both.**
|
||||||
The lint phase is based on the `golangci/golangci-lint` image (pinned by
|
The lint phase is based on the `golangci/golangci-lint` image (pinned by
|
||||||
@@ -218,10 +221,13 @@ style conventions are in separate documents:
|
|||||||
`script/docker` and `script/cibuild`; no stage may call `git describe`.
|
`script/docker` and `script/cibuild`; no stage may call `git describe`.
|
||||||
|
|
||||||
- Every repo should have a Gitea Actions workflow (`.gitea/workflows/`) that
|
- Every repo should have a Gitea Actions workflow (`.gitea/workflows/`) that
|
||||||
runs `script/cibuild` on push. That script runs the gate phases and then the
|
runs `script/cibuild` on push, and checks out the repo as its only other step.
|
||||||
image build, so a successful run means every check passed; a bare
|
That script bootstraps, runs the gate phases, and then builds the image, so a
|
||||||
`docker build .` does not carry the same guarantee, because its gate phases
|
successful run means every check passed; a bare `docker build .` does not
|
||||||
may come from the cache.
|
carry the same guarantee, because its gate phases may come from the cache. The
|
||||||
|
image build is uncached and so runs the gate phases a second time. That is the
|
||||||
|
price of the rule above, and it is worth paying: the image that ships is built
|
||||||
|
from a run of its own gates rather than from a cache entry.
|
||||||
|
|
||||||
- Use platform-standard formatters: `black` for Python, `prettier` for
|
- Use platform-standard formatters: `black` for Python, `prettier` for
|
||||||
JS/CSS/Markdown/HTML, `go fmt` for Go. Always use default configuration with
|
JS/CSS/Markdown/HTML, `go fmt` for Go. Always use default configuration with
|
||||||
@@ -352,7 +358,9 @@ style conventions are in separate documents:
|
|||||||
# trip `set -e`, so the inline form degrades to an empty constant.
|
# trip `set -e`, so the inline form degrades to an empty constant.
|
||||||
version="$(git describe --tags --always --dirty 2>/dev/null || true)"
|
version="$(git describe --tags --always --dirty 2>/dev/null || true)"
|
||||||
[ -n "$version" ] || version="unknown"
|
[ -n "$version" ] || version="unknown"
|
||||||
docker build --no-cache --build-arg VERSION="$version" .
|
docker build --no-cache \
|
||||||
|
--build-arg VERSION="$version" \
|
||||||
|
-t "$(script/projectname)" .
|
||||||
```
|
```
|
||||||
|
|
||||||
`--always` makes an untagged repo yield an abbreviated commit hash rather
|
`--always` makes an untagged repo yield an abbreviated commit hash rather
|
||||||
|
|||||||
+4
-10
@@ -1,22 +1,16 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# script/check: run all checks (test, lint, fmt-check) and then the
|
# script/check: run all checks (test, lint, fmt-check). Our own
|
||||||
# image build. Our own extension to scripts-to-rule-them-all. test and
|
# extension to scripts-to-rule-them-all. test and lint are Docker
|
||||||
# lint are Docker phases; fmt-check is native, because a formatter
|
# phases; fmt-check is native, because a formatter writes the working
|
||||||
# writes the working tree. Must not modify any files.
|
# tree. Must not modify any files.
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
|
||||||
ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)"
|
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
"$SCRIPT_DIR/test"
|
"$SCRIPT_DIR/test"
|
||||||
"$SCRIPT_DIR/lint"
|
"$SCRIPT_DIR/lint"
|
||||||
"$SCRIPT_DIR/fmt-check"
|
"$SCRIPT_DIR/fmt-check"
|
||||||
# No --no-cache here: the two phases this build depends on were just
|
|
||||||
# built uncached above, so what it reuses is that run and not an
|
|
||||||
# older one.
|
|
||||||
cd "$ROOT"
|
|
||||||
docker build -t "$("$SCRIPT_DIR/projectname")" .
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
+9
-5
@@ -1,7 +1,10 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# script/cibuild: run the CI build. script/check runs the gates and
|
# script/cibuild: run the CI build. It bootstraps first: a CI runner
|
||||||
# builds the image; this repeats the build with the version, which
|
# checks out and runs this and nothing else, and script/fmt-check runs
|
||||||
# reuses the phases just built rather than re-running them.
|
# the formatter on the host, which a pristine checkout cannot do.
|
||||||
|
# --no-cache for the same reason as script/docker: the gate phases the
|
||||||
|
# final stage depends on are RUN steps, and a cached one is a check that
|
||||||
|
# did not run.
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
|
||||||
@@ -9,6 +12,8 @@ ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)"
|
|||||||
|
|
||||||
main() {
|
main() {
|
||||||
cd "$ROOT"
|
cd "$ROOT"
|
||||||
|
"$SCRIPT_DIR/bootstrap"
|
||||||
|
"$SCRIPT_DIR/check"
|
||||||
# Own line: a failing command substitution inside an argument does
|
# Own line: a failing command substitution inside an argument does
|
||||||
# not trip `set -e`, so the inline form degrades silently to an
|
# not trip `set -e`, so the inline form degrades silently to an
|
||||||
# empty constant. VERSION is computed here because .dockerignore
|
# empty constant. VERSION is computed here because .dockerignore
|
||||||
@@ -16,8 +21,7 @@ main() {
|
|||||||
# version without failing.
|
# version without failing.
|
||||||
version="$(git describe --tags --always --dirty 2>/dev/null || true)"
|
version="$(git describe --tags --always --dirty 2>/dev/null || true)"
|
||||||
[ -n "$version" ] || version="unknown"
|
[ -n "$version" ] || version="unknown"
|
||||||
"$SCRIPT_DIR/check"
|
docker build --no-cache \
|
||||||
docker build \
|
|
||||||
--build-arg VERSION="$version" \
|
--build-arg VERSION="$version" \
|
||||||
-t "$("$SCRIPT_DIR/projectname")" .
|
-t "$("$SCRIPT_DIR/projectname")" .
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-3
@@ -1,9 +1,8 @@
|
|||||||
#!/bin/sh
|
#!/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.
|
||||||
# --no-cache because this is a standalone entrypoint: the lint and test
|
# --no-cache because the gate phases the final stage depends on are RUN
|
||||||
# phases the final stage depends on must run rather than be served from
|
# steps, and a cached one is a check that did not run.
|
||||||
# an older build.
|
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
|
||||||
|
|||||||
Reference in New Issue
Block a user