Compare commits
1 Commits
3a218497b8
...
61448b0c4e
| Author | SHA1 | Date | |
|---|---|---|---|
| 61448b0c4e |
@@ -10,7 +10,7 @@
|
||||
# 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.
|
||||
# context. In-repo agent scratch is the other case, for the same reason.
|
||||
#
|
||||
# Matching is case-sensitive, so `**/*.key` does not match
|
||||
# `certs/SERVER.KEY`, which is reachable on the case-insensitive
|
||||
@@ -31,9 +31,21 @@
|
||||
# belong here because a host build otherwise drops them into the
|
||||
# context.
|
||||
|
||||
# Repository metadata: exactly one, at the context root.
|
||||
# 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.
|
||||
.git
|
||||
|
||||
# In-repo agent scratch: one directory at the context root, holding a
|
||||
# full additional checkout of the repo for each in-flight agent. Written
|
||||
# anchored because it occurs exactly once — the `**/` form would also
|
||||
# match a nested directory of that name. Not case-folded, unlike the
|
||||
# secret patterns below: tooling creates this in exactly one spelling,
|
||||
# and a miss costs build-context bloat rather than exposure.
|
||||
.claude
|
||||
|
||||
# Environment files. `*.env` covers both the bare `.env` name (`*` matches
|
||||
# the empty string) and the `prod.env` convention.
|
||||
**/*.[eE][nN][vV]
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -11,6 +11,12 @@ Thumbs.db
|
||||
.vscode/
|
||||
*.sublime-*
|
||||
|
||||
# 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/
|
||||
|
||||
|
||||
21
TODO.md
21
TODO.md
@@ -21,6 +21,27 @@ fmt-check, and commit.
|
||||
|
||||
# Completed Steps
|
||||
|
||||
- 2026-08-09: Kept in-repo agent scratch out of the Docker build context and out
|
||||
of version control. `.claude/` holds one worktree — an entire additional
|
||||
checkout of the repo — per in-flight agent, and under `COPY . .` all of it was
|
||||
reaching the image: another session's unreviewed, sometimes uncommitted work,
|
||||
inflating the context by a multiple of the repo and invalidating `COPY` for
|
||||
reasons unrelated to the repo's own content. The `.dockerignore` entry is
|
||||
root-anchored, because the directory occurs exactly once and the `**/` form
|
||||
additionally deletes any nested directory of that name; the `.gitignore` entry
|
||||
is unanchored, because `.gitignore` patterns already match at every depth, and
|
||||
each file is written to its own semantics rather than derived from the other.
|
||||
Also closed the consequence that ships broken silently: excluding `.git` means
|
||||
`git describe` cannot run in any build stage and yields an empty version
|
||||
without erroring, so `script/docker` and `script/cibuild` now compute the
|
||||
version on the host and pass `--build-arg VERSION`, and `REPO_POLICIES.md`
|
||||
states where `VERSION` comes from instead of leaving the reader to fill the
|
||||
gap with `git describe` inside the build. The two Go documents that carry the
|
||||
`GOLDFLAGS` pattern were corrected in the same pass, from `:=` to `?=`, since
|
||||
a `$(shell git describe)` evaluated inside a build stage is exactly the empty
|
||||
version this closes. Verified by enumerating a probe image before, after, and
|
||||
against the `**/`-prefixed form, with a positive control and the `CHECK_EPOCH`
|
||||
cache verification re-run under the changed build context.
|
||||
- 2026-08-09: Closed the secret exposure in the canonical `.dockerignore`: a
|
||||
developer's local `.env`, `*.pem` or `*.key` was reaching the Docker build
|
||||
context under `COPY . .`, invisible to every git-based check because
|
||||
|
||||
@@ -49,7 +49,14 @@ last_modified: 2026-08-09
|
||||
```
|
||||
|
||||
```make
|
||||
VERSION := $(shell git describe --always --dirty)
|
||||
# ?= so a Docker build can supply the value. `.dockerignore` excludes
|
||||
# `.git`, so inside a build stage `$(shell git describe ...)` expands to
|
||||
# the empty string without failing and the binary reports no version at
|
||||
# all. The Dockerfile declares `ARG VERSION` in the stage that compiles,
|
||||
# which puts it in the environment where this `?=` defers to it, and
|
||||
# `script/docker` / `script/cibuild` compute it on the host. See the
|
||||
# git-describe rule in REPO_POLICIES.md.
|
||||
VERSION ?= $(shell git describe --always --dirty)
|
||||
BUILDARCH := $(shell uname -m)
|
||||
|
||||
GOLDFLAGS += -X main.Version=$(VERSION)
|
||||
|
||||
@@ -24,9 +24,14 @@ with your task.
|
||||
- [ ] `LICENSE` file exists and matches the README
|
||||
- [ ] `REPO_POLICIES.md` exists and version date is current — fetch from
|
||||
`https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/REPO_POLICIES.md`
|
||||
- [ ] `.gitignore` is comprehensive (OS, editor, language artifacts, secrets) —
|
||||
fetch from `https://git.eeqj.de/sneak/prompts/raw/branch/main/.gitignore`
|
||||
if missing
|
||||
- [ ] `.gitignore` is comprehensive (OS, editor, agent scratch, language
|
||||
artifacts, secrets) — fetch from
|
||||
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.gitignore` if missing.
|
||||
An existing repo usually has a hand-written one that is never re-fetched,
|
||||
so check the entries rather than the file's presence: `.claude/` in
|
||||
particular, unanchored, so agent worktrees cannot be committed by
|
||||
accident. Do not give it a `**/` prefix — that is a `.dockerignore` form
|
||||
and is wrong here.
|
||||
- [ ] `.editorconfig` exists — fetch from
|
||||
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.editorconfig`
|
||||
- [ ] `Dockerfile` and `.dockerignore` exist (fetch `.dockerignore` from
|
||||
@@ -42,6 +47,17 @@ with your task.
|
||||
`/myapp`, never `**/myapp`, which would also match `cmd/myapp/`. An
|
||||
existing repo is where such a binary is likeliest to already be sitting in
|
||||
the build context, invisible to git.
|
||||
- [ ] `.dockerignore` excludes `.claude`, root-anchored and with no `**/`
|
||||
prefix. Agent worktrees are entire checkouts of the repo, so they inflate
|
||||
the context by a multiple of it and can copy another session's unreviewed
|
||||
work into an image layer. Confirm by enumerating the image, not by reading
|
||||
the file — `.gitignore` hides these from `git status` too.
|
||||
- [ ] If the repo embeds a version in a binary, that version is computed on the
|
||||
host and passed with `--build-arg VERSION=...` by `script/docker` and
|
||||
`script/cibuild`. No stage calls `git describe`: `.dockerignore` excludes
|
||||
`.git`, so it yields an empty version without failing the build. A
|
||||
tag-derived version additionally needs `fetch-depth: 0` on the CI checkout
|
||||
step, which clones shallow and fetches no tags by default.
|
||||
- [ ] Every depth-independent pattern in `.dockerignore` carries a `**/` prefix;
|
||||
only genuinely root-anchored entries such as `.git` are unprefixed, and
|
||||
`.gitignore`'s patterns have not been transplanted unmodified.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Go HTTP Server Conventions
|
||||
last_modified: 2026-02-22
|
||||
last_modified: 2026-08-09
|
||||
---
|
||||
|
||||
This document defines the architectural patterns, design decisions, and
|
||||
@@ -991,7 +991,12 @@ func main() {
|
||||
Use ldflags to inject version information at build time:
|
||||
|
||||
```makefile
|
||||
VERSION := $(shell git describe --tags --always)
|
||||
# ?= so a Docker build can supply the value. `.dockerignore` excludes `.git`,
|
||||
# so inside a build stage `$(shell git describe ...)` expands to the empty
|
||||
# string without failing and the binary reports no version. The Dockerfile
|
||||
# declares `ARG VERSION` in the stage that compiles, and `script/docker` /
|
||||
# `script/cibuild` compute it on the host — see REPO_POLICIES.md.
|
||||
VERSION ?= $(shell git describe --tags --always)
|
||||
BUILDARCH := $(shell go env GOARCH)
|
||||
|
||||
build:
|
||||
|
||||
@@ -35,7 +35,11 @@ Template files can be fetched from:
|
||||
|
||||
- [ ] `.gitignore` — fetch from
|
||||
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.gitignore`, extend for
|
||||
language-specific artifacts
|
||||
language-specific artifacts. Extensions are written to `.gitignore`'s own
|
||||
semantics, where an unanchored pattern already matches at every depth:
|
||||
never add a `**/` prefix here, which is a `.dockerignore` form. The
|
||||
canonical file already carries `.claude/` so agent worktrees cannot be
|
||||
committed by accident.
|
||||
- [ ] `.editorconfig` — fetch from
|
||||
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.editorconfig`
|
||||
- [ ] `Makefile` — fetch from
|
||||
@@ -59,7 +63,13 @@ Template files can be fetched from:
|
||||
`.gitignore`'s patterns: `.dockerignore` anchors an unprefixed pattern at
|
||||
the context root, so the copied form leaves `config/.env` in the build
|
||||
context while reading as solved. See the `.dockerignore` rule in
|
||||
`REPO_POLICIES.md`.
|
||||
`REPO_POLICIES.md`. The canonical file's `.claude` entry is anchored for
|
||||
the same reason as a repo-root binary; leave it that way.
|
||||
- If the image embeds a version in a binary, the version is computed on the
|
||||
host and passed with `--build-arg VERSION=...`. `ARG VERSION=dev` is
|
||||
declared in the stage that compiles, and **no stage calls `git describe`**
|
||||
— `.dockerignore` excludes `.git`, so it yields an empty version without
|
||||
failing the build.
|
||||
- All Dockerfiles must run `make check` as a build step, and every stage
|
||||
containing a check-running `RUN` must declare `ARG CHECK_EPOCH` with the
|
||||
`RUN [ -n "$CHECK_EPOCH" ] || exit 1` guard immediately below it — see the
|
||||
@@ -104,11 +114,17 @@ are thin shims calling them. Model scripts:
|
||||
for the image tag)
|
||||
- [ ] `script/docker` / `make docker` — builds Docker image, tagged via
|
||||
`script/projectname` (byte-identical across repos); assigns
|
||||
`epoch="$(date +%s%N)$$"` on its own line and passes
|
||||
`--build-arg CHECK_EPOCH="$epoch"`
|
||||
- [ ] `script/cibuild` — cd to repo root, assign `epoch="$(date +%s%N)$$"` on
|
||||
its own line, then run `docker build --build-arg CHECK_EPOCH="$epoch" .`
|
||||
(what CI runs). The build arg is mandatory: see the `CHECK_EPOCH` rule in
|
||||
`epoch="$(date +%s%N)$$"` and the `git describe` version on their own
|
||||
lines, and passes `--build-arg CHECK_EPOCH="$epoch"` and
|
||||
`--build-arg VERSION="$version"`
|
||||
- [ ] `script/cibuild` — cd to repo root, assign `epoch="$(date +%s%N)$$"` and
|
||||
`version="$(git describe --tags --always --dirty 2>/dev/null || echo unknown)"`
|
||||
on their own lines, then run
|
||||
`docker build --build-arg CHECK_EPOCH="$epoch" --build-arg VERSION="$version" .`
|
||||
(what CI runs). Both build args are mandatory, and both assignments must
|
||||
be 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. 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.
|
||||
- [ ] `script/precommit` — called by the pre-commit hook; runs `script/check`
|
||||
|
||||
@@ -60,19 +60,21 @@ style conventions are in separate documents:
|
||||
prerequisite since nvm requires bash. yarn is then pinned via
|
||||
`corepack prepare yarn@<version> --activate`. Never install "latest" or "lts";
|
||||
always exact versions. `script/cibuild` runs the CI build: it changes to the
|
||||
repo root and runs `docker build --build-arg CHECK_EPOCH="$epoch" .`, where
|
||||
`epoch` is a per-invocation nonce (see the `CHECK_EPOCH` rule below); the
|
||||
Gitea workflow calls it. 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
|
||||
name. Scripts that need the name call `script/projectname` — e.g.
|
||||
`script/docker` assembles its image tag from it — so those scripts stay
|
||||
byte-identical across all repos. Repo-type-specific pre-commit extras (e.g.
|
||||
`go mod tidy` verification in Go repos) belong in `script/precommit`, not in
|
||||
the hook itself. Model scripts are at
|
||||
repo root and runs
|
||||
`docker build --build-arg CHECK_EPOCH="$epoch" --build-arg VERSION="$version" .`,
|
||||
where `epoch` is a per-invocation nonce (see the `CHECK_EPOCH` rule below) and
|
||||
`version` is computed on the host because `.git` is not in the build context
|
||||
(see the git-describe rule below); the Gitea workflow calls it. 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 name. Scripts that need the name call
|
||||
`script/projectname` — e.g. `script/docker` assembles its image tag from it —
|
||||
so those scripts stay byte-identical across all repos. Repo-type-specific
|
||||
pre-commit extras (e.g. `go mod tidy` verification in Go repos) belong in
|
||||
`script/precommit`, not in the hook itself. Model scripts are at
|
||||
`https://git.eeqj.de/sneak/prompts/raw/branch/main/script/<name>`. The README
|
||||
must document the provided scripts in an **Entrypoints** section (see the
|
||||
README requirements below).
|
||||
@@ -122,11 +124,18 @@ style conventions are in separate documents:
|
||||
|
||||
```sh
|
||||
epoch="$(date +%s%N)$$"
|
||||
docker build --build-arg CHECK_EPOCH="$epoch" .
|
||||
version="$(git describe --tags --always --dirty 2>/dev/null || echo unknown)"
|
||||
[ -n "$version" ] || version="unknown"
|
||||
docker build \
|
||||
--build-arg CHECK_EPOCH="$epoch" \
|
||||
--build-arg VERSION="$version" \
|
||||
.
|
||||
```
|
||||
|
||||
All four elements are load-bearing; none is optional, and each guards a
|
||||
failure mode that otherwise fails green:
|
||||
The `VERSION` lines are there for a different reason, covered by the
|
||||
git-describe rule below; they are shown here so the two rules do not each
|
||||
document half a command. All four `CHECK_EPOCH` elements are load-bearing;
|
||||
none is optional, and each guards a failure mode that otherwise fails green:
|
||||
- `ARG` is stage-scoped, so a single declaration leaves the other check
|
||||
stages frozen while the fix reviews as complete. Declare it in every stage
|
||||
that runs checks, immediately above the first such `RUN`.
|
||||
@@ -197,6 +206,9 @@ style conventions are in separate documents:
|
||||
RUN [ -n "$CHECK_EPOCH" ] || exit 1
|
||||
RUN echo "check epoch: ${CHECK_EPOCH}" && make test
|
||||
|
||||
# VERSION comes from the host via --build-arg; see the git-describe rule
|
||||
# below. Never run `git describe` here: .dockerignore excludes .git, so
|
||||
# it yields an empty version without failing the build.
|
||||
ARG VERSION=dev
|
||||
RUN CGO_ENABLED=0 go build -trimpath \
|
||||
-ldflags="-s -w -X main.Version=${VERSION}" \
|
||||
@@ -247,18 +259,24 @@ style conventions are in separate documents:
|
||||
each stage is invalidated at two independent points. The later `RUN`s in
|
||||
the same stage need no expansion of their own: they are already
|
||||
invalidated by their busted parent layer.
|
||||
- `ARG VERSION=dev` is declared in the build stage, and its value is
|
||||
supplied on the host by `script/docker` and `script/cibuild` via
|
||||
`--build-arg VERSION=...`. The `dev` default is a placeholder for a local
|
||||
build, not a source of truth. **No stage may call `git describe`**:
|
||||
`.dockerignore` excludes `.git`, so it yields an empty version without
|
||||
failing. See the git-describe rule further down.
|
||||
|
||||
- Every repo should have a Gitea Actions workflow (`.gitea/workflows/`) that
|
||||
runs `script/cibuild` (which runs
|
||||
`docker build --build-arg CHECK_EPOCH="$epoch" .`) on push. The Dockerfile
|
||||
runs `make check`, so a successful build implies all checks pass — but that
|
||||
implication holds **only** because of the `CHECK_EPOCH` cache-bust described
|
||||
above. Without it, an unchanged tree serves the check layer from cache and the
|
||||
build reports a green it never earned. A bare `docker build .` fails closed by
|
||||
design, on the `[ -n "$CHECK_EPOCH" ]` guard; always go through
|
||||
`script/cibuild` or `script/docker`. Never accept a `script/cibuild` pass as
|
||||
evidence without confirming it ran: a sub-second wall time, or `CACHED` on the
|
||||
check layer, means nothing was executed.
|
||||
`docker build --build-arg CHECK_EPOCH="$epoch" --build-arg VERSION="$version" .`)
|
||||
on push. The Dockerfile runs `make check`, so a successful build implies all
|
||||
checks pass — but that implication holds **only** because of the `CHECK_EPOCH`
|
||||
cache-bust described above. Without it, an unchanged tree serves the check
|
||||
layer from cache and the build reports a green it never earned. A bare
|
||||
`docker build .` fails closed by design, on the `[ -n "$CHECK_EPOCH" ]` guard;
|
||||
always go through `script/cibuild` or `script/docker`. Never accept a
|
||||
`script/cibuild` pass as evidence without confirming it ran: a sub-second wall
|
||||
time, or `CACHED` on the check layer, means nothing was executed.
|
||||
|
||||
- Use platform-standard formatters: `black` for Python, `prettier` for
|
||||
JS/CSS/Markdown/HTML, `go fmt` for Go. Always use default configuration with
|
||||
@@ -328,8 +346,10 @@ style conventions are in separate documents:
|
||||
must be in `.gitignore`. No exceptions.
|
||||
|
||||
- `.gitignore` should be comprehensive from the start: OS files (`.DS_Store`),
|
||||
editor files (`.swp`, `*~`), language build artifacts, and `node_modules/`.
|
||||
Fetch the standard `.gitignore` from
|
||||
editor files (`.swp`, `*~`), in-repo agent scratch directories (`.claude/`,
|
||||
which holds one worktree — an entire additional checkout of the repo — per
|
||||
in-flight agent), language build artifacts, and `node_modules/`. Fetch the
|
||||
standard `.gitignore` from
|
||||
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.gitignore` when setting up
|
||||
a new repo. These patterns are written to `.gitignore`'s own semantics, in
|
||||
which an unanchored pattern already matches at every depth. They are not a
|
||||
@@ -349,7 +369,8 @@ style conventions are in separate documents:
|
||||
depth-independent pattern the `**/` prefix — `**/node_modules`,
|
||||
`**/.DS_Store`, and the secret patterns in the canonical file, which are
|
||||
additionally case-folded per the rule below — and leave only genuinely
|
||||
root-anchored entries such as `.git` unprefixed. The inverse move is equally
|
||||
root-anchored entries unprefixed: `.git`, the in-repo agent scratch directory
|
||||
`.claude`, and the repo's own host-built binary. The inverse move is equally
|
||||
wrong: never apply `**/` to `.gitignore`, where it is redundant and produces a
|
||||
file that is wrong in a way that looks careful. Each file is written to its
|
||||
own semantics; neither is derived from the other. Fetch the standard
|
||||
@@ -361,6 +382,20 @@ style conventions are in separate documents:
|
||||
anchored, `/myapp` and never `**/myapp`: the prefixed form also matches
|
||||
`cmd/myapp/` and deletes the package directory from the context.
|
||||
|
||||
- **In-repo agent scratch belongs in both files, written to each file's own
|
||||
semantics.** `.claude/` holds one worktree per in-flight agent — an entire
|
||||
additional checkout of the repo — so with `COPY . .` the build context
|
||||
inflates by a multiple of the repo, and another session's unreviewed,
|
||||
sometimes uncommitted work can be copied into an image layer. The directory is
|
||||
also created and destroyed constantly, so it invalidates `COPY . .` for
|
||||
reasons that have nothing to do with the repo's own content. In `.gitignore`
|
||||
the entry is `.claude/`, unanchored, which already matches at every depth. In
|
||||
`.dockerignore` it is `.claude`, anchored and with **no** `**/` prefix: the
|
||||
directory occurs exactly once, at the context root, and the prefixed form
|
||||
would also match any nested directory of that name. It is not case-folded the
|
||||
way the secret patterns are, because tooling creates it in exactly one
|
||||
spelling and a miss costs context bloat rather than exposure.
|
||||
|
||||
- **`.dockerignore` matching is case-sensitive, so cover capitalisation with
|
||||
character classes rather than by doubling patterns.** `**/*.key` does not
|
||||
match `certs/SERVER.KEY`, which is reachable on the case-insensitive
|
||||
@@ -390,6 +425,42 @@ style conventions are in separate documents:
|
||||
bytes, and BuildKit transfers only the delta from the previous build, so the
|
||||
reported size describes the transfer and not the contents of the image.
|
||||
|
||||
- **Excluding `.git` means `git describe` cannot run inside any build stage, and
|
||||
it fails quietly there.** The `GOLDFLAGS` version-embedding pattern assumes
|
||||
`.git` is present; in a build stage there is no repository, so `git describe`
|
||||
writes nothing to stdout and the `-X main.Version=` value comes out **empty**
|
||||
rather than erroring. The binary then reports no version at all and the build
|
||||
still exits 0. Compute the version **on the host** and thread it in as a build
|
||||
arg. `script/docker` and `script/cibuild` do this, byte-identically across
|
||||
repos:
|
||||
|
||||
```sh
|
||||
# Assign on its own line: a failing command substitution inside an
|
||||
# argument does not trip `set -e`, so the inline form degrades to an
|
||||
# empty constant — the same silent-empty failure this rule is about.
|
||||
version="$(git describe --tags --always --dirty 2>/dev/null || echo unknown)"
|
||||
[ -n "$version" ] || version="unknown"
|
||||
docker build \
|
||||
--build-arg CHECK_EPOCH="$epoch" \
|
||||
--build-arg VERSION="$version" \
|
||||
.
|
||||
```
|
||||
|
||||
`--always` makes an untagged repo yield the abbreviated commit hash instead
|
||||
of failing, and the `unknown` fallback covers a build from an export with no
|
||||
`.git` at all. Both are non-empty by construction: an empty version reads as
|
||||
a successful one, which is precisely the failure being closed. The
|
||||
Dockerfile's side is `ARG VERSION=dev` in the stage that compiles, declared
|
||||
there and not inherited, because `ARG` is stage-scoped exactly as
|
||||
`CHECK_EPOCH` is. Passing `VERSION` to a repo whose Dockerfile declares no
|
||||
such `ARG` is silently ignored by BuildKit and costs nothing, which is why
|
||||
the scripts stay byte-identical rather than growing a per-repo variant.
|
||||
|
||||
One consequence for CI: the standard checkout action clones shallow and
|
||||
fetches no tags, so `git describe --tags` there falls back to a bare commit
|
||||
hash. A repo that embeds a tag-derived version must set `fetch-depth: 0` on
|
||||
its checkout step; a repo that does not embed a version needs no change.
|
||||
|
||||
- **No build artifacts in version control.** Code-derived data (compiled
|
||||
bundles, minified output, generated assets) must never be committed to the
|
||||
repository if it can be avoided. The build process (e.g. Dockerfile, Makefile)
|
||||
|
||||
@@ -14,7 +14,18 @@ main() {
|
||||
# nonce to an empty constant. `$$` is required because busybox `date`
|
||||
# drops %N without erroring.
|
||||
epoch="$(date +%s%N)$$"
|
||||
docker build --build-arg CHECK_EPOCH="$epoch" .
|
||||
# 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, plus a non-empty fallback, so a repo built from an export
|
||||
# with no .git reports `unknown` rather than an empty version that
|
||||
# reads as a successful one.
|
||||
version="$(git describe --tags --always --dirty 2>/dev/null || echo unknown)"
|
||||
[ -n "$version" ] || version="unknown"
|
||||
docker build \
|
||||
--build-arg CHECK_EPOCH="$epoch" \
|
||||
--build-arg VERSION="$version" \
|
||||
.
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
@@ -16,7 +16,17 @@ main() {
|
||||
# nonce to an empty constant. `$$` is required because busybox `date`
|
||||
# drops %N without erroring.
|
||||
epoch="$(date +%s%N)$$"
|
||||
docker build --build-arg CHECK_EPOCH="$epoch" \
|
||||
# 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, plus a non-empty fallback, so a repo built from an export
|
||||
# with no .git reports `unknown` rather than an empty version that
|
||||
# reads as a successful one.
|
||||
version="$(git describe --tags --always --dirty 2>/dev/null || echo unknown)"
|
||||
[ -n "$version" ] || version="unknown"
|
||||
docker build \
|
||||
--build-arg CHECK_EPOCH="$epoch" \
|
||||
--build-arg VERSION="$version" \
|
||||
-t "$("$SCRIPT_DIR/projectname")" .
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user